Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
b22cfd8
Applied schema manipulation to request body as well.
MaciekMis Mar 25, 2026
22182c8
fix
MaciekMis Mar 25, 2026
17dd89a
Applying unicode transformation at write / load from file.
MaciekMis Mar 26, 2026
2e5d9cf
Modify a clone instead of original oas spec
MaciekMis Mar 26, 2026
7e7e15c
Clone error
MaciekMis Mar 27, 2026
a8daee4
better error handling
MaciekMis Mar 27, 2026
b979a82
detailed error message
MaciekMis Mar 27, 2026
d7a6bec
Merge branch 'master' into TT-12238-javascript-regex-issue-on-oas-api…
MaciekMis Mar 31, 2026
7f747bd
test for LoadDefFromFilePath func
MaciekMis Mar 31, 2026
1c5ba77
Merge branch 'master' into TT-12238-javascript-regex-issue-on-oas-api…
MaciekMis Mar 31, 2026
7416c47
removed embedded field T
MaciekMis Mar 31, 2026
d82ab61
Merge remote-tracking branch 'origin/TT-12238-javascript-regex-issue-…
MaciekMis Mar 31, 2026
1d32fa2
removed unnecessary error validation
MaciekMis Apr 2, 2026
e21ac2a
Merge branch 'master' into TT-12238-javascript-regex-issue-on-oas-api…
MaciekMis Apr 2, 2026
c8c2b28
tyk extension fix
MaciekMis Apr 2, 2026
ba701f4
moved visitor up in the chain
MaciekMis Apr 2, 2026
9152c17
Merge branch 'master' into TT-12238-javascript-regex-issue-on-oas-api…
MaciekMis Apr 7, 2026
90fdb81
Cloning oas object when writing to a file, disabled linter for clone …
MaciekMis Apr 7, 2026
21f9b92
Merge branch 'master' into TT-12238-javascript-regex-issue-on-oas-api…
MaciekMis Apr 9, 2026
d0aad7f
Merge branch 'master' into TT-12238-javascript-regex-issue-on-oas-api…
MaciekMis Apr 9, 2026
d3975ac
Merge branch 'master' into TT-12238-javascript-regex-issue-on-oas-api…
radkrawczyk Apr 10, 2026
3bd2a89
Merge branch 'master' into TT-12238-javascript-regex-issue-on-oas-api…
MaciekMis Apr 14, 2026
a0ebcd7
Merge branch 'master' into TT-12238-javascript-regex-issue-on-oas-api…
ilijabojanovic Apr 14, 2026
160c328
Merge branch 'master' into TT-12238-javascript-regex-issue-on-oas-api…
radkrawczyk Apr 15, 2026
be3b306
Merge branch 'master' into TT-12238-javascript-regex-issue-on-oas-api…
radkrawczyk Apr 16, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 24 additions & 9 deletions gateway/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -1275,11 +1275,21 @@
defer gw.apisMu.RUnlock()

obj, code := gw.handleGetAPI(apiID, true)
if apiOAS, ok := obj.(*oas.OAS); ok && modePublic {
apiOAS.RemoveTykExtension()
if apiOAS, ok := obj.(*oas.OAS); ok {
// We have to operate on oas clone in order to preserve original state after any manipulations on schema.
oasClone, _ := apiOAS.Clone() // nolint:errcheck

Check warning on line 1280 in gateway/api.go

View check run for this annotation

probelabs / Visor: security

logic Issue

The error returned from `apiOAS.Clone()` is unchecked. While the current implementation may not return an error, this could change in the future, leading to silent failures. This also affects line 1461.
Raw output
Check the error returned from `apiOAS.Clone()` and handle it appropriately, for example by logging it or returning an error response. This applies to all calls to `Clone()` where the error is currently ignored.
if modePublic {

Check warning on line 1281 in gateway/api.go

View check run for this annotation

probelabs / Visor: quality

architecture Issue

The error returned by `apiOAS.Clone()` is ignored using `// nolint:errcheck`. While this function may currently never return an error, this assumption is fragile and can lead to silent failures if the underlying implementation changes. An unhandled error during a clone operation could lead to data corruption or inconsistent state.
Raw output
Handle the error explicitly. If an error here is considered an unrecoverable state (which it likely is), the program should panic or log a fatal error to ensure the failure is immediately visible and stops further execution with a potentially nil or invalid object.
oasClone.RemoveTykExtension()

Check warning on line 1282 in gateway/api.go

View check run for this annotation

probelabs / Visor: performance

performance Issue

Cloning and processing a large OAS object on every GET request can be inefficient. The `apiOAS.Clone()` operation followed by `visitor.ProcessOAS()` introduces CPU and memory overhead for each call to this endpoint. For large and complex OAS definitions, this can lead to increased latency and garbage collection pressure under load.
Raw output
Consider caching the processed, public-facing OAS definition. When an API is loaded or updated, you could generate and cache both the internal representation and the public representation (with Tyk extensions removed and regexes restored). This would trade a small amount of memory for a significant performance gain on this read-heavy endpoint, turning the expensive computation into a simple cache lookup.
}

visitor := schema.NewVisitor()
visitor.AddSchemaManipulation(schema.RestoreUnicodeEscapesFromRE2Manipulation)
visitor.ProcessOAS(oasClone)

obj = oasClone
}
return obj, code

return obj, code
}

