diff --git a/docs/commands.md b/docs/commands.md index dc6e1fb505b..f3b922d717d 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -49,6 +49,7 @@ Flags: --config string path to configuration file --disable-full-descriptions disable request for full descriptions and use default vulnerability descriptions --disable-secrets disable secrets scanning + --enable-openapi-refs resolve the file reference, on OpenAPI files (default [false]) --exclude-categories strings exclude categories by providing its name cannot be provided with query inclusion flags can be provided multiple times or as a comma separated string @@ -102,7 +103,7 @@ Flags: --exclude-type strings case insensitive list of platform types not to scan (Ansible, AzureResourceManager, Buildah, CICD, CloudFormation, Crossplane, DockerCompose, Dockerfile, GRPC, GoogleDeploymentManager, Knative, Kubernetes, OpenAPI, Pulumi, ServerLessFW, Terraform) cannot be provided with type inclusion flags - + Global Flags: --ci display only log messages to CLI output (mutually exclusive with silent) -f, --log-format string determines log format (pretty,json) (default "pretty") diff --git a/docs/dockerhub.md b/docs/dockerhub.md index 6c30d17f3fe..341626f261d 100644 --- a/docs/dockerhub.md +++ b/docs/dockerhub.md @@ -86,6 +86,7 @@ Flags: --config string path to configuration file --disable-full-descriptions disable request for full descriptions and use default vulnerability descriptions --disable-secrets disable secrets scanning + --enable-openapi-refs resolve the file reference, on OpenAPI files (default [false]) --exclude-categories strings exclude categories by providing its name cannot be provided with query inclusion flags can be provided multiple times or as a comma separated string @@ -137,6 +138,7 @@ Flags: --exclude-type strings case insensitive list of platform types not to scan (Ansible, AzureResourceManager, Buildah, CICD, CloudFormation, Crossplane, DockerCompose, Dockerfile, GRPC, GoogleDeploymentManager, Knative, Kubernetes, OpenAPI, Pulumi, ServerLessFW, Terraform) cannot be provided with type inclusion flags + ``` ```txt diff --git a/e2e/fixtures/E2E_CLI_070_RESULT.json b/e2e/fixtures/E2E_CLI_070_RESULT.json index 36d67b2b5a7..82551d1b29e 100644 --- a/e2e/fixtures/E2E_CLI_070_RESULT.json +++ b/e2e/fixtures/E2E_CLI_070_RESULT.json @@ -22,8 +22,8 @@ "start": "2023-10-27T16:46:52.5513995+01:00", "end": "2023-10-27T16:46:52.8805179+01:00", "paths": [ - "/path/test/fixtures/experimental_test/sample", - "/path/test/fixtures/experimental_test/queries" + "/path/test/fixtures/experimental_test/sample", + "/path/test/fixtures/experimental_test/queries" ], "queries": [ { diff --git a/e2e/fixtures/E2E_CLI_071_RESULT.json b/e2e/fixtures/E2E_CLI_071_RESULT.json new file mode 100644 index 00000000000..2b1573a6508 --- /dev/null +++ b/e2e/fixtures/E2E_CLI_071_RESULT.json @@ -0,0 +1,53 @@ +{ + "kics_version": "development", + "files_scanned": 1, + "lines_scanned": 19, + "files_parsed": 1, + "lines_parsed": 19, + "lines_ignored": 0, + "files_failed_to_scan": 0, + "queries_total": 17, + "queries_failed_to_execute": 0, + "queries_failed_to_compute_similarity_id": 0, + "scan_id": "console", + "severity_counters": { + "HIGH": 0, + "INFO": 1, + "LOW": 0, + "MEDIUM": 0, + "TRACE": 0 + }, + "total_counter": 1, + "total_bom_resources": 0, + "start": "2023-11-08T16:02:34.2300252Z", + "end": "2023-11-08T16:02:36.2803423Z", + "paths": [ + "/path/test/fixtures/resolve_references" + ], + "queries": [ + { + "query_name": "Components Schema Definition Is Unused", + "query_id": "962fa01e-b791-4dcc-b04a-4a3e7389be5e", + "query_url": "https://swagger.io/specification/#components-object", + "severity": "INFO", + "platform": "OpenAPI", + "category": "Best Practices", + "experimental": false, + "description": "Components schemas definitions should be referenced or removed from Open API definition", + "description_id": "5cdc0f3b", + "files": [ + { + "file_name": "path\\test\\fixtures\\resolve_references\\swagger.yaml", + "similarity_id": "ff39e561509c13315ce34a0be602a974d63231b70cb5cdf778109e062302f8eb", + "line": 17, + "issue_type": "IncorrectValue", + "search_key": "components.schemas.{{MyResponse}}", + "search_line": -1, + "search_value": "", + "expected_value": "Schema should be used as reference somewhere", + "actual_value": "Schema is not used as reference" + } + ] + } + ] +} diff --git a/e2e/fixtures/E2E_CLI_072_RESULT.json b/e2e/fixtures/E2E_CLI_072_RESULT.json new file mode 100644 index 00000000000..f9982d51012 --- /dev/null +++ b/e2e/fixtures/E2E_CLI_072_RESULT.json @@ -0,0 +1,99 @@ +{ + "kics_version": "development", + "files_scanned": 1, + "lines_scanned": 50, + "files_parsed": 1, + "lines_parsed": 55, + "lines_ignored": 0, + "files_failed_to_scan": 0, + "queries_total": 17, + "queries_failed_to_execute": 0, + "queries_failed_to_compute_similarity_id": 0, + "scan_id": "console", + "severity_counters": { + "HIGH": 0, + "INFO": 1, + "LOW": 0, + "MEDIUM": 3, + "TRACE": 0 + }, + "total_counter": 4, + "total_bom_resources": 0, + "start": "2023-11-08T16:01:57.5219527Z", + "end": "2023-11-08T16:01:59.1971883Z", + "paths": [ + "/path/test/fixtures/resolve_references" + ], + "queries": [ + { + "query_name": "Response Code Missing (v3)", + "query_id": "6c35d2c6-09f2-4e5c-a094-e0e91327071d", + "query_url": "https://swagger.io/specification/#operation-object", + "severity": "MEDIUM", + "platform": "OpenAPI", + "category": "Networking and Firewall", + "experimental": false, + "description": "500, 429 and 400 responses should be defined for all operations, except head operation. 415 response should be defined for the post, put, and patch operations. 404 response should be defined for the get, put, head, delete operations. 200 response should be defined for options operation. 401 and 403 response should be defined for all operations when the security field is defined.", + "description_id": "dbf15009", + "files": [ + { + "file_name": "path\\test\\fixtures\\resolve_references\\swagger.yaml", + "similarity_id": "0e9d0a90c2069babcc7d07b581105ebda5dba82dc83c0ef588103f8805662c8c", + "line": 14, + "issue_type": "MissingAttribute", + "search_key": "paths.{{/users/{userId}}}.$ref=./paths/users/user.yaml", + "search_line": 0, + "search_value": "400 response", + "expected_value": "400 response should be set", + "actual_value": "400 response is undefined" + }, + { + "file_name": "path\\test\\fixtures\\resolve_references\\swagger.yaml", + "similarity_id": "21b4b94761ab17c403b6455c8b88f295729ed9e98fd3101b2bb5cf5373fba1e6", + "line": 14, + "issue_type": "MissingAttribute", + "search_key": "paths.{{/users/{userId}}}.$ref=./paths/users/user.yaml", + "search_line": 0, + "search_value": "429 response", + "expected_value": "429 response should be set", + "actual_value": "429 response is undefined" + }, + { + "file_name": "path\\test\\fixtures\\resolve_references\\swagger.yaml", + "similarity_id": "22e82edb39085e8787d853eed386b45f1774e7bbf7e1f08ed9662c33cd69d883", + "line": 14, + "issue_type": "MissingAttribute", + "search_key": "paths.{{/users/{userId}}}.$ref=./paths/users/user.yaml", + "search_line": 0, + "search_value": "500 response", + "expected_value": "500 response should be set", + "actual_value": "500 response is undefined" + } + ] + }, + { + "query_name": "Components Schema Definition Is Unused", + "query_id": "962fa01e-b791-4dcc-b04a-4a3e7389be5e", + "query_url": "https://swagger.io/specification/#components-object", + "severity": "INFO", + "platform": "OpenAPI", + "category": "Best Practices", + "experimental": false, + "description": "Components schemas definitions should be referenced or removed from Open API definition", + "description_id": "5cdc0f3b", + "files": [ + { + "file_name": "path\\test\\fixtures\\resolve_references\\swagger.yaml", + "similarity_id": "ff39e561509c13315ce34a0be602a974d63231b70cb5cdf778109e062302f8eb", + "line": 17, + "issue_type": "IncorrectValue", + "search_key": "components.schemas.{{MyResponse}}", + "search_line": 0, + "search_value": "", + "expected_value": "Schema should be used as reference somewhere", + "actual_value": "Schema is not used as reference" + } + ] + } + ] +} diff --git a/e2e/fixtures/E2E_CLI_073_RESULT.json b/e2e/fixtures/E2E_CLI_073_RESULT.json new file mode 100644 index 00000000000..7b173664e38 --- /dev/null +++ b/e2e/fixtures/E2E_CLI_073_RESULT.json @@ -0,0 +1,28 @@ +{ + "kics_version": "development", + "files_scanned": 1, + "lines_scanned": 565, + "files_parsed": 1, + "lines_parsed": 565, + "lines_ignored": 0, + "files_failed_to_scan": 0, + "queries_total": 1, + "queries_failed_to_execute": 0, + "queries_failed_to_compute_similarity_id": 0, + "scan_id": "console", + "severity_counters": { + "HIGH": 0, + "INFO": 0, + "LOW": 0, + "MEDIUM": 0, + "TRACE": 0 + }, + "total_counter": 0, + "total_bom_resources": 0, + "start": "2023-11-09T14:36:44.3290943Z", + "end": "2023-11-09T14:36:45.6631156Z", + "paths": [ + "/path/test/fixtures/resolve_references_json" + ], + "queries": [] +} diff --git a/e2e/fixtures/E2E_CLI_074_RESULT.json b/e2e/fixtures/E2E_CLI_074_RESULT.json new file mode 100644 index 00000000000..3fec7af1ec3 --- /dev/null +++ b/e2e/fixtures/E2E_CLI_074_RESULT.json @@ -0,0 +1,845 @@ +{ + "kics_version": "development", + "files_scanned": 1, + "lines_scanned": 633, + "files_parsed": 1, + "lines_parsed": 909, + "lines_ignored": 0, + "files_failed_to_scan": 0, + "queries_total": 1, + "queries_failed_to_execute": 0, + "queries_failed_to_compute_similarity_id": 0, + "scan_id": "console", + "severity_counters": { + "HIGH": 0, + "INFO": 73, + "LOW": 0, + "MEDIUM": 0, + "TRACE": 0 + }, + "total_counter": 73, + "total_bom_resources": 0, + "start": "2023-11-11T21:07:30.1876667Z", + "end": "2023-11-11T21:07:32.1639213Z", + "paths": [ + "/path/test/fixtures/resolve_references_json" + ], + "queries": [ + { + "query_name": "Property Not Unique", + "query_id": "750b40be-4bac-4f59-bdc4-1ca0e6c3450e", + "query_url": "https://swagger.io/specification/v2/#schemaObject", + "severity": "INFO", + "platform": "OpenAPI", + "category": "Structure and Semantics", + "experimental": false, + "description": "Every defined property must be unique throughout the whole API", + "description_id": "eb2e14e6", + "files": [ + { + "file_name": "path\\test\\fixtures\\resolve_references_json\\scan-2files.json", + "similarity_id": "d922a00ae36332672049542c53e877ca3cea762566073824805efe7902afa1b7", + "line": 307, + "issue_type": "IncorrectValue", + "search_key": "paths.{{/user/create/password}}.post.responses.200.schema.$ref=./definitions.json#/User", + "search_line": 0, + "search_value": "", + "expected_value": "'info' property is unique throughout the whole API", + "actual_value": "'info' property is not unique throughout the whole API" + }, + { + "file_name": "path\\test\\fixtures\\resolve_references_json\\scan-2files.json", + "similarity_id": "ae9a2c4194ef97f7a5e8dd6699ca69a23b5776268a44405e26ae261586c49aaa", + "line": 460, + "issue_type": "IncorrectValue", + "search_key": "paths.{{/user/insert}}.post.parameters.schema.$ref=./definitions.json#/User", + "search_line": 0, + "search_value": "", + "expected_value": "'firstName' property is unique throughout the whole API", + "actual_value": "'firstName' property is not unique throughout the whole API" + }, + { + "file_name": "path\\test\\fixtures\\resolve_references_json\\scan-2files.json", + "similarity_id": "e26a1373b6b00c852b5734d922665c09b9f200a32d796f0a08f5f0fd1b63935c", + "line": 468, + "issue_type": "IncorrectValue", + "search_key": "paths.{{/user/insert}}.post.responses.200.schema.$ref=./definitions.json#/User", + "search_line": 0, + "search_value": "", + "expected_value": "'id' property is unique throughout the whole API", + "actual_value": "'id' property is not unique throughout the whole API" + }, + { + "file_name": "path\\test\\fixtures\\resolve_references_json\\scan-2files.json", + "similarity_id": "f864a2440ec855e97e618df141d6d64560ae2b8c85f6fedcdbfcfa268e2f5eaf", + "line": 299, + "issue_type": "IncorrectValue", + "search_key": "paths.{{/user/create/password}}.post.parameters.schema.$ref=./definitions.json#/User", + "search_line": 0, + "search_value": "", + "expected_value": "'id' property is unique throughout the whole API", + "actual_value": "'id' property is not unique throughout the whole API" + }, + { + "file_name": "path\\test\\fixtures\\resolve_references_json\\scan-2files.json", + "similarity_id": "3f50576472289df6d0dc0a3ada162734486f5ca9b65c39fdd2d86b7e97ef3bfc", + "line": 249, + "issue_type": "IncorrectValue", + "search_key": "paths.{{/user/create/mapCart}}.post.parameters.schema.$ref=./definitions.json#/User", + "search_line": 0, + "search_value": "", + "expected_value": "'info' property is unique throughout the whole API", + "actual_value": "'info' property is not unique throughout the whole API" + }, + { + "file_name": "path\\test\\fixtures\\resolve_references_json\\scan-2files.json", + "similarity_id": "33d4880a27d20f1d94813f7839ffba6bd421d515b2a4a1a1d2f9439577a44cbd", + "line": 506, + "issue_type": "IncorrectValue", + "search_key": "paths.{{/user/update}}.post.parameters.schema.$ref=./definitions.json#/User", + "search_line": 0, + "search_value": "", + "expected_value": "'info' property is unique throughout the whole API", + "actual_value": "'info' property is not unique throughout the whole API" + }, + { + "file_name": "path\\test\\fixtures\\resolve_references_json\\scan-2files.json", + "similarity_id": "9d19bd172ff710d659a31e4196991fc11f4fb9ba856ea0c82bf86ab4ac1b0b53", + "line": 307, + "issue_type": "IncorrectValue", + "search_key": "paths.{{/user/create/password}}.post.responses.200.schema.$ref=./definitions.json#/User", + "search_line": 0, + "search_value": "", + "expected_value": "'email' property is unique throughout the whole API", + "actual_value": "'email' property is not unique throughout the whole API" + }, + { + "file_name": "path\\test\\fixtures\\resolve_references_json\\scan-2files.json", + "similarity_id": "076023938ac61210137d19ce19c441e9418be944be4c04a7fae641f2f3b086a8", + "line": 387, + "issue_type": "IncorrectValue", + "search_key": "paths.{{/user/get/byId/{id}}}.get.responses.200.schema.$ref=./definitions.json#/User", + "search_line": 0, + "search_value": "", + "expected_value": "'id' property is unique throughout the whole API", + "actual_value": "'id' property is not unique throughout the whole API" + }, + { + "file_name": "path\\test\\fixtures\\resolve_references_json\\scan-2files.json", + "similarity_id": "a18abf68c25c5626c5f3bb02f7804120cfec2b0af7d25234b35424965e7b9d57", + "line": 563, + "issue_type": "IncorrectValue", + "search_key": "definitions.$ref=./definitions.json", + "search_line": 0, + "search_value": "", + "expected_value": "'gmtOffset' property is unique throughout the whole API", + "actual_value": "'gmtOffset' property is not unique throughout the whole API" + }, + { + "file_name": "path\\test\\fixtures\\resolve_references_json\\scan-2files.json", + "similarity_id": "eb7a06cc175f972b5d7edcb3616905a9a1cc3b8b20223316addd6f1114d5c33f", + "line": 307, + "issue_type": "IncorrectValue", + "search_key": "paths.{{/user/create/password}}.post.responses.200.schema.$ref=./definitions.json#/User", + "search_line": 0, + "search_value": "", + "expected_value": "'id' property is unique throughout the whole API", + "actual_value": "'id' property is not unique throughout the whole API" + }, + { + "file_name": "path\\test\\fixtures\\resolve_references_json\\scan-2files.json", + "similarity_id": "21c3a8b9dbb3622aa45eaa495d925bf9d298cc5d7c6618ff44ff6b15fff93183", + "line": 563, + "issue_type": "IncorrectValue", + "search_key": "definitions.$ref=./definitions.json", + "search_line": 0, + "search_value": "", + "expected_value": "'a2' property is unique throughout the whole API", + "actual_value": "'a2' property is not unique throughout the whole API" + }, + { + "file_name": "path\\test\\fixtures\\resolve_references_json\\scan-2files.json", + "similarity_id": "db633da9cf870fa37830b3c65fb285c9a4b8cff92a36343e40a7cd9796c55e2f", + "line": 545, + "issue_type": "IncorrectValue", + "search_key": "paths.{{/users/findAll}}.get.responses.200.schema.items.$ref=./definitions.json#/User", + "search_line": 0, + "search_value": "", + "expected_value": "'email' property is unique throughout the whole API", + "actual_value": "'email' property is not unique throughout the whole API" + }, + { + "file_name": "path\\test\\fixtures\\resolve_references_json\\scan-2files.json", + "similarity_id": "066626dc90876e4b8e0acb085e4883f8742ead6e726f66b33ed364ed3aeb2d92", + "line": 563, + "issue_type": "IncorrectValue", + "search_key": "definitions.$ref=./definitions.json", + "search_line": 0, + "search_value": "", + "expected_value": "'password' property is unique throughout the whole API", + "actual_value": "'password' property is not unique throughout the whole API" + }, + { + "file_name": "path\\test\\fixtures\\resolve_references_json\\scan-2files.json", + "similarity_id": "06131bef351103acfb7ee9fbb205cd3072eab03438dc64574bb4541cd7ba48d9", + "line": 348, + "issue_type": "IncorrectValue", + "search_key": "paths.{{/user/get/byEmail/{email}}}.get.responses.200.schema.$ref=./definitions.json#/User", + "search_line": 0, + "search_value": "", + "expected_value": "'password' property is unique throughout the whole API", + "actual_value": "'password' property is not unique throughout the whole API" + }, + { + "file_name": "path\\test\\fixtures\\resolve_references_json\\scan-2files.json", + "similarity_id": "7a7de79e78550abfe8a44b27648d61bf7d2e22e563792d106292d8110428b60f", + "line": 387, + "issue_type": "IncorrectValue", + "search_key": "paths.{{/user/get/byId/{id}}}.get.responses.200.schema.$ref=./definitions.json#/User", + "search_line": 0, + "search_value": "", + "expected_value": "'email' property is unique throughout the whole API", + "actual_value": "'email' property is not unique throughout the whole API" + }, + { + "file_name": "path\\test\\fixtures\\resolve_references_json\\scan-2files.json", + "similarity_id": "0ed9019bf3b823e4b9cec5623ecb02c577f495d7b3be84dceb009b9f6e7dc8ff", + "line": 299, + "issue_type": "IncorrectValue", + "search_key": "paths.{{/user/create/password}}.post.parameters.schema.$ref=./definitions.json#/User", + "search_line": 0, + "search_value": "", + "expected_value": "'firstName' property is unique throughout the whole API", + "actual_value": "'firstName' property is not unique throughout the whole API" + }, + { + "file_name": "path\\test\\fixtures\\resolve_references_json\\scan-2files.json", + "similarity_id": "a36106e9755dd59573e65e745036739846241c265d2265ce433882d80991d216", + "line": 545, + "issue_type": "IncorrectValue", + "search_key": "paths.{{/users/findAll}}.get.responses.200.schema.items.$ref=./definitions.json#/User", + "search_line": 0, + "search_value": "", + "expected_value": "'firstName' property is unique throughout the whole API", + "actual_value": "'firstName' property is not unique throughout the whole API" + }, + { + "file_name": "path\\test\\fixtures\\resolve_references_json\\scan-2files.json", + "similarity_id": "3e833473f334445d14dea0b81ad200bb6f9715983d5c330cea890862e3e621a1", + "line": 348, + "issue_type": "IncorrectValue", + "search_key": "paths.{{/user/get/byEmail/{email}}}.get.responses.200.schema.$ref=./definitions.json#/User", + "search_line": 0, + "search_value": "", + "expected_value": "'info' property is unique throughout the whole API", + "actual_value": "'info' property is not unique throughout the whole API" + }, + { + "file_name": "path\\test\\fixtures\\resolve_references_json\\scan-2files.json", + "similarity_id": "6a86f8ece0f67a20a6f2d953aeec7133a4d8b2db05a91c88b77e6daa559da255", + "line": 460, + "issue_type": "IncorrectValue", + "search_key": "paths.{{/user/insert}}.post.parameters.schema.$ref=./definitions.json#/User", + "search_line": 0, + "search_value": "", + "expected_value": "'info' property is unique throughout the whole API", + "actual_value": "'info' property is not unique throughout the whole API" + }, + { + "file_name": "path\\test\\fixtures\\resolve_references_json\\scan-2files.json", + "similarity_id": "742df9bdb7b0bbdfd27893a2328da8c0cf3313562121f05b4933e49df8d71d71", + "line": 299, + "issue_type": "IncorrectValue", + "search_key": "paths.{{/user/create/password}}.post.parameters.schema.$ref=./definitions.json#/User", + "search_line": 0, + "search_value": "", + "expected_value": "'password' property is unique throughout the whole API", + "actual_value": "'password' property is not unique throughout the whole API" + }, + { + "file_name": "path\\test\\fixtures\\resolve_references_json\\scan-2files.json", + "similarity_id": "70d44bfb825c6276aefef00842ac929b64193c9030066ecea83f8bfc9ed6f964", + "line": 545, + "issue_type": "IncorrectValue", + "search_key": "paths.{{/users/findAll}}.get.responses.200.schema.items.$ref=./definitions.json#/User", + "search_line": 0, + "search_value": "", + "expected_value": "'password' property is unique throughout the whole API", + "actual_value": "'password' property is not unique throughout the whole API" + }, + { + "file_name": "path\\test\\fixtures\\resolve_references_json\\scan-2files.json", + "similarity_id": "c9fc36b4134eceed43134427be36539a2733c36f0be7b001e2e28acf70138451", + "line": 49, + "issue_type": "IncorrectValue", + "search_key": "paths.{{/country/get/byId/{id}}}.get.responses.200.schema.$ref=./definitions.json#/Country", + "search_line": 0, + "search_value": "", + "expected_value": "'a2' property is unique throughout the whole API", + "actual_value": "'a2' property is not unique throughout the whole API" + }, + { + "file_name": "path\\test\\fixtures\\resolve_references_json\\scan-2files.json", + "similarity_id": "6f2361667cb5eef4416ee89133e7f000bc857bab23be7f63dc29fb219932ffae", + "line": 468, + "issue_type": "IncorrectValue", + "search_key": "paths.{{/user/insert}}.post.responses.200.schema.$ref=./definitions.json#/User", + "search_line": 0, + "search_value": "", + "expected_value": "'firstName' property is unique throughout the whole API", + "actual_value": "'firstName' property is not unique throughout the whole API" + }, + { + "file_name": "path\\test\\fixtures\\resolve_references_json\\scan-2files.json", + "similarity_id": "5eda8746744c20aeccec4b5e818cbed3788cd3703f4c2aa7327a88e1045dd256", + "line": 387, + "issue_type": "IncorrectValue", + "search_key": "paths.{{/user/get/byId/{id}}}.get.responses.200.schema.$ref=./definitions.json#/User", + "search_line": 0, + "search_value": "", + "expected_value": "'firstName' property is unique throughout the whole API", + "actual_value": "'firstName' property is not unique throughout the whole API" + }, + { + "file_name": "path\\test\\fixtures\\resolve_references_json\\scan-2files.json", + "similarity_id": "3ff7cf91a10e3c0dae8a89638de17797354aa05c16c0dc58b45e916bd393f1b3", + "line": 387, + "issue_type": "IncorrectValue", + "search_key": "paths.{{/user/get/byId/{id}}}.get.responses.200.schema.$ref=./definitions.json#/User", + "search_line": 0, + "search_value": "", + "expected_value": "'password' property is unique throughout the whole API", + "actual_value": "'password' property is not unique throughout the whole API" + }, + { + "file_name": "path\\test\\fixtures\\resolve_references_json\\scan-2files.json", + "similarity_id": "910467f7887c76affc7cea19c0fe396e3d3531ea8a43fd3144a91010f73f5f8e", + "line": 249, + "issue_type": "IncorrectValue", + "search_key": "paths.{{/user/create/mapCart}}.post.parameters.schema.$ref=./definitions.json#/User", + "search_line": 0, + "search_value": "", + "expected_value": "'id' property is unique throughout the whole API", + "actual_value": "'id' property is not unique throughout the whole API" + }, + { + "file_name": "path\\test\\fixtures\\resolve_references_json\\scan-2files.json", + "similarity_id": "68309d0960a7dc0655457b3ebf9fc6ba6ffe06957c7ee22fa569cc138d91868b", + "line": 49, + "issue_type": "IncorrectValue", + "search_key": "paths.{{/country/get/byId/{id}}}.get.responses.200.schema.$ref=./definitions.json#/Country", + "search_line": 0, + "search_value": "", + "expected_value": "'definition' property is unique throughout the whole API", + "actual_value": "'definition' property is not unique throughout the whole API" + }, + { + "file_name": "path\\test\\fixtures\\resolve_references_json\\scan-2files.json", + "similarity_id": "960914db94494373fc175325d916372db0940040c1d8a73c153dae8b398a6b3e", + "line": 194, + "issue_type": "IncorrectValue", + "search_key": "paths.{{/user/create/cart}}.post.parameters.schema.$ref=./definitions.json#/User", + "search_line": 0, + "search_value": "", + "expected_value": "'email' property is unique throughout the whole API", + "actual_value": "'email' property is not unique throughout the whole API" + }, + { + "file_name": "path\\test\\fixtures\\resolve_references_json\\scan-2files.json", + "similarity_id": "d8e93cca1ed74130a03faee787fd52a35a64c4d5093a264c969d47f05d8a7e53", + "line": 545, + "issue_type": "IncorrectValue", + "search_key": "paths.{{/users/findAll}}.get.responses.200.schema.items.$ref=./definitions.json#/User", + "search_line": 0, + "search_value": "", + "expected_value": "'info' property is unique throughout the whole API", + "actual_value": "'info' property is not unique throughout the whole API" + }, + { + "file_name": "path\\test\\fixtures\\resolve_references_json\\scan-2files.json", + "similarity_id": "115612d7d5a2e88c8ebdfda5c4e3c2e35671d903bf1c00a3a68465a4bc3c3a8a", + "line": 506, + "issue_type": "IncorrectValue", + "search_key": "paths.{{/user/update}}.post.parameters.schema.$ref=./definitions.json#/User", + "search_line": 0, + "search_value": "", + "expected_value": "'password' property is unique throughout the whole API", + "actual_value": "'password' property is not unique throughout the whole API" + }, + { + "file_name": "path\\test\\fixtures\\resolve_references_json\\scan-2files.json", + "similarity_id": "4e1e62ee80dc6711e967c9be6a1d14df22033bfe3b3e12b8385508d9ad942da6", + "line": 49, + "issue_type": "IncorrectValue", + "search_key": "paths.{{/country/get/byId/{id}}}.get.responses.200.schema.$ref=./definitions.json#/Country", + "search_line": 0, + "search_value": "", + "expected_value": "'countryName' property is unique throughout the whole API", + "actual_value": "'countryName' property is not unique throughout the whole API" + }, + { + "file_name": "path\\test\\fixtures\\resolve_references_json\\scan-2files.json", + "similarity_id": "f27d2a4dda9db71d94614e720681050e5cd74c17cdd01f7ff7061ab58707981c", + "line": 49, + "issue_type": "IncorrectValue", + "search_key": "paths.{{/country/get/byId/{id}}}.get.responses.200.schema.$ref=./definitions.json#/Country", + "search_line": 0, + "search_value": "", + "expected_value": "'governmentForm' property is unique throughout the whole API", + "actual_value": "'governmentForm' property is not unique throughout the whole API" + }, + { + "file_name": "path\\test\\fixtures\\resolve_references_json\\scan-2files.json", + "similarity_id": "5a722738fa25f4a6eb01b730edf5b4552255e4b76de45dc9ff4f7d78aa94210f", + "line": 299, + "issue_type": "IncorrectValue", + "search_key": "paths.{{/user/create/password}}.post.parameters.schema.$ref=./definitions.json#/User", + "search_line": 0, + "search_value": "", + "expected_value": "'email' property is unique throughout the whole API", + "actual_value": "'email' property is not unique throughout the whole API" + }, + { + "file_name": "path\\test\\fixtures\\resolve_references_json\\scan-2files.json", + "similarity_id": "ca6add4f28bb864af1341d26682aa2d0fa6d759b3efc99479127ca1c9758cc12", + "line": 460, + "issue_type": "IncorrectValue", + "search_key": "paths.{{/user/insert}}.post.parameters.schema.$ref=./definitions.json#/User", + "search_line": 0, + "search_value": "", + "expected_value": "'email' property is unique throughout the whole API", + "actual_value": "'email' property is not unique throughout the whole API" + }, + { + "file_name": "path\\test\\fixtures\\resolve_references_json\\scan-2files.json", + "similarity_id": "36abb555656ac0fa2cb2b975b1a4a90076d424c1083144673dfce7e1daa44512", + "line": 194, + "issue_type": "IncorrectValue", + "search_key": "paths.{{/user/create/cart}}.post.parameters.schema.$ref=./definitions.json#/User", + "search_line": 0, + "search_value": "", + "expected_value": "'firstName' property is unique throughout the whole API", + "actual_value": "'firstName' property is not unique throughout the whole API" + }, + { + "file_name": "path\\test\\fixtures\\resolve_references_json\\scan-2files.json", + "similarity_id": "44ff15d550e3eb9cb2a7b5d4de12a5df15ad61577ffc297ea27e832a453e7ab6", + "line": 49, + "issue_type": "IncorrectValue", + "search_key": "paths.{{/country/get/byId/{id}}}.get.responses.200.schema.$ref=./definitions.json#/Country", + "search_line": 0, + "search_value": "", + "expected_value": "'id' property is unique throughout the whole API", + "actual_value": "'id' property is not unique throughout the whole API" + }, + { + "file_name": "path\\test\\fixtures\\resolve_references_json\\scan-2files.json", + "similarity_id": "bb64c1c7f4b43fdb4a2d84048203a0183f1b88c8fcbfc505916cc8a974fef901", + "line": 545, + "issue_type": "IncorrectValue", + "search_key": "paths.{{/users/findAll}}.get.responses.200.schema.items.$ref=./definitions.json#/User", + "search_line": 0, + "search_value": "", + "expected_value": "'id' property is unique throughout the whole API", + "actual_value": "'id' property is not unique throughout the whole API" + }, + { + "file_name": "path\\test\\fixtures\\resolve_references_json\\scan-2files.json", + "similarity_id": "ae22dd7f5074e293bd07c146e0ef6e070c34c323307841641d9b19250d976896", + "line": 563, + "issue_type": "IncorrectValue", + "search_key": "definitions.$ref=./definitions.json", + "search_line": 0, + "search_value": "", + "expected_value": "'displayName' property is unique throughout the whole API", + "actual_value": "'displayName' property is not unique throughout the whole API" + }, + { + "file_name": "path\\test\\fixtures\\resolve_references_json\\scan-2files.json", + "similarity_id": "506a8a035b6eafb480cdb17248ec20312a6977b73043a60da2ac4f7ae77777e5", + "line": 563, + "issue_type": "IncorrectValue", + "search_key": "definitions.$ref=./definitions.json", + "search_line": 0, + "search_value": "", + "expected_value": "'email' property is unique throughout the whole API", + "actual_value": "'email' property is not unique throughout the whole API" + }, + { + "file_name": "path\\test\\fixtures\\resolve_references_json\\scan-2files.json", + "similarity_id": "d0afe0e12eb9e83f4ad891f0349a94bd059d33765209ceaa827ea5153ffcf8ed", + "line": 563, + "issue_type": "IncorrectValue", + "search_key": "definitions.$ref=./definitions.json", + "search_line": 0, + "search_value": "", + "expected_value": "'countryName' property is unique throughout the whole API", + "actual_value": "'countryName' property is not unique throughout the whole API" + }, + { + "file_name": "path\\test\\fixtures\\resolve_references_json\\scan-2files.json", + "similarity_id": "1f1c4e0ee7b45e50c78cd7a2947a5c380620e2ff6099fd207985c81ebab2c193", + "line": 194, + "issue_type": "IncorrectValue", + "search_key": "paths.{{/user/create/cart}}.post.parameters.schema.$ref=./definitions.json#/User", + "search_line": 0, + "search_value": "", + "expected_value": "'password' property is unique throughout the whole API", + "actual_value": "'password' property is not unique throughout the whole API" + }, + { + "file_name": "path\\test\\fixtures\\resolve_references_json\\scan-2files.json", + "similarity_id": "78e7639ae2bdaa28b67a3b147d83befcbdc6fa12eff4bcbc04dd3d17fee8269e", + "line": 563, + "issue_type": "IncorrectValue", + "search_key": "definitions.$ref=./definitions.json", + "search_line": 0, + "search_value": "", + "expected_value": "'firstName' property is unique throughout the whole API", + "actual_value": "'firstName' property is not unique throughout the whole API" + }, + { + "file_name": "path\\test\\fixtures\\resolve_references_json\\scan-2files.json", + "similarity_id": "6a933de1599716878e2e3ef21cdadff44c5481452d18dc63d835546693df7ebe", + "line": 49, + "issue_type": "IncorrectValue", + "search_key": "paths.{{/country/get/byId/{id}}}.get.responses.200.schema.$ref=./definitions.json#/Country", + "search_line": 0, + "search_value": "", + "expected_value": "'gmtOffset' property is unique throughout the whole API", + "actual_value": "'gmtOffset' property is not unique throughout the whole API" + }, + { + "file_name": "path\\test\\fixtures\\resolve_references_json\\scan-2files.json", + "similarity_id": "1ebf2eee6e73bf46d0b14e39a6a8b32b3c987ab78bfdf877203be7730ca8213a", + "line": 563, + "issue_type": "IncorrectValue", + "search_key": "definitions.$ref=./definitions.json", + "search_line": 0, + "search_value": "", + "expected_value": "'id' property is unique throughout the whole API", + "actual_value": "'id' property is not unique throughout the whole API" + }, + { + "file_name": "path\\test\\fixtures\\resolve_references_json\\scan-2files.json", + "similarity_id": "5a33cd81809bbfbc0dd79fad23450999e4f9153e798ee7d1703cfb8230fe974a", + "line": 468, + "issue_type": "IncorrectValue", + "search_key": "paths.{{/user/insert}}.post.responses.200.schema.$ref=./definitions.json#/User", + "search_line": 0, + "search_value": "", + "expected_value": "'info' property is unique throughout the whole API", + "actual_value": "'info' property is not unique throughout the whole API" + }, + { + "file_name": "path\\test\\fixtures\\resolve_references_json\\scan-2files.json", + "similarity_id": "6e356e76aa611d446e8b47bcb98c2e89a60f4e11ec0215fa4c3cb68e6b8ef39b", + "line": 49, + "issue_type": "IncorrectValue", + "search_key": "paths.{{/country/get/byId/{id}}}.get.responses.200.schema.$ref=./definitions.json#/Country", + "search_line": 0, + "search_value": "", + "expected_value": "'displayName' property is unique throughout the whole API", + "actual_value": "'displayName' property is not unique throughout the whole API" + }, + { + "file_name": "path\\test\\fixtures\\resolve_references_json\\scan-2files.json", + "similarity_id": "931de466521d641a25205229ac2c975fb74d54bd9a6ff5a80b8a61b742bda09b", + "line": 249, + "issue_type": "IncorrectValue", + "search_key": "paths.{{/user/create/mapCart}}.post.parameters.schema.$ref=./definitions.json#/User", + "search_line": 0, + "search_value": "", + "expected_value": "'email' property is unique throughout the whole API", + "actual_value": "'email' property is not unique throughout the whole API" + }, + { + "file_name": "path\\test\\fixtures\\resolve_references_json\\scan-2files.json", + "similarity_id": "090388d5ad23a89ba5a21dd99ca769b91921ba85ac62f9fea9eef9b2a35ca3c2", + "line": 506, + "issue_type": "IncorrectValue", + "search_key": "paths.{{/user/update}}.post.parameters.schema.$ref=./definitions.json#/User", + "search_line": 0, + "search_value": "", + "expected_value": "'firstName' property is unique throughout the whole API", + "actual_value": "'firstName' property is not unique throughout the whole API" + }, + { + "file_name": "path\\test\\fixtures\\resolve_references_json\\scan-2files.json", + "similarity_id": "d7dc8a4773bc3422171faf9375a10992195d2995804cc67a1b397283e5be9742", + "line": 194, + "issue_type": "IncorrectValue", + "search_key": "paths.{{/user/create/cart}}.post.parameters.schema.$ref=./definitions.json#/User", + "search_line": 0, + "search_value": "", + "expected_value": "'id' property is unique throughout the whole API", + "actual_value": "'id' property is not unique throughout the whole API" + }, + { + "file_name": "path\\test\\fixtures\\resolve_references_json\\scan-2files.json", + "similarity_id": "00f42dde173a16ce24cee4f0c29f4da7af613a0ea5dbbf875323f1cb39ec214f", + "line": 506, + "issue_type": "IncorrectValue", + "search_key": "paths.{{/user/update}}.post.parameters.schema.$ref=./definitions.json#/User", + "search_line": 0, + "search_value": "", + "expected_value": "'id' property is unique throughout the whole API", + "actual_value": "'id' property is not unique throughout the whole API" + }, + { + "file_name": "path\\test\\fixtures\\resolve_references_json\\scan-2files.json", + "similarity_id": "2b831616eccfc184da4ed859fd2ee92e6c3e53753608eb855c65021d0ebfbdc9", + "line": 563, + "issue_type": "IncorrectValue", + "search_key": "definitions.$ref=./definitions.json", + "search_line": 0, + "search_value": "", + "expected_value": "'phoneCode' property is unique throughout the whole API", + "actual_value": "'phoneCode' property is not unique throughout the whole API" + }, + { + "file_name": "path\\test\\fixtures\\resolve_references_json\\scan-2files.json", + "similarity_id": "850677a8f5bf1c669df3dab2b7c7a825af4fb99bf2012197de0d1729455aac8f", + "line": 563, + "issue_type": "IncorrectValue", + "search_key": "definitions.$ref=./definitions.json", + "search_line": 0, + "search_value": "", + "expected_value": "'a3' property is unique throughout the whole API", + "actual_value": "'a3' property is not unique throughout the whole API" + }, + { + "file_name": "path\\test\\fixtures\\resolve_references_json\\scan-2files.json", + "similarity_id": "5caaee296537e635d3def3adaa4a110f1251f45f9c87682372e6dd8f0f7e64df", + "line": 563, + "issue_type": "IncorrectValue", + "search_key": "definitions.$ref=./definitions.json", + "search_line": 0, + "search_value": "", + "expected_value": "'definition' property is unique throughout the whole API", + "actual_value": "'definition' property is not unique throughout the whole API" + }, + { + "file_name": "path\\test\\fixtures\\resolve_references_json\\scan-2files.json", + "similarity_id": "28c44bb9f10e51406f2e7afb8287fdd192eb5f02c2a8acdb1b2df4f8823da145", + "line": 194, + "issue_type": "IncorrectValue", + "search_key": "paths.{{/user/create/cart}}.post.parameters.schema.$ref=./definitions.json#/User", + "search_line": 0, + "search_value": "", + "expected_value": "'info' property is unique throughout the whole API", + "actual_value": "'info' property is not unique throughout the whole API" + }, + { + "file_name": "path\\test\\fixtures\\resolve_references_json\\scan-2files.json", + "similarity_id": "bf9a072e845e726db76c3c4b3de244e611f2cace131d1ae7d6f206d90b2a1ed6", + "line": 387, + "issue_type": "IncorrectValue", + "search_key": "paths.{{/user/get/byId/{id}}}.get.responses.200.schema.$ref=./definitions.json#/User", + "search_line": 0, + "search_value": "", + "expected_value": "'info' property is unique throughout the whole API", + "actual_value": "'info' property is not unique throughout the whole API" + }, + { + "file_name": "path\\test\\fixtures\\resolve_references_json\\scan-2files.json", + "similarity_id": "485cca9ca27689883477aadaff52f90dc1d5a264a423e1e32a4007a27d7d51bf", + "line": 249, + "issue_type": "IncorrectValue", + "search_key": "paths.{{/user/create/mapCart}}.post.parameters.schema.$ref=./definitions.json#/User", + "search_line": 0, + "search_value": "", + "expected_value": "'password' property is unique throughout the whole API", + "actual_value": "'password' property is not unique throughout the whole API" + }, + { + "file_name": "path\\test\\fixtures\\resolve_references_json\\scan-2files.json", + "similarity_id": "3c85384e7ef46fcbe92f029694d68dd19c8621a1a81d40c30c7ec7aba9a34278", + "line": 460, + "issue_type": "IncorrectValue", + "search_key": "paths.{{/user/insert}}.post.parameters.schema.$ref=./definitions.json#/User", + "search_line": 0, + "search_value": "", + "expected_value": "'password' property is unique throughout the whole API", + "actual_value": "'password' property is not unique throughout the whole API" + }, + { + "file_name": "path\\test\\fixtures\\resolve_references_json\\scan-2files.json", + "similarity_id": "e3077490ffad83609036247b4010681858afe1663ca767c8787ad86c83b63a42", + "line": 506, + "issue_type": "IncorrectValue", + "search_key": "paths.{{/user/update}}.post.parameters.schema.$ref=./definitions.json#/User", + "search_line": 0, + "search_value": "", + "expected_value": "'email' property is unique throughout the whole API", + "actual_value": "'email' property is not unique throughout the whole API" + }, + { + "file_name": "path\\test\\fixtures\\resolve_references_json\\scan-2files.json", + "similarity_id": "a60875f03b9d4ba7e30e984a1721574d40ae990a07f9580810362309499b47a9", + "line": 563, + "issue_type": "IncorrectValue", + "search_key": "definitions.$ref=./definitions.json", + "search_line": 0, + "search_value": "", + "expected_value": "'governmentForm' property is unique throughout the whole API", + "actual_value": "'governmentForm' property is not unique throughout the whole API" + }, + { + "file_name": "path\\test\\fixtures\\resolve_references_json\\scan-2files.json", + "similarity_id": "9e71d759cb0f8f63855f0215e2c95c646c20083100bfa04ebf609e80841f816b", + "line": 460, + "issue_type": "IncorrectValue", + "search_key": "paths.{{/user/insert}}.post.parameters.schema.$ref=./definitions.json#/User", + "search_line": 0, + "search_value": "", + "expected_value": "'id' property is unique throughout the whole API", + "actual_value": "'id' property is not unique throughout the whole API" + }, + { + "file_name": "path\\test\\fixtures\\resolve_references_json\\scan-2files.json", + "similarity_id": "d63880c933e60f14fe31e1706419ad0131a43c47639fbfa68e9e18dc29353d89", + "line": 299, + "issue_type": "IncorrectValue", + "search_key": "paths.{{/user/create/password}}.post.parameters.schema.$ref=./definitions.json#/User", + "search_line": 0, + "search_value": "", + "expected_value": "'info' property is unique throughout the whole API", + "actual_value": "'info' property is not unique throughout the whole API" + }, + { + "file_name": "path\\test\\fixtures\\resolve_references_json\\scan-2files.json", + "similarity_id": "ba0fe4366991ff946be2b3264f2f12971e5b7a913bc67464bc1b7bda6aaff15b", + "line": 307, + "issue_type": "IncorrectValue", + "search_key": "paths.{{/user/create/password}}.post.responses.200.schema.$ref=./definitions.json#/User", + "search_line": 0, + "search_value": "", + "expected_value": "'password' property is unique throughout the whole API", + "actual_value": "'password' property is not unique throughout the whole API" + }, + { + "file_name": "path\\test\\fixtures\\resolve_references_json\\scan-2files.json", + "similarity_id": "63de9d74b7d91a430a8935b2b351a0bdef31166b67ddc5be974a684b2c58f576", + "line": 49, + "issue_type": "IncorrectValue", + "search_key": "paths.{{/country/get/byId/{id}}}.get.responses.200.schema.$ref=./definitions.json#/Country", + "search_line": 0, + "search_value": "", + "expected_value": "'phoneCode' property is unique throughout the whole API", + "actual_value": "'phoneCode' property is not unique throughout the whole API" + }, + { + "file_name": "path\\test\\fixtures\\resolve_references_json\\scan-2files.json", + "similarity_id": "03296fbe7d92c85cc639a282edacb96f285ea877602c60f95a9ea46052d07bbb", + "line": 348, + "issue_type": "IncorrectValue", + "search_key": "paths.{{/user/get/byEmail/{email}}}.get.responses.200.schema.$ref=./definitions.json#/User", + "search_line": 0, + "search_value": "", + "expected_value": "'email' property is unique throughout the whole API", + "actual_value": "'email' property is not unique throughout the whole API" + }, + { + "file_name": "path\\test\\fixtures\\resolve_references_json\\scan-2files.json", + "similarity_id": "12cba189eca9ebafb21b6d2b8c50e8b323bbd1a0032b93598b85573e27387c3e", + "line": 249, + "issue_type": "IncorrectValue", + "search_key": "paths.{{/user/create/mapCart}}.post.parameters.schema.$ref=./definitions.json#/User", + "search_line": 0, + "search_value": "", + "expected_value": "'firstName' property is unique throughout the whole API", + "actual_value": "'firstName' property is not unique throughout the whole API" + }, + { + "file_name": "path\\test\\fixtures\\resolve_references_json\\scan-2files.json", + "similarity_id": "ab36a26fc165f9e6f99745303d833c21eb3f0ebc8fe29a3f7f4c00ebdd7f5f4a", + "line": 307, + "issue_type": "IncorrectValue", + "search_key": "paths.{{/user/create/password}}.post.responses.200.schema.$ref=./definitions.json#/User", + "search_line": 0, + "search_value": "", + "expected_value": "'firstName' property is unique throughout the whole API", + "actual_value": "'firstName' property is not unique throughout the whole API" + }, + { + "file_name": "path\\test\\fixtures\\resolve_references_json\\scan-2files.json", + "similarity_id": "c13e062ec0e0a367b6fd475c182fb6db2e485cd29bd98cba6c8efc8715493280", + "line": 348, + "issue_type": "IncorrectValue", + "search_key": "paths.{{/user/get/byEmail/{email}}}.get.responses.200.schema.$ref=./definitions.json#/User", + "search_line": 0, + "search_value": "", + "expected_value": "'firstName' property is unique throughout the whole API", + "actual_value": "'firstName' property is not unique throughout the whole API" + }, + { + "file_name": "path\\test\\fixtures\\resolve_references_json\\scan-2files.json", + "similarity_id": "cd645894b66fff7e5353951a29877faf402ba3e3d51d56b9f2e936105949512f", + "line": 563, + "issue_type": "IncorrectValue", + "search_key": "definitions.$ref=./definitions.json", + "search_line": 0, + "search_value": "", + "expected_value": "'id' property is unique throughout the whole API", + "actual_value": "'id' property is not unique throughout the whole API" + }, + { + "file_name": "path\\test\\fixtures\\resolve_references_json\\scan-2files.json", + "similarity_id": "9e825d84c9683c86687eac7a319c0ef77d5296a6f64ef1224e9808181e52879c", + "line": 348, + "issue_type": "IncorrectValue", + "search_key": "paths.{{/user/get/byEmail/{email}}}.get.responses.200.schema.$ref=./definitions.json#/User", + "search_line": 0, + "search_value": "", + "expected_value": "'id' property is unique throughout the whole API", + "actual_value": "'id' property is not unique throughout the whole API" + }, + { + "file_name": "path\\test\\fixtures\\resolve_references_json\\scan-2files.json", + "similarity_id": "9c53f0e8c5ed42f8c81e44b07ab37c073ec4af1bf6654c85e6795b17fee8d9f6", + "line": 563, + "issue_type": "IncorrectValue", + "search_key": "definitions.$ref=./definitions.json", + "search_line": 0, + "search_value": "", + "expected_value": "'info' property is unique throughout the whole API", + "actual_value": "'info' property is not unique throughout the whole API" + }, + { + "file_name": "path\\test\\fixtures\\resolve_references_json\\scan-2files.json", + "similarity_id": "1596f75c7fa2b0d4362c6ea777462156610fca823d3025cb2999200b025130c2", + "line": 468, + "issue_type": "IncorrectValue", + "search_key": "paths.{{/user/insert}}.post.responses.200.schema.$ref=./definitions.json#/User", + "search_line": 0, + "search_value": "", + "expected_value": "'password' property is unique throughout the whole API", + "actual_value": "'password' property is not unique throughout the whole API" + }, + { + "file_name": "path\\test\\fixtures\\resolve_references_json\\scan-2files.json", + "similarity_id": "a29891f01be9d769faeb1937e8d5d741a05876fde469b6c50246b7a67e2597f1", + "line": 49, + "issue_type": "IncorrectValue", + "search_key": "paths.{{/country/get/byId/{id}}}.get.responses.200.schema.$ref=./definitions.json#/Country", + "search_line": 0, + "search_value": "", + "expected_value": "'a3' property is unique throughout the whole API", + "actual_value": "'a3' property is not unique throughout the whole API" + }, + { + "file_name": "path\\test\\fixtures\\resolve_references_json\\scan-2files.json", + "similarity_id": "dd12c8363e0fcd3f36136d30a87321885a42d5da08b937e65f9ca4e74b8eb85f", + "line": 468, + "issue_type": "IncorrectValue", + "search_key": "paths.{{/user/insert}}.post.responses.200.schema.$ref=./definitions.json#/User", + "search_line": 0, + "search_value": "", + "expected_value": "'email' property is unique throughout the whole API", + "actual_value": "'email' property is not unique throughout the whole API" + } + ] + } + ] +} diff --git a/e2e/fixtures/assets/scan_help b/e2e/fixtures/assets/scan_help index 4fe4c91fc54..3ef086b5c14 100644 --- a/e2e/fixtures/assets/scan_help +++ b/e2e/fixtures/assets/scan_help @@ -7,6 +7,7 @@ Flags: --config string path to configuration file --disable-full-descriptions disable request for full descriptions and use default vulnerability descriptions --disable-secrets disable secrets scanning + --enable-openapi-refs resolve the file reference, on OpenAPI files --exclude-categories strings exclude categories by providing its name cannot be provided with query inclusion flags can be provided multiple times or as a comma separated string diff --git a/e2e/testcases/e2e-cli-071_no_flag_resolve_references.go b/e2e/testcases/e2e-cli-071_no_flag_resolve_references.go new file mode 100644 index 00000000000..c0de9e72766 --- /dev/null +++ b/e2e/testcases/e2e-cli-071_no_flag_resolve_references.go @@ -0,0 +1,27 @@ +package testcases + +// E2E-CLI-071 - KICS scan and ignore references +// should perform the scan successfully and return exit code 20 +func init() { //nolint + testSample := TestCase{ + Name: "should perform a valid scan and not resolve references [E2E-CLI-071]", + Args: args{ + Args: []cmdArgs{ + []string{"scan", "-o", "/path/e2e/output", + "--output-name", "E2E_CLI_071_RESULT", + "-p", "\"/path/test/fixtures/resolve_references\"", + "-i", "6c35d2c6-09f2-4e5c-a094-e0e91327071d,962fa01e-b791-4dcc-b04a-4a3e7389be5e", + }, + }, + ExpectedResult: []ResultsValidation{ + { + ResultsFile: "E2E_CLI_071_RESULT", + ResultsFormats: []string{"json"}, + }, + }, + }, + WantStatus: []int{20}, + } + + Tests = append(Tests, testSample) +} diff --git a/e2e/testcases/e2e-cli-072_flag_resolve_references.go b/e2e/testcases/e2e-cli-072_flag_resolve_references.go new file mode 100644 index 00000000000..413b6b6a14a --- /dev/null +++ b/e2e/testcases/e2e-cli-072_flag_resolve_references.go @@ -0,0 +1,28 @@ +package testcases + +// E2E-CLI-072 - KICS scan and ignore references +// should perform the scan successfully and return exit code 40,20 +func init() { //nolint + testSample := TestCase{ + Name: "should perform a valid scan and resolve references [E2E-CLI-072]", + Args: args{ + Args: []cmdArgs{ + []string{"scan", "-o", "/path/e2e/output", + "--output-name", "E2E_CLI_072_RESULT", + "-p", "\"/path/test/fixtures/resolve_references\"", + "-i", "6c35d2c6-09f2-4e5c-a094-e0e91327071d,962fa01e-b791-4dcc-b04a-4a3e7389be5e", + "--enable-openapi-refs", + }, + }, + ExpectedResult: []ResultsValidation{ + { + ResultsFile: "E2E_CLI_072_RESULT", + ResultsFormats: []string{"json"}, + }, + }, + }, + WantStatus: []int{40}, + } + + Tests = append(Tests, testSample) +} diff --git a/e2e/testcases/e2e-cli-073_json_no_flag_resolve_references.go b/e2e/testcases/e2e-cli-073_json_no_flag_resolve_references.go new file mode 100644 index 00000000000..e27f8b74745 --- /dev/null +++ b/e2e/testcases/e2e-cli-073_json_no_flag_resolve_references.go @@ -0,0 +1,28 @@ +package testcases + +// E2E-CLI-073 - KICS scan and ignore references +// should perform the scan successfully and return exit code 0 +// no results expected +func init() { //nolint + testSample := TestCase{ + Name: "should perform a valid scan and not resolve references [E2E-CLI-073]", + Args: args{ + Args: []cmdArgs{ + []string{"scan", "-o", "/path/e2e/output", + "--output-name", "E2E_CLI_073_RESULT", + "-p", "\"/path/test/fixtures/resolve_references_json\"", + "-i", "750b40be-4bac-4f59-bdc4-1ca0e6c3450e", + }, + }, + ExpectedResult: []ResultsValidation{ + { + ResultsFile: "E2E_CLI_073_RESULT", + ResultsFormats: []string{"json"}, + }, + }, + }, + WantStatus: []int{0}, + } + + Tests = append(Tests, testSample) +} diff --git a/e2e/testcases/e2e-cli-074_json_flag_resolve_references.go b/e2e/testcases/e2e-cli-074_json_flag_resolve_references.go new file mode 100644 index 00000000000..90a5388c959 --- /dev/null +++ b/e2e/testcases/e2e-cli-074_json_flag_resolve_references.go @@ -0,0 +1,28 @@ +package testcases + +// E2E-CLI-074 - KICS scan and ignore references +// should perform the scan successfully and return exit code 20 +func init() { //nolint + testSample := TestCase{ + Name: "should perform a valid scan and resolve references [E2E-CLI-074]", + Args: args{ + Args: []cmdArgs{ + []string{"scan", "-o", "/path/e2e/output", + "--output-name", "E2E_CLI_074_RESULT", + "-p", "\"/path/test/fixtures/resolve_references_json\"", + "-i", "750b40be-4bac-4f59-bdc4-1ca0e6c3450e", + "--enable-openapi-refs", + }, + }, + ExpectedResult: []ResultsValidation{ + { + ResultsFile: "E2E_CLI_074_RESULT", + ResultsFormats: []string{"json"}, + }, + }, + }, + WantStatus: []int{20}, + } + + Tests = append(Tests, testSample) +} diff --git a/internal/console/assets/scan-flags.json b/internal/console/assets/scan-flags.json index fb7164209fa..73484b94778 100644 --- a/internal/console/assets/scan-flags.json +++ b/internal/console/assets/scan-flags.json @@ -201,5 +201,11 @@ "shorthandFlag": "", "defaultValue": "false", "usage": "disables the exclusion of paths specified within .gitignore file" + }, + "enable-openapi-refs": { + "flagType": "bool", + "shorthandFlag": "", + "defaultValue": "false", + "usage": "resolve the file reference, on OpenAPI files" } } diff --git a/internal/console/flags/scan_flags.go b/internal/console/flags/scan_flags.go index c2d2288aca8..95f0caa0009 100644 --- a/internal/console/flags/scan_flags.go +++ b/internal/console/flags/scan_flags.go @@ -34,4 +34,5 @@ const ( DisableSecretsFlag = "disable-secrets" SecretsRegexesPathFlag = "secrets-regexes-path" //nolint:gosec ExcludeGitIgnore = "exclude-gitignore" + OpenAPIReferencesFlag = "enable-openapi-refs" ) diff --git a/internal/console/remediate.go b/internal/console/remediate.go index aaea010d0f9..ffe7dbc7f3a 100644 --- a/internal/console/remediate.go +++ b/internal/console/remediate.go @@ -78,6 +78,7 @@ func preRemediate(cmd *cobra.Command) error { func remediate() error { resultsPath := flags.GetStrFlag(flags.Results) include := flags.GetMultiStrFlag(flags.IncludeIds) + openAPIResolveReferences := flags.GetBoolFlag(flags.OpenAPIReferencesFlag) filepath.Clean(resultsPath) @@ -105,7 +106,7 @@ func remediate() error { for filePath := range remediationSets { fix := remediationSets[filePath].(remediation.Set) - err = summary.RemediateFile(filePath, fix) + err = summary.RemediateFile(filePath, fix, openAPIResolveReferences) if err != nil { return err } diff --git a/internal/console/scan.go b/internal/console/scan.go index c9ea0e5f2d7..26d132f309d 100644 --- a/internal/console/scan.go +++ b/internal/console/scan.go @@ -139,6 +139,7 @@ func getScanParameters(changedDefaultQueryPath, changedDefaultLibrariesPath bool ChangedDefaultQueryPath: changedDefaultQueryPath, BillOfMaterials: flags.GetBoolFlag(flags.BomFlag), ExcludeGitIgnore: flags.GetBoolFlag(flags.ExcludeGitIgnore), + OpenAPIResolveReferences: flags.GetBoolFlag(flags.OpenAPIReferencesFlag), } return &scanParams diff --git a/pkg/analyzer/analyzer.go b/pkg/analyzer/analyzer.go index 82a1fc95b81..5326c782681 100644 --- a/pkg/analyzer/analyzer.go +++ b/pkg/analyzer/analyzer.go @@ -19,6 +19,7 @@ import ( yamlParser "gopkg.in/yaml.v3" ) +// move the openApi regex to public to be used on file.go // openAPIRegex - Regex that finds OpenAPI defining property "openapi" or "swagger" // openAPIRegexInfo - Regex that finds OpenAPI defining property "info" // openAPIRegexPath - Regex that finds OpenAPI defining property "paths", "components", or "webhooks" (from 3.1.0) @@ -28,9 +29,9 @@ import ( // k8sRegexMetadata - Regex that finds Kubernetes defining property "metadata" // k8sRegexSpec - Regex that finds Kubernetes defining property "spec" var ( - openAPIRegex = regexp.MustCompile(`("(openapi|swagger)"|(openapi|swagger))\s*:`) - openAPIRegexInfo = regexp.MustCompile(`("info"|info)\s*:`) - openAPIRegexPath = regexp.MustCompile(`("(paths|components|webhooks)"|(paths|components|webhooks))\s*:`) + OpenAPIRegex = regexp.MustCompile(`("(openapi|swagger)"|(openapi|swagger))\s*:`) + OpenAPIRegexInfo = regexp.MustCompile(`("info"|info)\s*:`) + OpenAPIRegexPath = regexp.MustCompile(`("(paths|components|webhooks)"|(paths|components|webhooks))\s*:`) armRegexContentVersion = regexp.MustCompile(`"contentVersion"\s*:`) armRegexResources = regexp.MustCompile(`"resources"\s*:`) cloudRegex = regexp.MustCompile(`("Resources"|Resources)\s*:`) @@ -152,9 +153,9 @@ type Analyzer struct { var types = map[string]regexSlice{ "openapi": { regex: []*regexp.Regexp{ - openAPIRegex, - openAPIRegexInfo, - openAPIRegexPath, + OpenAPIRegex, + OpenAPIRegexInfo, + OpenAPIRegexPath, }, }, "kubernetes": { diff --git a/pkg/kics/resolver_sink.go b/pkg/kics/resolver_sink.go index 54c7ca9f24d..c6da572dff2 100644 --- a/pkg/kics/resolver_sink.go +++ b/pkg/kics/resolver_sink.go @@ -14,7 +14,7 @@ import ( "github.com/rs/zerolog/log" ) -func (s *Service) resolverSink(ctx context.Context, filename, scanID string) ([]string, error) { +func (s *Service) resolverSink(ctx context.Context, filename, scanID string, openAPIResolveReferences bool) ([]string, error) { kind := s.Resolver.GetType(filename) if kind == model.KindCOMMON { return []string{}, nil @@ -30,7 +30,7 @@ func (s *Service) resolverSink(ctx context.Context, filename, scanID string) ([] countLines := bytes.Count(rfile.Content, []byte{'\n'}) + 1 s.Tracker.TrackFileFoundCountLines(countLines) - documents, err := s.Parser.Parse(rfile.FileName, rfile.Content) + documents, err := s.Parser.Parse(rfile.FileName, rfile.Content, openAPIResolveReferences) if err != nil { if documents.Kind == "break" { return []string{}, nil diff --git a/pkg/kics/service.go b/pkg/kics/service.go index e2ba6feb1c4..f13c40ae5e0 100644 --- a/pkg/kics/service.go +++ b/pkg/kics/service.go @@ -61,7 +61,10 @@ type Service struct { } // PrepareSources will prepare the sources to be scanned -func (s *Service) PrepareSources(ctx context.Context, scanID string, wg *sync.WaitGroup, errCh chan<- error) { +func (s *Service) PrepareSources(ctx context.Context, + scanID string, + openAPIResolveReferences bool, + wg *sync.WaitGroup, errCh chan<- error) { defer wg.Done() // CxSAST query under review data := make([]byte, mbConst) @@ -69,10 +72,10 @@ func (s *Service) PrepareSources(ctx context.Context, scanID string, wg *sync.Wa ctx, s.Parser.SupportedExtensions(), func(ctx context.Context, filename string, rc io.ReadCloser) error { - return s.sink(ctx, filename, scanID, rc, data) + return s.sink(ctx, filename, scanID, rc, data, openAPIResolveReferences) }, func(ctx context.Context, filename string) ([]string, error) { // Sink used for resolver files and templates - return s.resolverSink(ctx, filename, scanID) + return s.resolverSink(ctx, filename, scanID, openAPIResolveReferences) }, ); err != nil { errCh <- errors.Wrap(err, "failed to read sources") diff --git a/pkg/kics/sink.go b/pkg/kics/sink.go index a1acf322b32..10524e63937 100644 --- a/pkg/kics/sink.go +++ b/pkg/kics/sink.go @@ -26,7 +26,9 @@ var ( } ) -func (s *Service) sink(ctx context.Context, filename, scanID string, rc io.Reader, data []byte) error { +func (s *Service) sink(ctx context.Context, filename, scanID string, + rc io.Reader, data []byte, + openAPIResolveReferences bool) error { s.Tracker.TrackFileFound() log.Debug().Msgf("Starting to process file %s", filename) @@ -40,8 +42,7 @@ func (s *Service) sink(ctx context.Context, filename, scanID string, rc io.Reade if err != nil { return errors.Wrapf(err, "failed to get file content: %s", filename) } - - documents, err := s.Parser.Parse(filename, *content) + documents, err := s.Parser.Parse(filename, *content, openAPIResolveReferences) if err != nil { log.Err(err).Msgf("failed to parse file content: %s", filename) return nil diff --git a/pkg/parser/ansible/ini/config/parser.go b/pkg/parser/ansible/ini/config/parser.go index 03e232ddf57..67e9b8be983 100644 --- a/pkg/parser/ansible/ini/config/parser.go +++ b/pkg/parser/ansible/ini/config/parser.go @@ -13,7 +13,7 @@ import ( type Parser struct { } -func (p *Parser) Resolve(fileContent []byte, filename string) ([]byte, error) { +func (p *Parser) Resolve(fileContent []byte, _ string, _ bool) ([]byte, error) { return fileContent, nil } diff --git a/pkg/parser/ansible/ini/hosts/parser.go b/pkg/parser/ansible/ini/hosts/parser.go index d83271f01d6..b921062f684 100644 --- a/pkg/parser/ansible/ini/hosts/parser.go +++ b/pkg/parser/ansible/ini/hosts/parser.go @@ -13,7 +13,7 @@ import ( type Parser struct { } -func (p *Parser) Resolve(fileContent []byte, _ string) ([]byte, error) { +func (p *Parser) Resolve(fileContent []byte, _ string, _ bool) ([]byte, error) { return fileContent, nil } diff --git a/pkg/parser/buildah/parser.go b/pkg/parser/buildah/parser.go index 7f21f751028..07b1ad67fc8 100644 --- a/pkg/parser/buildah/parser.go +++ b/pkg/parser/buildah/parser.go @@ -51,7 +51,7 @@ const ( ) // Resolve - replace or modifies in-memory content before parsing -func (p *Parser) Resolve(fileContent []byte, _ string) ([]byte, error) { +func (p *Parser) Resolve(fileContent []byte, _ string, _ bool) ([]byte, error) { return fileContent, nil } diff --git a/pkg/parser/buildah/parser_test.go b/pkg/parser/buildah/parser_test.go index b321acc1257..cb27ee87f17 100644 --- a/pkg/parser/buildah/parser_test.go +++ b/pkg/parser/buildah/parser_test.go @@ -318,7 +318,7 @@ func TestParser_Resolve(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { p := &Parser{} - got, err := p.Resolve(tt.args.fileContent, tt.args.filename) + got, err := p.Resolve(tt.args.fileContent, tt.args.filename, true) if (err != nil) != tt.wantErr { t.Errorf("Parser.Resolve() error = %v, wantErr %v", err, tt.wantErr) return diff --git a/pkg/parser/docker/parser.go b/pkg/parser/docker/parser.go index 95f0d370880..9e0a2d88582 100644 --- a/pkg/parser/docker/parser.go +++ b/pkg/parser/docker/parser.go @@ -34,7 +34,7 @@ type Command struct { } // Resolve - replace or modifies in-memory content before parsing -func (p *Parser) Resolve(fileContent []byte, _ string) ([]byte, error) { +func (p *Parser) Resolve(fileContent []byte, _ string, _ bool) ([]byte, error) { return fileContent, nil } diff --git a/pkg/parser/docker/parser_test.go b/pkg/parser/docker/parser_test.go index 836605415da..5369f4ecbef 100644 --- a/pkg/parser/docker/parser_test.go +++ b/pkg/parser/docker/parser_test.go @@ -149,7 +149,7 @@ func Test_Resolve(t *testing.T) { ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-jar","/app.jar"] ` - resolved, err := parser.Resolve([]byte(have), "Dockerfile") + resolved, err := parser.Resolve([]byte(have), "Dockerfile", true) require.NoError(t, err) require.Equal(t, []byte(have), resolved) } diff --git a/pkg/parser/grpc/parser.go b/pkg/parser/grpc/parser.go index 3274a9fab0d..97f88b5602d 100644 --- a/pkg/parser/grpc/parser.go +++ b/pkg/parser/grpc/parser.go @@ -65,7 +65,7 @@ func (p *Parser) StringifyContent(content []byte) (string, error) { } // Resolve resolves proto files variables -func (p *Parser) Resolve(fileContent []byte, _ string) ([]byte, error) { +func (p *Parser) Resolve(fileContent []byte, _ string, _ bool) ([]byte, error) { return fileContent, nil } diff --git a/pkg/parser/grpc/parser_test.go b/pkg/parser/grpc/parser_test.go index 20a19d6813a..a7d35db7ef1 100644 --- a/pkg/parser/grpc/parser_test.go +++ b/pkg/parser/grpc/parser_test.go @@ -527,7 +527,7 @@ func TestParser_Resolve(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { p := &Parser{} - got, err := p.Resolve(tt.args.fileContent, tt.args.filename) + got, err := p.Resolve(tt.args.fileContent, tt.args.filename, true) if (err != nil) != tt.wantErr { t.Errorf("Parser.Resolve() error = %v, wantErr %v", err, tt.wantErr) return diff --git a/pkg/parser/json/parser.go b/pkg/parser/json/parser.go index dae67f0628e..53c1828de5a 100644 --- a/pkg/parser/json/parser.go +++ b/pkg/parser/json/parser.go @@ -16,11 +16,11 @@ type Parser struct { } // Resolve - replace or modifies in-memory content before parsing -func (p *Parser) Resolve(fileContent []byte, filename string) ([]byte, error) { +func (p *Parser) Resolve(fileContent []byte, filename string, resolveReferences bool) ([]byte, error) { // Resolve files passed as arguments with file resolver (e.g. file://) res := file.NewResolver(json.Unmarshal, json.Marshal, p.SupportedExtensions()) resolvedFilesCache := make(map[string]file.ResolvedFile) - resolved := res.Resolve(fileContent, filename, 0, resolvedFilesCache) + resolved := res.Resolve(fileContent, filename, 0, resolvedFilesCache, resolveReferences) p.resolvedFiles = res.ResolvedFiles if len(res.ResolvedFiles) == 0 { return fileContent, nil diff --git a/pkg/parser/json/parser_test.go b/pkg/parser/json/parser_test.go index fdbccd5f960..923ca70111a 100644 --- a/pkg/parser/json/parser_test.go +++ b/pkg/parser/json/parser_test.go @@ -49,7 +49,7 @@ func TestParser_Parse(t *testing.T) { func Test_Resolve(t *testing.T) { parser := &Parser{} - resolved, err := parser.Resolve([]byte(have), "test.json") + resolved, err := parser.Resolve([]byte(have), "test.json", true) require.NoError(t, err) require.Equal(t, have, string(resolved)) } diff --git a/pkg/parser/parser.go b/pkg/parser/parser.go index d8137647aae..a42fcf5ee7e 100644 --- a/pkg/parser/parser.go +++ b/pkg/parser/parser.go @@ -17,7 +17,7 @@ type kindParser interface { SupportedExtensions() []string SupportedTypes() map[string]bool Parse(filePath string, fileContent []byte) ([]model.Document, []int, error) - Resolve(fileContent []byte, filename string) ([]byte, error) + Resolve(fileContent []byte, filename string, _ bool) ([]byte, error) StringifyContent(content []byte) (string, error) GetResolvedFiles() map[string]model.ResolvedFile } @@ -117,11 +117,11 @@ func (c *Parser) CommentsCommands(filePath string, fileContent []byte) model.Com // Parse executes a parser on the fileContent and returns the file content as a Document, the file kind and // an error, if an error has occurred -func (c *Parser) Parse(filePath string, fileContent []byte) (ParsedDocument, error) { +func (c *Parser) Parse(filePath string, fileContent []byte, openAPIResolveReferences bool) (ParsedDocument, error) { fileContent = utils.DecryptAnsibleVault(fileContent, os.Getenv("ANSIBLE_VAULT_PASSWORD_FILE")) if c.isValidExtension(filePath) { - resolved, err := c.parsers.Resolve(fileContent, filePath) + resolved, err := c.parsers.Resolve(fileContent, filePath, openAPIResolveReferences) if err != nil { return ParsedDocument{}, err } diff --git a/pkg/parser/parser_test.go b/pkg/parser/parser_test.go index 629cfb56986..a205a87ad5b 100644 --- a/pkg/parser/parser_test.go +++ b/pkg/parser/parser_test.go @@ -25,7 +25,7 @@ func TestParser_Parse(t *testing.T) { "name": "CxBraga" } } -`)) +`), true) require.NoError(t, err) require.Len(t, docs.Docs, 1) require.Contains(t, docs.Docs[0], "martin") @@ -39,7 +39,7 @@ func TestParser_Parse(t *testing.T) { docs, err := parser.Parse("test.yaml", []byte(` martin: name: CxBraga -`)) +`), true) require.NoError(t, err) require.Len(t, docs.Docs, 1) require.Contains(t, docs.Docs[0], "martin") @@ -54,7 +54,7 @@ martin: FROM foo COPY . / RUN echo hello -`)) +`), true) require.NoError(t, err) require.Len(t, docs.Docs, 1) @@ -70,7 +70,7 @@ func TestParser_Empty(t *testing.T) { t.Errorf("Error building parser: %s", err) } for _, parser := range p { - docs, err := parser.Parse("test.json", nil) + docs, err := parser.Parse("test.json", nil, true) require.Nil(t, docs.Docs) require.Equal(t, model.FileKind(""), docs.Kind) require.Error(t, err) diff --git a/pkg/parser/terraform/terraform.go b/pkg/parser/terraform/terraform.go index 79b3edabbf8..4e7e43e74cd 100644 --- a/pkg/parser/terraform/terraform.go +++ b/pkg/parser/terraform/terraform.go @@ -46,7 +46,7 @@ func NewDefaultWithVarsPath(terraformVarsPath string) *Parser { } // Resolve - replace or modifies in-memory content before parsing -func (p *Parser) Resolve(fileContent []byte, filename string) ([]byte, error) { +func (p *Parser) Resolve(fileContent []byte, filename string, _ bool) ([]byte, error) { // handle panic during resolve process defer func() { if r := recover(); r != nil { diff --git a/pkg/parser/terraform/terraform_test.go b/pkg/parser/terraform/terraform_test.go index 5e97a96cc16..60a164edc56 100644 --- a/pkg/parser/terraform/terraform_test.go +++ b/pkg/parser/terraform/terraform_test.go @@ -178,7 +178,7 @@ func Test_namelessResource(t *testing.T) { func Test_Resolve(t *testing.T) { parser := NewDefault() - resolved, err := parser.Resolve([]byte(have), "test.tf") + resolved, err := parser.Resolve([]byte(have), "test.tf", true) require.NoError(t, err) require.Equal(t, []byte(have), resolved) } diff --git a/pkg/parser/yaml/parser.go b/pkg/parser/yaml/parser.go index e213533a88a..fd5738b8b06 100644 --- a/pkg/parser/yaml/parser.go +++ b/pkg/parser/yaml/parser.go @@ -18,11 +18,11 @@ type Parser struct { } // Resolve - replace or modifies in-memory content before parsing -func (p *Parser) Resolve(fileContent []byte, filename string) ([]byte, error) { +func (p *Parser) Resolve(fileContent []byte, filename string, resolveReferences bool) ([]byte, error) { // Resolve files passed as arguments with file resolver (e.g. file://) res := file.NewResolver(yaml.Unmarshal, yaml.Marshal, p.SupportedExtensions()) resolvedFilesCache := make(map[string]file.ResolvedFile) - resolved := res.Resolve(fileContent, filename, 0, resolvedFilesCache) + resolved := res.Resolve(fileContent, filename, 0, resolvedFilesCache, resolveReferences) p.resolvedFiles = res.ResolvedFiles if len(res.ResolvedFiles) == 0 { return fileContent, nil diff --git a/pkg/parser/yaml/parser_test.go b/pkg/parser/yaml/parser_test.go index 0d0ccca953e..f674cdfcba9 100644 --- a/pkg/parser/yaml/parser_test.go +++ b/pkg/parser/yaml/parser_test.go @@ -389,7 +389,7 @@ func Test_Resolve(t *testing.T) { ` parser := &Parser{} - resolved, err := parser.Resolve([]byte(have), "test.yaml") + resolved, err := parser.Resolve([]byte(have), "test.yaml", true) require.NoError(t, err) require.Equal(t, []byte(have), resolved) } diff --git a/pkg/remediation/remediation.go b/pkg/remediation/remediation.go index 103b133d885..024e15ba589 100644 --- a/pkg/remediation/remediation.go +++ b/pkg/remediation/remediation.go @@ -51,7 +51,7 @@ type Set struct { } // RemediateFile remediationSets the replacements first and secondly, the additions sorted down -func (s *Summary) RemediateFile(filePath string, remediationSet Set) error { +func (s *Summary) RemediateFile(filePath string, remediationSet Set, openAPIResolveReferences bool) error { filepath.Clean(filePath) content, err := os.ReadFile(filePath) @@ -67,7 +67,7 @@ func (s *Summary) RemediateFile(filePath string, remediationSet Set) error { for i := range remediationSet.Replacement { r := remediationSet.Replacement[i] remediatedLines := replacement(&r, lines) - if len(remediatedLines) > 0 && willRemediate(remediatedLines, filePath, &r) { + if len(remediatedLines) > 0 && willRemediate(remediatedLines, filePath, &r, openAPIResolveReferences) { lines = s.writeRemediation(remediatedLines, lines, filePath, r.SimilarityID) } } @@ -83,7 +83,7 @@ func (s *Summary) RemediateFile(filePath string, remediationSet Set) error { for i := range remediationSet.Addition { a := remediationSet.Addition[i] remediatedLines := addition(&a, &lines) - if len(remediatedLines) > 0 && willRemediate(remediatedLines, filePath, &a) { + if len(remediatedLines) > 0 && willRemediate(remediatedLines, filePath, &a, openAPIResolveReferences) { lines = s.writeRemediation(remediatedLines, lines, filePath, a.SimilarityID) } } diff --git a/pkg/remediation/remediation_test.go b/pkg/remediation/remediation_test.go index f3f6c19b0c1..e9826b2e2fc 100644 --- a/pkg/remediation/remediation_test.go +++ b/pkg/remediation/remediation_test.go @@ -111,7 +111,7 @@ func Test_RemediateFile(t *testing.T) { tmpFileName := filepath.Join(os.TempDir(), "temporary-remediation"+utils.NextRandom()+filepath.Ext(filePathCopyFrom)) tmpFile := CreateTempFile(filePathCopyFrom, tmpFileName) - s.RemediateFile(tmpFile, tt.args.remediate) + s.RemediateFile(tmpFile, tt.args.remediate, false) os.Remove(tmpFile) require.Equal(t, s.ActualRemediationDoneNumber, tt.actualRemediationDoneNumber) diff --git a/pkg/remediation/scan.go b/pkg/remediation/scan.go index 79d51c399da..afa81532c7d 100644 --- a/pkg/remediation/scan.go +++ b/pkg/remediation/scan.go @@ -36,9 +36,9 @@ type runQueryInfo struct { } // scanTmpFile scans a temporary file against a specific query -func scanTmpFile(tmpFile, queryID string, remediated []byte) ([]model.Vulnerability, error) { +func scanTmpFile(tmpFile, queryID string, remediated []byte, openAPIResolveReferences bool) ([]model.Vulnerability, error) { // get payload - files, err := getPayload(tmpFile, remediated) + files, err := getPayload(tmpFile, remediated, openAPIResolveReferences) if err != nil { log.Err(err) @@ -81,7 +81,7 @@ func scanTmpFile(tmpFile, queryID string, remediated []byte) ([]model.Vulnerabil } // getPayload gets the payload of a file -func getPayload(filePath string, content []byte) (model.FileMetadatas, error) { +func getPayload(filePath string, content []byte, openAPIResolveReferences bool) (model.FileMetadatas, error) { ext := utils.GetExtension(filePath) var p []*parser.Parser var err error @@ -116,7 +116,7 @@ func getPayload(filePath string, content []byte) (model.FileMetadatas, error) { return model.FileMetadatas{}, errors.New("failed to get parser") } - documents, er := p[0].Parse(filePath, content) + documents, er := p[0].Parse(filePath, content, openAPIResolveReferences) if er != nil { log.Error().Msgf("failed to parse file '%s': %s", filePath, er) diff --git a/pkg/remediation/utils.go b/pkg/remediation/utils.go index 25c2be88109..1bda7da3482 100644 --- a/pkg/remediation/utils.go +++ b/pkg/remediation/utils.go @@ -49,7 +49,7 @@ func getBefore(line string) string { } // willRemediate verifies if the remediation actually removes the result -func willRemediate(remediated []string, originalFileName string, remediation *Remediation) bool { +func willRemediate(remediated []string, originalFileName string, remediation *Remediation, openAPIResolveReferences bool) bool { filepath.Clean(originalFileName) // create temporary file tmpFile := filepath.Join(os.TempDir(), "temporary-remediation-"+utils.NextRandom()+"-"+filepath.Base(originalFileName)) @@ -75,7 +75,7 @@ func willRemediate(remediated []string, originalFileName string, remediation *Re } // scan the temporary file to verify if the remediation removed the result - results, err := scanTmpFile(tmpFile, remediation.QueryID, content) + results, err := scanTmpFile(tmpFile, remediation.QueryID, content, openAPIResolveReferences) if err != nil { log.Error().Msgf("failed to get results of query %s: %s", remediation.QueryID, err) diff --git a/pkg/resolver/file/file.go b/pkg/resolver/file/file.go index 13a06aff1b4..c2c3cbd24f1 100644 --- a/pkg/resolver/file/file.go +++ b/pkg/resolver/file/file.go @@ -12,6 +12,7 @@ import ( "strings" "github.com/Checkmarx/kics/internal/constants" + "github.com/Checkmarx/kics/pkg/analyzer" "gopkg.in/yaml.v3" "github.com/Checkmarx/kics/pkg/model" @@ -46,8 +47,23 @@ func NewResolver( } } +func isOpenAPI(fileContent []byte) bool { + regexToRun := + []*regexp.Regexp{analyzer.OpenAPIRegexInfo, + analyzer.OpenAPIRegexPath, + analyzer.OpenAPIRegex} + for _, regex := range regexToRun { + if !regex.Match(fileContent) { + return false + } + } + return true +} + // Resolve - replace or modifies in-memory content before parsing -func (r *Resolver) Resolve(fileContent []byte, path string, resolveCount int, resolvedFilesCache map[string]ResolvedFile) []byte { +func (r *Resolver) Resolve(fileContent []byte, path string, + resolveCount int, resolvedFilesCache map[string]ResolvedFile, + resolveReferences bool) []byte { // handle panic during resolve process defer func() { if r := recover(); r != nil { @@ -56,8 +72,12 @@ func (r *Resolver) Resolve(fileContent []byte, path string, resolveCount int, re } }() + if !resolveReferences && isOpenAPI(fileContent) { + return fileContent + } + if utils.Contains(filepath.Ext(path), []string{".yml", ".yaml"}) { - return r.yamlResolve(fileContent, path, resolveCount, resolvedFilesCache) + return r.yamlResolve(fileContent, path, resolveCount, resolvedFilesCache, resolveReferences) } var obj any err := r.unmarshler(fileContent, &obj) @@ -66,7 +86,7 @@ func (r *Resolver) Resolve(fileContent []byte, path string, resolveCount int, re } // resolve the paths - obj, _ = r.walk(fileContent, obj, obj, path, resolveCount, resolvedFilesCache, false) + obj, _ = r.walk(fileContent, obj, obj, path, resolveCount, resolvedFilesCache, false, resolveReferences) b, err := json.MarshalIndent(obj, "", "") if err == nil { @@ -83,31 +103,39 @@ func (r *Resolver) walk( path string, resolveCount int, resolvedFilesCache map[string]ResolvedFile, - refBool bool) (any, bool) { + refBool bool, + resolveReferences bool) (any, bool) { // go over the value and replace paths with the real content switch typedValue := value.(type) { case string: if filepath.Base(path) != typedValue { - return r.resolvePath(originalFileContent, fullObject, typedValue, path, resolveCount, resolvedFilesCache, refBool) + return r.resolvePath(originalFileContent, fullObject, typedValue, path, resolveCount, resolvedFilesCache, refBool, resolveReferences) } return value, false case []any: for i, v := range typedValue { - typedValue[i], _ = r.walk(originalFileContent, fullObject, v, path, resolveCount, resolvedFilesCache, refBool) + typedValue[i], _ = r.walk(originalFileContent, fullObject, v, path, resolveCount, resolvedFilesCache, refBool, resolveReferences) } return typedValue, false case map[string]any: - return r.handleMap(originalFileContent, fullObject, typedValue, path, resolveCount, resolvedFilesCache) + return r.handleMap(originalFileContent, fullObject, typedValue, path, resolveCount, resolvedFilesCache, resolveReferences) default: return value, false } } -func (r *Resolver) handleMap(originalFileContent []byte, fullObject interface{}, value map[string]interface{}, path string, - resolveCount int, resolvedFilesCache map[string]ResolvedFile) (any, bool) { +func (r *Resolver) handleMap( + originalFileContent []byte, + fullObject interface{}, + value map[string]interface{}, + path string, + resolveCount int, + resolvedFilesCache map[string]ResolvedFile, + resolveReferences bool, +) (any, bool) { for k, v := range value { isRef := strings.Contains(strings.ToLower(k), "$ref") - val, res := r.walk(originalFileContent, fullObject, v, path, resolveCount, resolvedFilesCache, isRef) + val, res := r.walk(originalFileContent, fullObject, v, path, resolveCount, resolvedFilesCache, isRef, resolveReferences) // check if it is a ref then add new details if valMap, ok := val.(map[string]interface{}); (ok || !res) && isRef { // Create RefMetadata and add it to the resolved value map @@ -127,7 +155,9 @@ func (r *Resolver) handleMap(originalFileContent []byte, fullObject interface{}, return value, false } -func (r *Resolver) yamlResolve(fileContent []byte, path string, resolveCount int, resolvedFilesCache map[string]ResolvedFile) []byte { +func (r *Resolver) yamlResolve(fileContent []byte, path string, + resolveCount int, resolvedFilesCache map[string]ResolvedFile, + resolveReferences bool) []byte { var obj yaml.Node err := r.unmarshler(fileContent, &obj) if err != nil { @@ -137,7 +167,7 @@ func (r *Resolver) yamlResolve(fileContent []byte, path string, resolveCount int fullObjectCopy := obj // resolve the paths - obj, _ = r.yamlWalk(fileContent, &fullObjectCopy, &obj, path, resolveCount, resolvedFilesCache, false) + obj, _ = r.yamlWalk(fileContent, &fullObjectCopy, &obj, path, resolveCount, resolvedFilesCache, false, resolveReferences) if obj.Kind == 1 && len(obj.Content) == 1 { obj = *obj.Content[0] @@ -158,12 +188,16 @@ func (r *Resolver) yamlWalk( path string, resolveCount int, resolvedFilesCache map[string]ResolvedFile, - refBool bool) (yaml.Node, bool) { + refBool bool, + resolveReferences bool) (yaml.Node, bool) { // go over the value and replace paths with the real content switch value.Kind { case yaml.ScalarNode: if filepath.Base(path) != value.Value { - return r.resolveYamlPath(originalFileContent, fullObject, value, path, resolveCount, resolvedFilesCache, refBool) + return r.resolveYamlPath(originalFileContent, fullObject, + value, path, + resolveCount, resolvedFilesCache, + refBool, resolveReferences) } return *value, false default: @@ -172,7 +206,10 @@ func (r *Resolver) yamlWalk( if i >= 1 { refBool = strings.Contains(value.Content[i-1].Value, "$ref") } - resolved, ok := r.yamlWalk(originalFileContent, fullObject, value.Content[i], path, resolveCount, resolvedFilesCache, refBool) + resolved, ok := r.yamlWalk(originalFileContent, fullObject, + value.Content[i], path, + resolveCount, resolvedFilesCache, + refBool, resolveReferences) if i >= 1 && refBool && (resolved.Kind == yaml.MappingNode || !ok) { // Create RefMetadata and add it to yaml Node @@ -220,7 +257,7 @@ func (r *Resolver) resolveYamlPath( filePath string, resolveCount int, resolvedFilesCache map[string]ResolvedFile, - refBool bool) (yaml.Node, bool) { + refBool bool, resolveReferences bool) (yaml.Node, bool) { value := v.Value if resolveCount > constants.MaxResolvedFiles || (strings.HasPrefix(value, "#") && !refBool) || (value == "#" && refBool) { return *v, false @@ -251,7 +288,7 @@ func (r *Resolver) resolveYamlPath( // Check if file has already been resolved, if not resolve it and save it for future references if _, ok := resolvedFilesCache[filename]; !ok { - if ret, isError := r.resolveFile(value, onlyFilePath, resolveCount, resolvedFilesCache, true); isError { + if ret, isError := r.resolveFile(value, onlyFilePath, resolveCount, resolvedFilesCache, true, resolveReferences); isError { if retYaml, yamlNode := ret.(yaml.Node); yamlNode { return retYaml, false } else { @@ -308,7 +345,7 @@ func (r *Resolver) resolveFile( filePath string, resolveCount int, resolvedFilesCache map[string]ResolvedFile, - yamlResolve bool) (any, bool) { + yamlResolve bool, resolveReferences bool) (any, bool) { // open the file with the content to replace file, err := os.Open(filepath.Clean(filePath)) if err != nil { @@ -324,7 +361,7 @@ func (r *Resolver) resolveFile( // read the content fileContent, _ := io.ReadAll(file) - resolvedFile := r.Resolve(fileContent, filePath, resolveCount+1, resolvedFilesCache) + resolvedFile := r.Resolve(fileContent, filePath, resolveCount+1, resolvedFilesCache, resolveReferences) if yamlResolve { var obj yaml.Node @@ -367,7 +404,7 @@ func (r *Resolver) resolvePath( value, filePath string, resolveCount int, resolvedFilesCache map[string]ResolvedFile, - refBool bool) (any, bool) { + refBool bool, resolveReferences bool) (any, bool) { if resolveCount > constants.MaxResolvedFiles || (strings.HasPrefix(value, "#") && !refBool) || (value == "#" && refBool) { return value, false } @@ -393,7 +430,7 @@ func (r *Resolver) resolvePath( // Check if file has already been resolved, if not resolve it and save it for future references if _, ok := resolvedFilesCache[onlyFilePath]; !ok { - if ret, isError := r.resolveFile(value, onlyFilePath, resolveCount, resolvedFilesCache, false); isError { + if ret, isError := r.resolveFile(value, onlyFilePath, resolveCount, resolvedFilesCache, false, resolveReferences); isError { return ret, false } } diff --git a/pkg/resolver/file/file_test.go b/pkg/resolver/file/file_test.go index 1d2df5e8fb3..f80fcc10b02 100644 --- a/pkg/resolver/file/file_test.go +++ b/pkg/resolver/file/file_test.go @@ -2,6 +2,9 @@ package file import ( "encoding/json" + "fmt" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" "io/ioutil" "os" "path/filepath" @@ -13,7 +16,7 @@ import ( "gopkg.in/yaml.v3" ) -func TestResolver_Resolve(t *testing.T) { +func TestResolver_Resolve_With_ResolveReferences(t *testing.T) { err := test.ChangeCurrentDir("kics") if err != nil { t.Fatal(err) @@ -79,13 +82,118 @@ func TestResolver_Resolve(t *testing.T) { t.Fatal(err) } - if got := r.Resolve(cont, tt.args.path, 0, make(map[string]ResolvedFile)); !reflect.DeepEqual(prepareString(string(got)), prepareString(string(tt.want))) { + if got := r.Resolve(cont, tt.args.path, 0, make(map[string]ResolvedFile), true); !reflect.DeepEqual(prepareString(string(got)), prepareString(string(tt.want))) { t.Errorf("Resolve() = %v, want = %v", prepareString(string(got)), prepareString(string(tt.want))) } }) } } +func TestResolver_Resolve_Without_ResolveReferences(t *testing.T) { + err := test.ChangeCurrentDir("kics") + if err != nil { + t.Fatal(err) + } + type fields struct { + *Resolver + } + type args struct { + path string + } + tests := []struct { + name string + fields fields + args args + want []byte + }{ + { + name: "yaml should resolve because is not openapi file", + fields: fields{ + Resolver: NewResolver(yaml.Unmarshal, yaml.Marshal, []string{".yml", ".yaml"}), + }, + args: args{ + path: filepath.ToSlash("test/fixtures/unresolved_openapi/responses/_index.yaml"), + }, + want: []byte( + `UnexpectedError:description:unexpectederrorcontent:application/json:schema:type:objectrequired:-code-messageproperties:code:type:integerformat:int32message:type:stringRefMetadata:$ref:"../schemas/Error.yaml"alone:trueRefMetadata:$ref:"./UnexpectedError.yaml"alone:trueNullResponse:description:NullresponseRefMetadata:$ref:"./NullResponse.yaml"alone:true`), + }, + { + name: "json should not resolve because is a openapi file", + fields: fields{ + Resolver: NewResolver(json.Unmarshal, json.Marshal, []string{".json"}), + }, + args: args{ + path: filepath.ToSlash("test/fixtures/unresolved_openapi_json/openapi.json"), + }, + want: []byte( + "{\"openapi\":\"3.0.3\",\"info\":{\"title\":\"Reference in reference example\",\"version\":\"1.0.0\"},\"paths\":{\"/api/test/ref/in/ref\":{\"post\":{\"requestBody\":{\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"messages/request.json\"}}}},\"responses\":{\"200\":{\"description\":\"Successful response\",\"content\":{\"application/json\":{\"schema\":{\"$ref\":\"messages/response.json\"}}}}}}}}}", + ), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + r := &Resolver{ + unmarshler: tt.fields.unmarshler, + marshler: tt.fields.marshler, + ResolvedFiles: tt.fields.ResolvedFiles, + Extension: tt.fields.Extension, + } + + cont, err := getFileContent(tt.args.path) + if err != nil { + t.Fatal(err) + } + + if got := r.Resolve(cont, tt.args.path, 0, make(map[string]ResolvedFile), false); !reflect.DeepEqual(prepareString(string(got)), prepareString(string(tt.want))) { + t.Errorf("Resolve() = %v, want = %v", prepareString(string(got)), prepareString(string(tt.want))) + } + }) + } +} + +func Test_IsOpenApi(t *testing.T) { + err := test.ChangeCurrentDir("kics") + if err != nil { + t.Fatal(err) + } + + tests := []struct { + name string + path string + want bool + }{ + { + name: "yaml Open Api", + path: "test/fixtures/resolve_references/swagger.yaml", + want: true, + }, + { + name: "json Open Api", + path: "test/fixtures/resolve_references_json/scan-2files.json", + want: true, + }, + { + name: "yml not Open Api", + path: "test/fixtures/resolve_references/paths/users/user.yaml", + want: false, + }, + { + name: "json not Open Api", + path: "test/fixtures/resolve_references_json/definitions.json", + want: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + cont, err := getFileContent(tt.path) + require.NoError(t, err) + got := isOpenAPI(cont) + assert.Equal(t, tt.want, got, fmt.Sprintf("Error: %s", tt.name)) + }) + } +} + func getFileContent(path string) ([]byte, error) { _, err := os.Stat(path) if err != nil { @@ -98,6 +206,7 @@ func getFileContent(path string) ([]byte, error) { func prepareString(content string) string { content = strings.Replace(content, "\n", "", -1) content = strings.Replace(content, "\t", "", -1) + content = strings.Replace(content, "\r", "", -1) content = strings.Replace(content, " ", "", -1) return content } diff --git a/pkg/scan/client.go b/pkg/scan/client.go index 18484ddf8d3..d1c780350c5 100644 --- a/pkg/scan/client.go +++ b/pkg/scan/client.go @@ -44,6 +44,7 @@ type Parameters struct { ScanID string BillOfMaterials bool ExcludeGitIgnore bool + OpenAPIResolveReferences bool } // Client represents a scan client diff --git a/pkg/scan/post_scan.go b/pkg/scan/post_scan.go index 1396e7dc8bc..e73a07b8f3e 100644 --- a/pkg/scan/post_scan.go +++ b/pkg/scan/post_scan.go @@ -4,6 +4,7 @@ import ( _ "embed" // Embed kics CLI img and scan-flags "os" "path/filepath" + "sort" "strings" "time" @@ -114,7 +115,7 @@ func (c *Client) postScan(scanResults *Results) error { return err } } - + sort.Strings(c.ScanParams.Path) summary := c.getSummary(scanResults.Results, time.Now(), model.PathParameters{ ScannedPaths: c.ScanParams.Path, PathExtractionMap: scanResults.ExtractedPaths.ExtractionMap, diff --git a/pkg/scan/scan.go b/pkg/scan/scan.go index dd4c83a1f6c..b624dbf343f 100644 --- a/pkg/scan/scan.go +++ b/pkg/scan/scan.go @@ -135,7 +135,8 @@ func (c *Client) executeScan(ctx context.Context) (*Results, error) { return nil, nil } - if err = scanner.PrepareAndScan(ctx, c.ScanParams.ScanID, *c.ProBarBuilder, executeScanParameters.services); err != nil { + if err = scanner.PrepareAndScan(ctx, c.ScanParams.ScanID, c.ScanParams.OpenAPIResolveReferences, *c.ProBarBuilder, + executeScanParameters.services); err != nil { log.Err(err) return nil, err } diff --git a/pkg/scanner/scanner.go b/pkg/scanner/scanner.go index 9afd6d78356..c74ada02e4f 100644 --- a/pkg/scanner/scanner.go +++ b/pkg/scanner/scanner.go @@ -12,7 +12,13 @@ import ( type serviceSlice []*kics.Service -func PrepareAndScan(ctx context.Context, scanID string, proBarBuilder progress.PbBuilder, services serviceSlice) error { +func PrepareAndScan( + ctx context.Context, + scanID string, + openAPIResolveReferences bool, + proBarBuilder progress.PbBuilder, + services serviceSlice, +) error { metrics.Metric.Start("prepare_sources") var wg sync.WaitGroup wgDone := make(chan bool) @@ -21,7 +27,7 @@ func PrepareAndScan(ctx context.Context, scanID string, proBarBuilder progress.P for _, service := range services { wg.Add(1) - go service.PrepareSources(ctx, scanID, &wg, errCh) + go service.PrepareSources(ctx, scanID, openAPIResolveReferences, &wg, errCh) } go func() { @@ -48,7 +54,8 @@ func PrepareAndScan(ctx context.Context, scanID string, proBarBuilder progress.P } // StartScan will run concurrent scans by parser -func StartScan(ctx context.Context, scanID string, proBarBuilder progress.PbBuilder, services serviceSlice) error { +func StartScan(ctx context.Context, scanID string, + proBarBuilder progress.PbBuilder, services serviceSlice) error { defer metrics.Metric.Stop() metrics.Metric.Start("start_scan") var wg sync.WaitGroup diff --git a/test/fixtures/resolve_references/paths/users/user.yaml b/test/fixtures/resolve_references/paths/users/user.yaml new file mode 100644 index 00000000000..5a7596e8e17 --- /dev/null +++ b/test/fixtures/resolve_references/paths/users/user.yaml @@ -0,0 +1,18 @@ +--- +get: + tags: + - users + summary: Get user + responses: + "200": + description: Ok + content: + application/json: + schema: + $ref: ../../schemas/MyResponse.yaml + "404": + description: not found + content: + application/json: + schema: + $ref: ../../schemas/MyResponse.yaml diff --git a/test/fixtures/resolve_references/schemas/MyResponse.yaml b/test/fixtures/resolve_references/schemas/MyResponse.yaml new file mode 100644 index 00000000000..9a12086dbd9 --- /dev/null +++ b/test/fixtures/resolve_references/schemas/MyResponse.yaml @@ -0,0 +1,5 @@ +--- +properties: + message: + type: string +type: object diff --git a/test/fixtures/resolve_references/swagger.yaml b/test/fixtures/resolve_references/swagger.yaml new file mode 100644 index 00000000000..e4c3f144916 --- /dev/null +++ b/test/fixtures/resolve_references/swagger.yaml @@ -0,0 +1,18 @@ +--- +openapi: 3.0.0 +info: + description: my description + version: 1.8.8 + title: my title +servers: + - url: http://localhost:3000/ +tags: + - name: users + description: users tag description +paths: + "/users/{userId}": + $ref: ./paths/users/user.yaml +components: + schemas: + MyResponse: + $ref: ./schemas/MyResponse.yaml diff --git a/test/fixtures/resolve_references_json/definitions.json b/test/fixtures/resolve_references_json/definitions.json new file mode 100644 index 00000000000..c3054db28ca --- /dev/null +++ b/test/fixtures/resolve_references_json/definitions.json @@ -0,0 +1,68 @@ +{ + "Country": { + "type": "object", + "properties": { + "a2": { + "type": "string" + }, + "a3": { + "type": "string" + }, + "countryName": { + "type": "string" + }, + "definition": { + "type": "string" + }, + "displayName": { + "type": "string" + }, + "gmtOffset": { + "type": "string" + }, + "governmentForm": { + "type": "string", + "enum": [ + "ARISTOCRACY", + "DEMOCRACY", + "MONARCHY", + "OLIGARCHY", + "OTHER", + "THEOCRACY", + "TIMOCRACY", + "TYRANNY" + ] + }, + "id": { + "type": "integer", + "format": "int32" + }, + "phoneCode": { + "type": "string" + } + }, + "title": "Country" + }, + "User": { + "type": "object", + "properties": { + "email": { + "type": "string" + }, + "firstName": { + "type": "string" + }, + "id": { + "type": "integer", + "format": "int64" + }, + "info": { + "type": "string" + }, + "password": { + "type": "string" + } + }, + "title": "User" + } +} \ No newline at end of file diff --git a/test/fixtures/resolve_references_json/scan-2files.json b/test/fixtures/resolve_references_json/scan-2files.json new file mode 100644 index 00000000000..60955492a17 --- /dev/null +++ b/test/fixtures/resolve_references_json/scan-2files.json @@ -0,0 +1,565 @@ +{ + "swagger": "2.0", + "info": { + "description": "API Endpoint Decoration", + "version": "1.0.0", + "title": "SANITY SCAN" + }, + "host": "localhost:8080", + "basePath": "/", + "tags": [ + { + "name": "country-controller", + "description": "Country Controller" + }, + { + "name": "user-controller", + "description": "User Controller" + }, + { + "name": "util-controller", + "description": "Util Controller" + } + ], + "paths": { + "/country/get/byId/{id}": { + "get": { + "tags": [ + "country-controller" + ], + "summary": "getCountryById", + "operationId": "getCountryByIdUsingGET", + "produces": [ + "*/*" + ], + "parameters": [ + { + "name": "id", + "in": "path", + "description": "id", + "required": true, + "type": "integer", + "format": "int32" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "./definitions.json#/Country" + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "404": { + "description": "Not Found" + } + } + } + }, + "/country/get/governmentForm/{id}": { + "get": { + "tags": [ + "country-controller" + ], + "summary": "getGovernmentFormById", + "operationId": "getGovernmentFormByIdUsingGET", + "produces": [ + "*/*" + ], + "parameters": [ + { + "name": "id", + "in": "path", + "description": "id", + "required": true, + "type": "integer", + "format": "int32" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "string", + "enum": [ + "ARISTOCRACY", + "DEMOCRACY", + "MONARCHY", + "OLIGARCHY", + "OTHER", + "THEOCRACY", + "TIMOCRACY", + "TYRANNY" + ] + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "404": { + "description": "Not Found" + } + } + } + }, + "/runCommand/{cmd}": { + "get": { + "tags": [ + "util-controller" + ], + "summary": "runCommand", + "operationId": "runCommandUsingGET", + "produces": [ + "*/*" + ], + "parameters": [ + { + "name": "cmd", + "in": "path", + "description": "cmd", + "required": true, + "type": "string" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "string" + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "404": { + "description": "Not Found" + } + } + } + }, + "/user/create/cart": { + "post": { + "tags": [ + "user-controller" + ], + "summary": "createUserCart", + "operationId": "createUserCartUsingPOST", + "consumes": [ + "application/json" + ], + "produces": [ + "*/*" + ], + "parameters": [ + { + "name": "product_ids", + "in": "query", + "description": "product_ids", + "required": true, + "type": "array", + "items": { + "type": "string" + }, + "collectionFormat": "multi" + }, + { + "name": "quantities", + "in": "query", + "description": "quantities", + "required": true, + "type": "array", + "items": { + "type": "integer", + "format": "int32" + }, + "collectionFormat": "multi" + }, + { + "in": "body", + "name": "user", + "description": "user", + "required": true, + "schema": { + "$ref": "./definitions.json#/User" + } + } + ], + "responses": { + "200": { + "description": "OK" + }, + "201": { + "description": "Created" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "404": { + "description": "Not Found" + } + } + } + }, + "/user/create/mapCart": { + "post": { + "tags": [ + "user-controller" + ], + "summary": "createUserMapCart", + "operationId": "createUserMapCartUsingPOST", + "consumes": [ + "application/json" + ], + "produces": [ + "*/*" + ], + "parameters": [ + { + "name": "mapCart", + "in": "query", + "description": "mapCart", + "required": true, + "items": { + "type": "object", + "additionalProperties": { + "type": "string" + } + } + }, + { + "in": "body", + "name": "user", + "description": "user", + "required": true, + "schema": { + "$ref": "./definitions.json#/User" + } + } + ], + "responses": { + "200": { + "description": "OK" + }, + "201": { + "description": "Created" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "404": { + "description": "Not Found" + } + } + } + }, + "/user/create/password": { + "post": { + "tags": [ + "user-controller" + ], + "summary": "createUserVar", + "operationId": "createUserVarUsingPOST", + "consumes": [ + "application/json" + ], + "produces": [ + "*/*" + ], + "parameters": [ + { + "name": "password", + "in": "query", + "description": "password", + "required": true, + "type": "string" + }, + { + "in": "body", + "name": "user", + "description": "user", + "required": true, + "schema": { + "$ref": "./definitions.json#/User" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "./definitions.json#/User" + } + }, + "201": { + "description": "Created" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "404": { + "description": "Not Found" + } + } + } + }, + "/user/get/byEmail/{email}": { + "get": { + "tags": [ + "user-controller" + ], + "summary": "getUserByEmail", + "operationId": "getUserByEmailUsingGET", + "produces": [ + "*/*" + ], + "parameters": [ + { + "name": "email", + "in": "path", + "description": "email", + "required": true, + "type": "string" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "./definitions.json#/User" + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "404": { + "description": "Not Found" + } + } + } + }, + "/user/get/byId/{id}": { + "get": { + "tags": [ + "user-controller" + ], + "summary": "getUserById", + "operationId": "getUserByIdUsingGET", + "produces": [ + "*/*" + ], + "parameters": [ + { + "name": "id", + "in": "path", + "description": "id", + "required": true, + "type": "integer", + "format": "int64" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "./definitions.json#/User" + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "404": { + "description": "Not Found" + } + } + } + }, + "/user/get/firstName/byEmail/{email}": { + "get": { + "tags": [ + "user-controller" + ], + "summary": "getUserFirstName", + "operationId": "getUserFirstNameUsingGET", + "produces": [ + "*/*" + ], + "parameters": [ + { + "name": "email", + "in": "path", + "description": "email", + "required": true, + "type": "string" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "string" + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "404": { + "description": "Not Found" + } + } + } + }, + "/user/insert": { + "post": { + "tags": [ + "user-controller" + ], + "summary": "createUser", + "operationId": "createUserUsingPOST", + "consumes": [ + "application/json" + ], + "produces": [ + "*/*" + ], + "parameters": [ + { + "in": "body", + "name": "user", + "description": "user", + "required": true, + "schema": { + "$ref": "./definitions.json#/User" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "./definitions.json#/User" + } + }, + "201": { + "description": "Created" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "404": { + "description": "Not Found" + } + } + } + }, + "/user/update": { + "post": { + "tags": [ + "user-controller" + ], + "summary": "updateUser", + "operationId": "updateUserUsingPOST", + "consumes": [ + "application/json" + ], + "produces": [ + "*/*" + ], + "parameters": [ + { + "in": "body", + "name": "user", + "description": "user", + "required": true, + "schema": { + "$ref": "./definitions.json#/User" + } + } + ], + "responses": { + "200": { + "description": "OK" + }, + "201": { + "description": "Created" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "404": { + "description": "Not Found" + } + } + } + }, + "/users/findAll": { + "get": { + "tags": [ + "user-controller" + ], + "summary": "findAllUsers", + "operationId": "findAllUsersUsingGET", + "produces": [ + "*/*" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "array", + "items": { + "$ref": "./definitions.json#/User" + } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "404": { + "description": "Not Found" + } + } + } + } + }, + "definitions" : { + "$ref": "./definitions.json" + } +} \ No newline at end of file diff --git a/test/main_test.go b/test/main_test.go index 7d46aaf67d5..2037e3c40a6 100644 --- a/test/main_test.go +++ b/test/main_test.go @@ -170,7 +170,7 @@ func getFilesMetadatasWithContent(t testing.TB, filePath string, content []byte) files := make(model.FileMetadatas, 0) for _, parser := range combinedParser { - docs, err := parser.Parse(filePath, content) + docs, err := parser.Parse(filePath, content, true) for _, document := range docs.Docs { require.NoError(t, err) files = append(files, model.FileMetadata{ diff --git a/test/queries_test.go b/test/queries_test.go index f4c25f19a90..daebe2ecf33 100644 --- a/test/queries_test.go +++ b/test/queries_test.go @@ -96,7 +96,7 @@ func testRemediationQuery(t testing.TB, entry queryEntry, vulnerabilities []mode for filePath := range temporaryRemediationSets { fix := temporaryRemediationSets[filePath].(remediation.Set) - err = summary.RemediateFile(filePath, fix) + err = summary.RemediateFile(filePath, fix, false) os.Remove(filePath) if err != nil { require.NoError(t, err)