Repository: getkin/kin-openapi
Pull request #1066 (2025-04-28 08:54:33)

openapi3: resolve Snyk security warning with path traversal
I am using kin-openapi on a project and Snyk blocked me with an error:

> **Path Traversal**
> Unsanitized input from the request URL flows into os.ReadFile, where it is used as a path. This may result in a Path Traversal vulnerability and allow an attacker to read arbitrary files.

This PR proposes a remediation.

Thank for this really useful library!


Patch:
diff --git a/openapi2conv/openapi2_conv.go b/openapi2conv/openapi2_conv.go
index 8aa4525..c32d1a6 100644
--- a/openapi2conv/openapi2_conv.go
+++ b/openapi2conv/openapi2_conv.go
@@ -563,6 +563,9 @@ func convertRefsInV3SchemaRef(from *openapi3.SchemaRef) *openapi3.SchemaRef {
 	if to.Value != nil {
 		v := *from.Value
 		to.Value = &v
+		if to.Value.Items != nil {
+			to.Value.Items.Ref = ToV3Ref(to.Value.Items.Ref)
+		}
 		to.Value.AdditionalProperties = toV3AdditionalProperties(to.Value.AdditionalProperties)
 	}
 	return &to
diff --git a/openapi3/loader_uri_reader.go b/openapi3/loader_uri_reader.go
index b023dfb..5dc4a9b 100644
--- a/openapi3/loader_uri_reader.go
+++ b/openapi3/loader_uri_reader.go
@@ -7,6 +7,7 @@ import (
 	"net/http"
 	"net/url"
 	"os"
+	"path"
 	"path/filepath"
 	"sync"
 )
@@ -79,7 +80,7 @@ func ReadFromFile(loader *Loader, location *url.URL) ([]byte, error) {
 	if !is_file(location) {
 		return nil, ErrURINotSupported
 	}
-	return os.ReadFile(filepath.FromSlash(location.Path))
+	return os.ReadFile(path.Clean(filepath.FromSlash(location.Path)))
 }
 
 // URIMapCache returns a ReadFromURIFunc that caches the contents read from URI


Test patch:
diff --git a/openapi2conv/issue1049_test.go b/openapi2conv/issue1049_test.go
new file mode 100644
index 0000000..04ddf85
--- /dev/null
+++ b/openapi2conv/issue1049_test.go
@@ -0,0 +1,86 @@
+package openapi2conv
+
+import (
+	"context"
+	"encoding/json"
+	"testing"
+
+	"github.com/stretchr/testify/require"
+)
+
+func TestIssue1049(t *testing.T) {
+	spec := `
+{
+  "swagger": "2.0",
+  "info": {
+    "description": "Test for additionalProperties",
+    "version": "v1",
+    "title": "API Test"
+  },
+  "host": "Test",
+  "paths": {
+    "/map": {
+      "post": {
+        "summary": "api test summary",
+        "description": "api test description",
+        "operationId": "apiTestUsingPOST",
+        "consumes": ["application/json"],
+        "produces": ["*/*"],
+        "responses": {
+          "200": {
+            "description": "OK",
+            "schema": {
+              "$ref": "#/definitions/ResponseOfMap"
+            }
+          }
+        }
+      }
+    }
+  },
+  "definitions": {
+    "BaseDictResp": {
+      "type": "object",
+      "properties": {
+        "key": {
+          "type": "string",
+          "description": "key of map"
+        },
+        "value": {
+          "type": "string",
+          "description": "value of map"
+        }
+      },
+      "title": "BaseDictResp",
+      "description": "BaseDictResp description"
+    },
+
+    "ResponseOfMap": {
+      "type": "object",
+      "properties": {
+        "data": {
+          "type": "object",
+          "description": "response data",
+          "additionalProperties": {
+            "type": "array",
+            "items": {
+              "$ref": "#/definitions/BaseDictResp"
+            }
+          }
+        }
+      },
+      "title": "ResponseOfMap"
+    }
+  }
+}
+`
+	doc3, err := v2v3JSON([]byte(spec))
+	require.NoError(t, err)
+
+	spec3, err := json.Marshal(doc3)
+	require.NoError(t, err)
+	const expected = `{"components":{"schemas":{"BaseDictResp":{"description":"BaseDictResp description","properties":{"key":{"description":"key of map","type":"string"},"value":{"description":"value of map","type":"string"}},"title":"BaseDictResp","type":"object"},"ResponseOfMap":{"properties":{"data":{"additionalProperties":{"items":{"$ref":"#/components/schemas/BaseDictResp"},"type":"array"},"description":"response data","type":"object"}},"title":"ResponseOfMap","type":"object"}}},"info":{"description":"Test for additionalProperties","title":"API Test","version":"v1"},"openapi":"3.0.3","paths":{"/map":{"post":{"description":"api test description","operationId":"apiTestUsingPOST","responses":{"200":{"content":{"*/*":{"schema":{"$ref":"#/components/schemas/ResponseOfMap"}}},"description":"OK"}},"summary":"api test summary"}}},"servers":[{"url":"https://Test/"}]}`
+	require.JSONEq(t, expected, string(spec3))
+
+	err = doc3.Validate(context.Background())
+	require.NoError(t, err)
+}