func (gw *Gateway) handleAddApi(r *http.Request, fs afero.Fs, oasEndpoint bool) (interface{}, int) {
Expand Down Expand Up @@ -1448,7 +1458,7 @@
func (gw *Gateway) writeOASAndAPIDefToFile(fs afero.Fs, apiDef *apidef.APIDefinition, oasObj *oas.OAS) (err error, errCode int) {
err, errCode = gw.writeToFile(fs, apiDef, apiDef.APIID)
if err != nil {
return

Check warning on line 1461 in gateway/api.go

View check run for this annotation

probelabs / Visor: security

logic Issue

The error returned from `oasObj.Clone()` is unchecked. While the current implementation may not return an error, this could change in the future, leading to silent failures. This also affects line 1280.
Raw output
Check the error returned from `oasObj.Clone()` and handle it appropriately, for example by logging it or returning an error response. This applies to all calls to `Clone()` where the error is currently ignored.

Check warning on line 1461 in gateway/api.go

View check run for this annotation

probelabs / Visor: quality

architecture Issue

The error returned by `oasObj.Clone()` is ignored using `// nolint:errcheck`. While this function may currently never return an error, this assumption is fragile and can lead to silent failures if the underlying implementation changes. An unhandled error during a clone operation could lead to inconsistent data being written to the filesystem.
Raw output
Handle the error explicitly. If an error here is considered an unrecoverable state, the program should panic or log a fatal error to ensure the failure is immediately visible and stops further execution before writing a potentially invalid object to a file.
}

suffix := "-oas"
Expand All @@ -1456,7 +1466,12 @@
suffix = "-mcp"
}

err, errCode = gw.writeToFile(fs, oasObj, apiDef.APIID+suffix)
oasDeepCopy, _ := oasObj.Clone() // nolint:errcheck
visitor := schema.NewVisitor()
visitor.AddSchemaManipulation(schema.RestoreUnicodeEscapesFromRE2Manipulation)
visitor.ProcessOAS(oasDeepCopy)

err, errCode = gw.writeToFile(fs, oasDeepCopy, apiDef.APIID+suffix)
if err != nil {
return
}
Expand Down Expand Up @@ -1628,11 +1643,6 @@
}

if oasAPI, ok := obj.(*oas.OAS); ok {
visitor := schema.NewVisitor()
visitor.AddSchemaManipulation(schema.RestoreUnicodeEscapesFromRE2Manipulation)
visitor.ProcessOAS(oasAPI)
obj = oasAPI

gw.setBaseAPIIDHeader(w, oasAPI)
}

Expand Down Expand Up @@ -3685,7 +3695,7 @@
t, err := loader.LoadFromData(reqBodyInBytes)
if err != nil {
return nil, nil, ErrRequestMalformed
}

Check warning on line 3698 in gateway/api.go

View check run for this annotation

probelabs / Visor: performance

performance Issue

The function re-marshals the modified OAS object into `reqBodyInBytes`, but this return value appears to be unused by its callers (`handleAddApi` and `handleUpdateAPI`). This results in unnecessary CPU and memory allocation for a serialization operation whose result is immediately discarded.
Raw output
Verify the usage of the returned `reqBodyInBytes` in the calling functions. If it is indeed unused, remove the `json.Marshal` call and the related error handling. The function signature can be updated to no longer return the `[]byte` slice.

oasObj.T = *t

Expand All @@ -3693,6 +3703,11 @@
visitor.AddSchemaManipulation(schema.TransformUnicodeEscapesToRE2Manipulation)
visitor.ProcessOAS(&oasObj)

reqBodyInBytes, err = json.Marshal(&oasObj)
if err != nil {
return nil, nil, ErrRequestMalformed
}

return reqBodyInBytes, &oasObj, nil
}

