@@ -12,6 +12,7 @@ import (
1212 "net/url"
1313 "os"
1414 "path/filepath"
15+ "sort"
1516 "strings"
1617 "sync/atomic"
1718 texttemplate "text/template"
@@ -23,6 +24,7 @@ import (
2324 "github.com/TykTechnologies/tyk/internal/httpctx"
2425 "github.com/TykTechnologies/tyk/internal/httputil"
2526 "github.com/TykTechnologies/tyk/internal/mcp"
27+ "github.com/TykTechnologies/tyk/internal/oasutil"
2628
2729 "github.com/getkin/kin-openapi/routers/gorillamux"
2830
@@ -1372,7 +1374,6 @@ func (a APIDefinitionLoader) compileOASValidateRequestPathSpec(apiSpec *APISpec,
13721374 continue
13731375 }
13741376
1375- // Find the path and method for this operation
13761377 path , method := a .findPathAndMethodForOperation (apiSpec , operationID )
13771378 if path == "" || method == "" {
13781379 continue
@@ -1384,14 +1385,20 @@ func (a APIDefinitionLoader) compileOASValidateRequestPathSpec(apiSpec *APISpec,
13841385 OASPath : path ,
13851386 }
13861387
1387- // The path in OAS is relative to the server URL (listenPath)
1388- // For regex matching, we don't prepend listenPath because URLSpec.matchesPath
1389- // will strip the listenPath before matching
1390- // Use standard regex generation with gateway config
13911388 a .generateRegex (path , & newSpec , OASValidateRequest , conf )
13921389 urlSpec = append (urlSpec , newSpec )
13931390 }
13941391
1392+ urlSpec = a .addStaticPathShields (apiSpec , conf , urlSpec , OASValidateRequest , func (path , method string ) URLSpec {
1393+ return URLSpec {
1394+ OASValidateRequestMeta : & oas.ValidateRequest {Enabled : false },
1395+ OASMethod : method ,
1396+ OASPath : path ,
1397+ }
1398+ })
1399+
1400+ sortURLSpecsByPathPriority (urlSpec )
1401+
13951402 return urlSpec
13961403}
13971404
@@ -1417,7 +1424,6 @@ func (a APIDefinitionLoader) compileOASMockResponsePathSpec(apiSpec *APISpec, co
14171424 continue
14181425 }
14191426
1420- // Find the path and method for this operation
14211427 path , method := a .findPathAndMethodForOperation (apiSpec , operationID )
14221428 if path == "" || method == "" {
14231429 continue
@@ -1429,14 +1435,78 @@ func (a APIDefinitionLoader) compileOASMockResponsePathSpec(apiSpec *APISpec, co
14291435 OASPath : path ,
14301436 }
14311437
1432- // Use standard regex generation with gateway config
14331438 a .generateRegex (path , & newSpec , OASMockResponse , conf )
14341439 urlSpec = append (urlSpec , newSpec )
14351440 }
14361441
1442+ urlSpec = a .addStaticPathShields (apiSpec , conf , urlSpec , OASMockResponse , func (path , method string ) URLSpec {
1443+ return URLSpec {
1444+ OASMockResponseMeta : & oas.MockResponse {Enabled : false },
1445+ OASMethod : method ,
1446+ OASPath : path ,
1447+ }
1448+ })
1449+
1450+ sortURLSpecsByPathPriority (urlSpec )
1451+
1452+ return urlSpec
1453+ }
1454+
1455+ // addStaticPathShields adds synthetic disabled URLSpec entries for static OAS paths
1456+ // that don't already have an entry in urlSpec. These entries act as shields: when the
1457+ // middleware scans the path list, a static shield entry matches before any parameterised
1458+ // regex, and the middleware sees Enabled=false and skips it. This prevents parameterised
1459+ // paths (e.g. /employees/{id}) from incorrectly matching static paths (e.g. /employees/static).
1460+ //
1461+ // Shield entries are only added when urlSpec contains at least one parameterised path,
1462+ // since without parameterised paths there is no cross-matching risk.
1463+ func (a APIDefinitionLoader ) addStaticPathShields (
1464+ apiSpec * APISpec ,
1465+ conf config.Config ,
1466+ urlSpec []URLSpec ,
1467+ status URLStatus ,
1468+ newDisabledSpec func (path , method string ) URLSpec ,
1469+ ) []URLSpec {
1470+ if apiSpec .OAS .Paths == nil {
1471+ return urlSpec
1472+ }
1473+
1474+ existing , hasParameterised := indexURLSpecs (urlSpec )
1475+ if ! hasParameterised {
1476+ return urlSpec
1477+ }
1478+
1479+ for path , pathItem := range apiSpec .OAS .Paths .Map () {
1480+ if httputil .IsMuxTemplate (path ) {
1481+ continue
1482+ }
1483+ for method := range pathItem .Operations () {
1484+ method = strings .ToUpper (method )
1485+ if _ , exists := existing [path + ":" + method ]; exists {
1486+ continue
1487+ }
1488+ newSpec := newDisabledSpec (path , method )
1489+ a .generateRegex (path , & newSpec , status , conf )
1490+ urlSpec = append (urlSpec , newSpec )
1491+ }
1492+ }
1493+
14371494 return urlSpec
14381495}
14391496
1497+ // indexURLSpecs builds a set of "path:METHOD" keys from the given specs and reports
1498+ // whether any spec uses a parameterised (mux-template) path.
1499+ func indexURLSpecs (specs []URLSpec ) (existing map [string ]struct {}, hasParameterised bool ) {
1500+ existing = make (map [string ]struct {}, len (specs ))
1501+ for _ , spec := range specs {
1502+ existing [spec .OASPath + ":" + spec .OASMethod ] = struct {}{}
1503+ if httputil .IsMuxTemplate (spec .OASPath ) {
1504+ hasParameterised = true
1505+ }
1506+ }
1507+ return
1508+ }
1509+
14401510// findPathAndMethodForOperation finds the path and method for a given operation ID
14411511// by searching through the OAS paths.
14421512func (a APIDefinitionLoader ) findPathAndMethodForOperation (apiSpec * APISpec , operationID string ) (string , string ) {
@@ -1455,6 +1525,14 @@ func (a APIDefinitionLoader) findPathAndMethodForOperation(apiSpec *APISpec, ope
14551525 return "" , ""
14561526}
14571527
1528+ // sortURLSpecsByPathPriority sorts URLSpec entries using the same path priority
1529+ // rules as oasutil.SortByPathLength, ensuring consistent ordering across the gateway.
1530+ func sortURLSpecsByPathPriority (specs []URLSpec ) {
1531+ sort .Slice (specs , func (i , j int ) bool {
1532+ return oasutil .PathLess (specs [i ].OASPath , specs [j ].OASPath )
1533+ })
1534+ }
1535+
14581536// extractMCPPrimitivesToPaths extracts MCP primitives (tools, resources, prompts) from the OAS
14591537// definition and populates them into the ExtendedPaths structure for each API version.
14601538// It also adds built-in MCP operation paths (tools/call, resources/read, prompts/get) to
0 commit comments