Expand Down
9 changes: 8 additions & 1 deletion gateway/api_definition.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ import (
"github.com/TykTechnologies/tyk/config"
"github.com/TykTechnologies/tyk/header"
"github.com/TykTechnologies/tyk/internal/model"
"github.com/TykTechnologies/tyk/pkg/schema"
"github.com/TykTechnologies/tyk/regexp"
"github.com/TykTechnologies/tyk/rpc"
"github.com/TykTechnologies/tyk/storage"
Expand Down Expand Up @@ -773,7 +774,13 @@ func (a APIDefinitionLoader) loadDefFromFilePath(filePath string) (*APISpec, err

oasDoc, err := loader.LoadFromFile(oasFilepath)
if err == nil {
nestDef.OAS = &oas.OAS{T: *oasDoc}
oasObj := &oas.OAS{T: *oasDoc}

visitor := schema.NewVisitor()
visitor.AddSchemaManipulation(schema.TransformUnicodeEscapesToRE2Manipulation)
visitor.ProcessOAS(oasObj)

nestDef.OAS = oasObj
}
}

Expand Down
109 changes: 109 additions & 0 deletions gateway/api_definition_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import (
"net"
"net/http"
"net/http/httptest"
"os"
"path/filepath"
"strings"
"sync"
"testing"
Expand Down Expand Up @@ -2937,3 +2939,110 @@ func TestURLAllowedAndIgnored_CORSPreflight(t *testing.T) {
assert.Equal(t, EndPointNotAllowed, status)
})
}

func TestLoadDefFromFilePath(t *testing.T) {
ts := StartTest(nil)
defer ts.Close()

loader := APIDefinitionLoader{Gw: ts.Gw}

t.Run("load classic definition", func(t *testing.T) {
apiName := "Test API"
apiID := "test-api-1"
def := apidef.APIDefinition{
Name: apiName,
APIID: apiID,
Proxy: apidef.ProxyConfig{
ListenPath: "/test-api-1",
},
}
data, err := json.Marshal(def)
assert.NoError(t, err)

tmpFile, err := os.CreateTemp("", "api_def_*.json")
assert.NoError(t, err)
defer os.Remove(tmpFile.Name())

_, err = tmpFile.Write(data)
assert.NoError(t, err)
tmpFile.Close()

spec, err := loader.loadDefFromFilePath(tmpFile.Name())
assert.NoError(t, err)
assert.NotNil(t, spec)
assert.Equal(t, apiName, spec.Name)
assert.Equal(t, apiID, spec.APIID)
})

t.Run("load OAS definition", func(t *testing.T) {
tmpDir, err := os.MkdirTemp("", "oas_test")
assert.NoError(t, err)
defer os.RemoveAll(tmpDir)

apiFilePath := filepath.Join(tmpDir, "test-api.json")
oasFilePath := filepath.Join(tmpDir, "test-api-oas.json")

// Test whether schema validator successfully applies schema manipulation to oas doc.
expectedPatternAfterLoad := "{\"^[\\\\x{0000}-\\\\x{017f}]*$\"}"
schema := openapi3.NewSchema()
schema.Pattern = "{\"^[\\\\u0000-\\\\u017f]*$\"}"
oasDoc := &oas.OAS{
T: openapi3.T{
Components: &openapi3.Components{
Schemas: openapi3.Schemas{
"Schema1": openapi3.NewSchemaRef("", schema),
},
},
},
}
oasData, err := json.Marshal(oasDoc)
assert.NoError(t, err)

err = os.WriteFile(oasFilePath, oasData, 0644)
assert.NoError(t, err)

apiName := "Test OAS API"
apiID := "test-oas-api-1"
def := apidef.APIDefinition{
Name: apiName,
APIID: apiID,
IsOAS: true,
Proxy: apidef.ProxyConfig{
ListenPath: "/test-oas-api-1",
},
}
data, err := json.Marshal(def)
assert.NoError(t, err)

err = os.WriteFile(apiFilePath, data, 0644)
assert.NoError(t, err)

spec, err := loader.loadDefFromFilePath(apiFilePath)
assert.NoError(t, err)
assert.NotNil(t, spec)
assert.Equal(t, apiName, spec.Name)
assert.Equal(t, apiID, spec.APIID)
assert.NotNil(t, spec.OAS)
assert.Equal(t, expectedPatternAfterLoad, spec.OAS.Components.Schemas["Schema1"].Value.Pattern)
})

t.Run("file not found", func(t *testing.T) {
spec, err := loader.loadDefFromFilePath("non_existent_file.json")
assert.Error(t, err)
assert.Nil(t, spec)
})

t.Run("invalid json", func(t *testing.T) {
tmpFile, err := os.CreateTemp("", "api_def_*.json")
assert.NoError(t, err)
defer os.Remove(tmpFile.Name())

_, err = tmpFile.Write([]byte("{invalid json}"))
assert.NoError(t, err)
tmpFile.Close()

spec, err := loader.loadDefFromFilePath(tmpFile.Name())
assert.Error(t, err)
assert.Nil(t, spec)
})
}
Loading