@oculum/scanner 1.0.9 → 1.0.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/baseline/diff.d.ts +32 -0
- package/dist/baseline/diff.d.ts.map +1 -0
- package/dist/baseline/diff.js +119 -0
- package/dist/baseline/diff.js.map +1 -0
- package/dist/baseline/index.d.ts +9 -0
- package/dist/baseline/index.d.ts.map +1 -0
- package/dist/baseline/index.js +19 -0
- package/dist/baseline/index.js.map +1 -0
- package/dist/baseline/manager.d.ts +67 -0
- package/dist/baseline/manager.d.ts.map +1 -0
- package/dist/baseline/manager.js +180 -0
- package/dist/baseline/manager.js.map +1 -0
- package/dist/baseline/types.d.ts +91 -0
- package/dist/baseline/types.d.ts.map +1 -0
- package/dist/baseline/types.js +12 -0
- package/dist/baseline/types.js.map +1 -0
- package/dist/formatters/cli-terminal.d.ts +38 -0
- package/dist/formatters/cli-terminal.d.ts.map +1 -1
- package/dist/formatters/cli-terminal.js +365 -42
- package/dist/formatters/cli-terminal.js.map +1 -1
- package/dist/formatters/github-comment.d.ts +1 -1
- package/dist/formatters/github-comment.d.ts.map +1 -1
- package/dist/formatters/github-comment.js +75 -11
- package/dist/formatters/github-comment.js.map +1 -1
- package/dist/formatters/index.d.ts +1 -1
- package/dist/formatters/index.d.ts.map +1 -1
- package/dist/formatters/index.js +4 -1
- package/dist/formatters/index.js.map +1 -1
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +155 -16
- package/dist/index.js.map +1 -1
- package/dist/layer1/config-audit.d.ts.map +1 -1
- package/dist/layer1/config-audit.js +20 -3
- package/dist/layer1/config-audit.js.map +1 -1
- package/dist/layer1/config-mcp-audit.d.ts +20 -0
- package/dist/layer1/config-mcp-audit.d.ts.map +1 -0
- package/dist/layer1/config-mcp-audit.js +239 -0
- package/dist/layer1/config-mcp-audit.js.map +1 -0
- package/dist/layer1/index.d.ts +1 -0
- package/dist/layer1/index.d.ts.map +1 -1
- package/dist/layer1/index.js +9 -1
- package/dist/layer1/index.js.map +1 -1
- package/dist/layer2/ai-agent-tools.d.ts.map +1 -1
- package/dist/layer2/ai-agent-tools.js +303 -0
- package/dist/layer2/ai-agent-tools.js.map +1 -1
- package/dist/layer2/ai-endpoint-protection.d.ts.map +1 -1
- package/dist/layer2/ai-endpoint-protection.js +17 -3
- package/dist/layer2/ai-endpoint-protection.js.map +1 -1
- package/dist/layer2/ai-execution-sinks.d.ts.map +1 -1
- package/dist/layer2/ai-execution-sinks.js +462 -12
- package/dist/layer2/ai-execution-sinks.js.map +1 -1
- package/dist/layer2/ai-fingerprinting.d.ts.map +1 -1
- package/dist/layer2/ai-fingerprinting.js +3 -0
- package/dist/layer2/ai-fingerprinting.js.map +1 -1
- package/dist/layer2/ai-mcp-security.d.ts +17 -0
- package/dist/layer2/ai-mcp-security.d.ts.map +1 -0
- package/dist/layer2/ai-mcp-security.js +679 -0
- package/dist/layer2/ai-mcp-security.js.map +1 -0
- package/dist/layer2/ai-package-hallucination.d.ts +19 -0
- package/dist/layer2/ai-package-hallucination.d.ts.map +1 -0
- package/dist/layer2/ai-package-hallucination.js +696 -0
- package/dist/layer2/ai-package-hallucination.js.map +1 -0
- package/dist/layer2/ai-prompt-hygiene.d.ts.map +1 -1
- package/dist/layer2/ai-prompt-hygiene.js +495 -9
- package/dist/layer2/ai-prompt-hygiene.js.map +1 -1
- package/dist/layer2/ai-rag-safety.d.ts.map +1 -1
- package/dist/layer2/ai-rag-safety.js +372 -1
- package/dist/layer2/ai-rag-safety.js.map +1 -1
- package/dist/layer2/auth-antipatterns.d.ts.map +1 -1
- package/dist/layer2/auth-antipatterns.js +4 -0
- package/dist/layer2/auth-antipatterns.js.map +1 -1
- package/dist/layer2/byok-patterns.d.ts.map +1 -1
- package/dist/layer2/byok-patterns.js +3 -0
- package/dist/layer2/byok-patterns.js.map +1 -1
- package/dist/layer2/dangerous-functions/child-process.d.ts +16 -0
- package/dist/layer2/dangerous-functions/child-process.d.ts.map +1 -0
- package/dist/layer2/dangerous-functions/child-process.js +74 -0
- package/dist/layer2/dangerous-functions/child-process.js.map +1 -0
- package/dist/layer2/dangerous-functions/dom-xss.d.ts +29 -0
- package/dist/layer2/dangerous-functions/dom-xss.d.ts.map +1 -0
- package/dist/layer2/dangerous-functions/dom-xss.js +179 -0
- package/dist/layer2/dangerous-functions/dom-xss.js.map +1 -0
- package/dist/layer2/dangerous-functions/index.d.ts +13 -0
- package/dist/layer2/dangerous-functions/index.d.ts.map +1 -0
- package/dist/layer2/dangerous-functions/index.js +621 -0
- package/dist/layer2/dangerous-functions/index.js.map +1 -0
- package/dist/layer2/dangerous-functions/json-parse.d.ts +31 -0
- package/dist/layer2/dangerous-functions/json-parse.d.ts.map +1 -0
- package/dist/layer2/dangerous-functions/json-parse.js +319 -0
- package/dist/layer2/dangerous-functions/json-parse.js.map +1 -0
- package/dist/layer2/dangerous-functions/math-random.d.ts +61 -0
- package/dist/layer2/dangerous-functions/math-random.d.ts.map +1 -0
- package/dist/layer2/dangerous-functions/math-random.js +459 -0
- package/dist/layer2/dangerous-functions/math-random.js.map +1 -0
- package/dist/layer2/dangerous-functions/patterns.d.ts +21 -0
- package/dist/layer2/dangerous-functions/patterns.d.ts.map +1 -0
- package/dist/layer2/dangerous-functions/patterns.js +161 -0
- package/dist/layer2/dangerous-functions/patterns.js.map +1 -0
- package/dist/layer2/dangerous-functions/request-validation.d.ts +13 -0
- package/dist/layer2/dangerous-functions/request-validation.d.ts.map +1 -0
- package/dist/layer2/dangerous-functions/request-validation.js +119 -0
- package/dist/layer2/dangerous-functions/request-validation.js.map +1 -0
- package/dist/layer2/dangerous-functions/utils/control-flow.d.ts +23 -0
- package/dist/layer2/dangerous-functions/utils/control-flow.d.ts.map +1 -0
- package/dist/layer2/dangerous-functions/utils/control-flow.js +149 -0
- package/dist/layer2/dangerous-functions/utils/control-flow.js.map +1 -0
- package/dist/layer2/dangerous-functions/utils/helpers.d.ts +31 -0
- package/dist/layer2/dangerous-functions/utils/helpers.d.ts.map +1 -0
- package/dist/layer2/dangerous-functions/utils/helpers.js +124 -0
- package/dist/layer2/dangerous-functions/utils/helpers.js.map +1 -0
- package/dist/layer2/dangerous-functions/utils/index.d.ts +9 -0
- package/dist/layer2/dangerous-functions/utils/index.d.ts.map +1 -0
- package/dist/layer2/dangerous-functions/utils/index.js +23 -0
- package/dist/layer2/dangerous-functions/utils/index.js.map +1 -0
- package/dist/layer2/dangerous-functions/utils/schema-validation.d.ts +22 -0
- package/dist/layer2/dangerous-functions/utils/schema-validation.d.ts.map +1 -0
- package/dist/layer2/dangerous-functions/utils/schema-validation.js +89 -0
- package/dist/layer2/dangerous-functions/utils/schema-validation.js.map +1 -0
- package/dist/layer2/data-exposure.d.ts.map +1 -1
- package/dist/layer2/data-exposure.js +3 -0
- package/dist/layer2/data-exposure.js.map +1 -1
- package/dist/layer2/framework-checks.d.ts.map +1 -1
- package/dist/layer2/framework-checks.js +3 -0
- package/dist/layer2/framework-checks.js.map +1 -1
- package/dist/layer2/index.d.ts +3 -0
- package/dist/layer2/index.d.ts.map +1 -1
- package/dist/layer2/index.js +61 -2
- package/dist/layer2/index.js.map +1 -1
- package/dist/layer2/logic-gates.d.ts.map +1 -1
- package/dist/layer2/logic-gates.js +4 -0
- package/dist/layer2/logic-gates.js.map +1 -1
- package/dist/layer2/model-supply-chain.d.ts +20 -0
- package/dist/layer2/model-supply-chain.d.ts.map +1 -0
- package/dist/layer2/model-supply-chain.js +376 -0
- package/dist/layer2/model-supply-chain.js.map +1 -0
- package/dist/layer2/risky-imports.d.ts.map +1 -1
- package/dist/layer2/risky-imports.js +4 -0
- package/dist/layer2/risky-imports.js.map +1 -1
- package/dist/layer2/variables.d.ts.map +1 -1
- package/dist/layer2/variables.js +4 -0
- package/dist/layer2/variables.js.map +1 -1
- package/dist/layer3/anthropic/auto-dismiss.d.ts +24 -0
- package/dist/layer3/anthropic/auto-dismiss.d.ts.map +1 -0
- package/dist/layer3/anthropic/auto-dismiss.js +188 -0
- package/dist/layer3/anthropic/auto-dismiss.js.map +1 -0
- package/dist/layer3/anthropic/clients.d.ts +44 -0
- package/dist/layer3/anthropic/clients.d.ts.map +1 -0
- package/dist/layer3/anthropic/clients.js +81 -0
- package/dist/layer3/anthropic/clients.js.map +1 -0
- package/dist/layer3/anthropic/index.d.ts +41 -0
- package/dist/layer3/anthropic/index.d.ts.map +1 -0
- package/dist/layer3/anthropic/index.js +141 -0
- package/dist/layer3/anthropic/index.js.map +1 -0
- package/dist/layer3/anthropic/prompts/index.d.ts +8 -0
- package/dist/layer3/anthropic/prompts/index.d.ts.map +1 -0
- package/dist/layer3/anthropic/prompts/index.js +14 -0
- package/dist/layer3/anthropic/prompts/index.js.map +1 -0
- package/dist/layer3/anthropic/prompts/semantic-analysis.d.ts +15 -0
- package/dist/layer3/anthropic/prompts/semantic-analysis.d.ts.map +1 -0
- package/dist/layer3/anthropic/prompts/semantic-analysis.js +169 -0
- package/dist/layer3/anthropic/prompts/semantic-analysis.js.map +1 -0
- package/dist/layer3/anthropic/prompts/validation.d.ts +12 -0
- package/dist/layer3/anthropic/prompts/validation.d.ts.map +1 -0
- package/dist/layer3/anthropic/prompts/validation.js +421 -0
- package/dist/layer3/anthropic/prompts/validation.js.map +1 -0
- package/dist/layer3/anthropic/providers/anthropic.d.ts +21 -0
- package/dist/layer3/anthropic/providers/anthropic.d.ts.map +1 -0
- package/dist/layer3/anthropic/providers/anthropic.js +266 -0
- package/dist/layer3/anthropic/providers/anthropic.js.map +1 -0
- package/dist/layer3/anthropic/providers/index.d.ts +8 -0
- package/dist/layer3/anthropic/providers/index.d.ts.map +1 -0
- package/dist/layer3/anthropic/providers/index.js +15 -0
- package/dist/layer3/anthropic/providers/index.js.map +1 -0
- package/dist/layer3/anthropic/providers/openai.d.ts +18 -0
- package/dist/layer3/anthropic/providers/openai.d.ts.map +1 -0
- package/dist/layer3/anthropic/providers/openai.js +340 -0
- package/dist/layer3/anthropic/providers/openai.js.map +1 -0
- package/dist/layer3/anthropic/request-builder.d.ts +20 -0
- package/dist/layer3/anthropic/request-builder.d.ts.map +1 -0
- package/dist/layer3/anthropic/request-builder.js +134 -0
- package/dist/layer3/anthropic/request-builder.js.map +1 -0
- package/dist/layer3/anthropic/types.d.ts +88 -0
- package/dist/layer3/anthropic/types.d.ts.map +1 -0
- package/dist/layer3/anthropic/types.js +38 -0
- package/dist/layer3/anthropic/types.js.map +1 -0
- package/dist/layer3/anthropic/utils/index.d.ts +9 -0
- package/dist/layer3/anthropic/utils/index.d.ts.map +1 -0
- package/dist/layer3/anthropic/utils/index.js +24 -0
- package/dist/layer3/anthropic/utils/index.js.map +1 -0
- package/dist/layer3/anthropic/utils/path-helpers.d.ts +21 -0
- package/dist/layer3/anthropic/utils/path-helpers.d.ts.map +1 -0
- package/dist/layer3/anthropic/utils/path-helpers.js +69 -0
- package/dist/layer3/anthropic/utils/path-helpers.js.map +1 -0
- package/dist/layer3/anthropic/utils/response-parser.d.ts +40 -0
- package/dist/layer3/anthropic/utils/response-parser.d.ts.map +1 -0
- package/dist/layer3/anthropic/utils/response-parser.js +285 -0
- package/dist/layer3/anthropic/utils/response-parser.js.map +1 -0
- package/dist/layer3/anthropic/utils/retry.d.ts +15 -0
- package/dist/layer3/anthropic/utils/retry.d.ts.map +1 -0
- package/dist/layer3/anthropic/utils/retry.js +62 -0
- package/dist/layer3/anthropic/utils/retry.js.map +1 -0
- package/dist/layer3/index.d.ts +1 -0
- package/dist/layer3/index.d.ts.map +1 -1
- package/dist/layer3/index.js +16 -6
- package/dist/layer3/index.js.map +1 -1
- package/dist/layer3/osv-check.d.ts +75 -0
- package/dist/layer3/osv-check.d.ts.map +1 -0
- package/dist/layer3/osv-check.js +308 -0
- package/dist/layer3/osv-check.js.map +1 -0
- package/dist/rules/framework-fixes.d.ts +48 -0
- package/dist/rules/framework-fixes.d.ts.map +1 -0
- package/dist/rules/framework-fixes.js +439 -0
- package/dist/rules/framework-fixes.js.map +1 -0
- package/dist/rules/index.d.ts +8 -0
- package/dist/rules/index.d.ts.map +1 -0
- package/dist/rules/index.js +18 -0
- package/dist/rules/index.js.map +1 -0
- package/dist/rules/metadata.d.ts +43 -0
- package/dist/rules/metadata.d.ts.map +1 -0
- package/dist/rules/metadata.js +734 -0
- package/dist/rules/metadata.js.map +1 -0
- package/dist/suppression/config-loader.d.ts +74 -0
- package/dist/suppression/config-loader.d.ts.map +1 -0
- package/dist/suppression/config-loader.js +424 -0
- package/dist/suppression/config-loader.js.map +1 -0
- package/dist/suppression/hash.d.ts +48 -0
- package/dist/suppression/hash.d.ts.map +1 -0
- package/dist/suppression/hash.js +88 -0
- package/dist/suppression/hash.js.map +1 -0
- package/dist/suppression/index.d.ts +11 -0
- package/dist/suppression/index.d.ts.map +1 -0
- package/dist/suppression/index.js +39 -0
- package/dist/suppression/index.js.map +1 -0
- package/dist/suppression/inline-parser.d.ts +39 -0
- package/dist/suppression/inline-parser.d.ts.map +1 -0
- package/dist/suppression/inline-parser.js +218 -0
- package/dist/suppression/inline-parser.js.map +1 -0
- package/dist/suppression/manager.d.ts +94 -0
- package/dist/suppression/manager.d.ts.map +1 -0
- package/dist/suppression/manager.js +292 -0
- package/dist/suppression/manager.js.map +1 -0
- package/dist/suppression/types.d.ts +151 -0
- package/dist/suppression/types.d.ts.map +1 -0
- package/dist/suppression/types.js +28 -0
- package/dist/suppression/types.js.map +1 -0
- package/dist/tiers.d.ts +1 -1
- package/dist/tiers.d.ts.map +1 -1
- package/dist/tiers.js +27 -0
- package/dist/tiers.js.map +1 -1
- package/dist/types.d.ts +62 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/utils/context-helpers.d.ts +4 -0
- package/dist/utils/context-helpers.d.ts.map +1 -1
- package/dist/utils/context-helpers.js +13 -9
- package/dist/utils/context-helpers.js.map +1 -1
- package/package.json +4 -2
- package/src/__tests__/benchmark/fixtures/layer1/mcp-config-audit.json +31 -0
- package/src/__tests__/benchmark/fixtures/layer2/ai-execution-sinks.ts +1489 -82
- package/src/__tests__/benchmark/fixtures/layer2/ai-mcp-security.ts +495 -0
- package/src/__tests__/benchmark/fixtures/layer2/ai-package-hallucination.ts +255 -0
- package/src/__tests__/benchmark/fixtures/layer2/ai-prompt-hygiene.ts +300 -1
- package/src/__tests__/benchmark/fixtures/layer2/ai-rag-safety.ts +139 -0
- package/src/__tests__/benchmark/fixtures/layer2/byok-patterns.ts +7 -0
- package/src/__tests__/benchmark/fixtures/layer2/data-exposure.ts +63 -0
- package/src/__tests__/benchmark/fixtures/layer2/excessive-agency.ts +221 -0
- package/src/__tests__/benchmark/fixtures/layer2/index.ts +18 -0
- package/src/__tests__/benchmark/fixtures/layer2/model-supply-chain.ts +204 -0
- package/src/__tests__/benchmark/fixtures/layer2/phase1-enhancements.ts +157 -0
- package/src/__tests__/snapshots/__snapshots__/anthropic-validation-refactor.test.ts.snap +758 -0
- package/src/__tests__/snapshots/__snapshots__/dangerous-functions-refactor.test.ts.snap +503 -0
- package/src/__tests__/snapshots/anthropic-validation-refactor.test.ts +321 -0
- package/src/__tests__/snapshots/dangerous-functions-refactor.test.ts +439 -0
- package/src/baseline/__tests__/diff.test.ts +261 -0
- package/src/baseline/__tests__/manager.test.ts +225 -0
- package/src/baseline/diff.ts +135 -0
- package/src/baseline/index.ts +29 -0
- package/src/baseline/manager.ts +230 -0
- package/src/baseline/types.ts +97 -0
- package/src/formatters/cli-terminal.ts +444 -41
- package/src/formatters/github-comment.ts +79 -11
- package/src/formatters/index.ts +4 -0
- package/src/index.ts +197 -14
- package/src/layer1/config-audit.ts +24 -3
- package/src/layer1/config-mcp-audit.ts +276 -0
- package/src/layer1/index.ts +16 -6
- package/src/layer2/ai-agent-tools.ts +336 -0
- package/src/layer2/ai-endpoint-protection.ts +16 -3
- package/src/layer2/ai-execution-sinks.ts +516 -12
- package/src/layer2/ai-fingerprinting.ts +5 -1
- package/src/layer2/ai-mcp-security.ts +730 -0
- package/src/layer2/ai-package-hallucination.ts +791 -0
- package/src/layer2/ai-prompt-hygiene.ts +547 -9
- package/src/layer2/ai-rag-safety.ts +382 -3
- package/src/layer2/auth-antipatterns.ts +5 -0
- package/src/layer2/byok-patterns.ts +5 -1
- package/src/layer2/dangerous-functions/child-process.ts +98 -0
- package/src/layer2/dangerous-functions/dom-xss.ts +220 -0
- package/src/layer2/dangerous-functions/index.ts +949 -0
- package/src/layer2/dangerous-functions/json-parse.ts +385 -0
- package/src/layer2/dangerous-functions/math-random.ts +537 -0
- package/src/layer2/dangerous-functions/patterns.ts +174 -0
- package/src/layer2/dangerous-functions/request-validation.ts +145 -0
- package/src/layer2/dangerous-functions/utils/control-flow.ts +162 -0
- package/src/layer2/dangerous-functions/utils/helpers.ts +170 -0
- package/src/layer2/dangerous-functions/utils/index.ts +25 -0
- package/src/layer2/dangerous-functions/utils/schema-validation.ts +91 -0
- package/src/layer2/data-exposure.ts +5 -1
- package/src/layer2/framework-checks.ts +5 -0
- package/src/layer2/index.ts +63 -1
- package/src/layer2/logic-gates.ts +5 -0
- package/src/layer2/model-supply-chain.ts +456 -0
- package/src/layer2/risky-imports.ts +5 -0
- package/src/layer2/variables.ts +5 -0
- package/src/layer3/__tests__/osv-check.test.ts +384 -0
- package/src/layer3/anthropic/auto-dismiss.ts +212 -0
- package/src/layer3/anthropic/clients.ts +84 -0
- package/src/layer3/anthropic/index.ts +170 -0
- package/src/layer3/anthropic/prompts/index.ts +14 -0
- package/src/layer3/anthropic/prompts/semantic-analysis.ts +173 -0
- package/src/layer3/anthropic/prompts/validation.ts +419 -0
- package/src/layer3/anthropic/providers/anthropic.ts +310 -0
- package/src/layer3/anthropic/providers/index.ts +8 -0
- package/src/layer3/anthropic/providers/openai.ts +384 -0
- package/src/layer3/anthropic/request-builder.ts +150 -0
- package/src/layer3/anthropic/types.ts +148 -0
- package/src/layer3/anthropic/utils/index.ts +26 -0
- package/src/layer3/anthropic/utils/path-helpers.ts +68 -0
- package/src/layer3/anthropic/utils/response-parser.ts +322 -0
- package/src/layer3/anthropic/utils/retry.ts +75 -0
- package/src/layer3/index.ts +18 -5
- package/src/layer3/osv-check.ts +420 -0
- package/src/rules/__tests__/framework-fixes.test.ts +689 -0
- package/src/rules/__tests__/metadata.test.ts +218 -0
- package/src/rules/framework-fixes.ts +470 -0
- package/src/rules/index.ts +21 -0
- package/src/rules/metadata.ts +831 -0
- package/src/suppression/__tests__/config-loader.test.ts +382 -0
- package/src/suppression/__tests__/hash.test.ts +166 -0
- package/src/suppression/__tests__/inline-parser.test.ts +212 -0
- package/src/suppression/__tests__/manager.test.ts +415 -0
- package/src/suppression/config-loader.ts +462 -0
- package/src/suppression/hash.ts +95 -0
- package/src/suppression/index.ts +51 -0
- package/src/suppression/inline-parser.ts +273 -0
- package/src/suppression/manager.ts +379 -0
- package/src/suppression/types.ts +174 -0
- package/src/tiers.ts +36 -0
- package/src/types.ts +90 -0
- package/src/utils/context-helpers.ts +13 -9
- package/dist/layer2/dangerous-functions.d.ts +0 -7
- package/dist/layer2/dangerous-functions.d.ts.map +0 -1
- package/dist/layer2/dangerous-functions.js +0 -1701
- package/dist/layer2/dangerous-functions.js.map +0 -1
- package/dist/layer3/anthropic.d.ts +0 -87
- package/dist/layer3/anthropic.d.ts.map +0 -1
- package/dist/layer3/anthropic.js +0 -1948
- package/dist/layer3/anthropic.js.map +0 -1
- package/dist/layer3/openai.d.ts +0 -25
- package/dist/layer3/openai.d.ts.map +0 -1
- package/dist/layer3/openai.js +0 -238
- package/dist/layer3/openai.js.map +0 -1
- package/src/layer2/dangerous-functions.ts +0 -1940
- package/src/layer3/anthropic.ts +0 -2257
|
@@ -54,21 +54,19 @@ function isUITemplateSuggestion(lineContent: string, surroundingContext: string)
|
|
|
54
54
|
const fullContext = lineContent + '\n' + surroundingContext
|
|
55
55
|
|
|
56
56
|
// UI suggestion object patterns (command palette, autocomplete suggestions)
|
|
57
|
+
// Note: Be careful not to match variable declarations like `const completion =`
|
|
57
58
|
const uiSuggestionPatterns = [
|
|
58
|
-
// Object property patterns for suggestion items
|
|
59
|
-
/(?:id|key|label|title|name|description|
|
|
60
|
-
// Common suggestion UI patterns
|
|
61
|
-
/suggestions
|
|
62
|
-
/completions?\s*[=:]/i,
|
|
59
|
+
// Object property patterns for suggestion items (key: value in objects)
|
|
60
|
+
/(?:id|key|label|title|name|description|display|text|value|placeholder):\s*`[^`]*\$\{/i,
|
|
61
|
+
// Common suggestion UI patterns (arrays or objects, not variable declarations)
|
|
62
|
+
/(?:set)?suggestions\s*[=:]\s*\[/i, // suggestions: [...] or setSuggestions([])
|
|
63
63
|
/autocomplete/i,
|
|
64
64
|
/command\s*palette/i,
|
|
65
65
|
/fuzzy\s*search/i,
|
|
66
66
|
/search\s*result/i,
|
|
67
|
-
// UI component context patterns
|
|
68
|
-
/\.map\s*\(\s*\(?(?:item|result|suggestion|node|entry)/i,
|
|
69
|
-
/\.filter\s*\(/i,
|
|
70
67
|
// React/UI state patterns
|
|
71
|
-
/useState|
|
|
68
|
+
/useState.*suggestions|setSuggestions/i,
|
|
69
|
+
/setItems|setResults/i,
|
|
72
70
|
// Template ID generation for UI
|
|
73
71
|
/id:\s*`[a-z]+-\$\{/i, // id: `delete-${...}`, id: `edit-${...}`
|
|
74
72
|
]
|
|
@@ -87,6 +85,12 @@ function isUITemplateSuggestion(lineContent: string, surroundingContext: string)
|
|
|
87
85
|
/exec\s*\(/i,
|
|
88
86
|
/spawn\s*\(/i,
|
|
89
87
|
/eval\s*\(/i,
|
|
88
|
+
/fetch\s*\(/i,
|
|
89
|
+
/axios\./i,
|
|
90
|
+
/\.redirect\s*\(/i,
|
|
91
|
+
/\.setHeader\s*\(/i,
|
|
92
|
+
/\.cookie\s*\(/i,
|
|
93
|
+
/location\./i,
|
|
90
94
|
]
|
|
91
95
|
|
|
92
96
|
// Check if context matches UI pattern but NOT execution pattern
|
|
@@ -174,7 +178,7 @@ function hasOutputValidation(content: string, lineNumber: number): boolean {
|
|
|
174
178
|
/validate/i,
|
|
175
179
|
/sanitize/i,
|
|
176
180
|
/escape/i,
|
|
177
|
-
|
|
181
|
+
/\.filter\s*\([^)]*(?:allowed|safe|valid)/i, // .filter(x => allowed.includes(x))
|
|
178
182
|
/parse.*catch/i,
|
|
179
183
|
/schema\./i,
|
|
180
184
|
/\.parse\s*\(/i,
|
|
@@ -182,9 +186,19 @@ function hasOutputValidation(content: string, lineNumber: number): boolean {
|
|
|
182
186
|
/whitelist/i,
|
|
183
187
|
/blocklist/i,
|
|
184
188
|
/blacklist/i,
|
|
189
|
+
/allowed(?:Columns|Tables|Hosts|Domains|Extensions|Types|Args|Paths)/i, // Allowlist variable names
|
|
185
190
|
/JSON\.parse.*catch/i,
|
|
186
191
|
/DOMPurify/i,
|
|
187
192
|
/xss/i,
|
|
193
|
+
/encodeURIComponent/i,
|
|
194
|
+
/\.replace\s*\(\s*\/\[.*\]\/[gi]*/i, // Regex sanitization like .replace(/[^a-z0-9]/gi, '')
|
|
195
|
+
/textContent\s*=/i, // Using textContent (safe) instead of innerHTML
|
|
196
|
+
/ReactMarkdown/i, // React Markdown sanitizes by default
|
|
197
|
+
/ast\.literal_eval/i, // Python safe eval
|
|
198
|
+
/yaml\.(?:safe_load|SafeLoader)/i, // Safe YAML parsing
|
|
199
|
+
/\.startsWith\s*\(\s*['"]\/['"]?\)/i, // Relative URL check
|
|
200
|
+
/new\s+URL\s*\(.*\).*origin/i, // URL origin check
|
|
201
|
+
/path\.resolve.*startsWith/i, // Path validation
|
|
188
202
|
]
|
|
189
203
|
|
|
190
204
|
return validationPatterns.some(p => p.test(context))
|
|
@@ -325,12 +339,28 @@ const EXECUTION_SINK_PATTERNS: ExecutionSinkPattern[] = [
|
|
|
325
339
|
// ========== Template/DOM Sinks ==========
|
|
326
340
|
{
|
|
327
341
|
name: 'LLM output to innerHTML',
|
|
328
|
-
pattern: /\.innerHTML\s*=\s*(?:response|result|output|completion|message|content)(?:\.|\.data\.|\.text|\.content)?/gi,
|
|
342
|
+
pattern: /\.innerHTML\s*=\s*(?:response|result|output|completion|message|content|generated)(?:\.|\.data\.|\.text|\.content)?/gi,
|
|
329
343
|
sinkType: 'template_render',
|
|
330
344
|
baseSeverity: 'high',
|
|
331
345
|
description: 'LLM output assigned to innerHTML. If the model outputs malicious HTML/JS, it will execute (XSS).',
|
|
332
346
|
suggestedFix: 'Use textContent for plain text. Sanitize HTML with DOMPurify before rendering. Use React/Vue which auto-escape by default.',
|
|
333
347
|
},
|
|
348
|
+
{
|
|
349
|
+
name: 'LLM output to outerHTML',
|
|
350
|
+
pattern: /\.outerHTML\s*=\s*(?:response|result|output|completion|message|content|generated)(?:\.|\.data\.|\.text|\.content)?/gi,
|
|
351
|
+
sinkType: 'template_render',
|
|
352
|
+
baseSeverity: 'high',
|
|
353
|
+
description: 'LLM output assigned to outerHTML. This replaces the entire element and allows XSS.',
|
|
354
|
+
suggestedFix: 'Use textContent for plain text. Sanitize HTML with DOMPurify before rendering.',
|
|
355
|
+
},
|
|
356
|
+
{
|
|
357
|
+
name: 'LLM output to insertAdjacentHTML',
|
|
358
|
+
pattern: /\.insertAdjacentHTML\s*\([^,]+,\s*(?:response|result|output|completion|message|content|generated)/gi,
|
|
359
|
+
sinkType: 'template_render',
|
|
360
|
+
baseSeverity: 'high',
|
|
361
|
+
description: 'LLM output passed to insertAdjacentHTML. This allows XSS via injected HTML/JS.',
|
|
362
|
+
suggestedFix: 'Use insertAdjacentText for plain text. Sanitize HTML with DOMPurify: el.insertAdjacentHTML("beforeend", DOMPurify.sanitize(content))',
|
|
363
|
+
},
|
|
334
364
|
{
|
|
335
365
|
name: 'LLM output to dangerouslySetInnerHTML',
|
|
336
366
|
pattern: /dangerouslySetInnerHTML\s*=\s*\{\s*\{\s*__html:\s*(?:response|result|output|completion|message|content)/gi,
|
|
@@ -399,8 +429,405 @@ const EXECUTION_SINK_PATTERNS: ExecutionSinkPattern[] = [
|
|
|
399
429
|
description: 'AI output used in module path resolution. Could leak information about file system or enable module confusion attacks.',
|
|
400
430
|
suggestedFix: 'Validate module name against allowlist before resolution.',
|
|
401
431
|
},
|
|
432
|
+
|
|
433
|
+
// ========== Phase 2: Network/SSRF Sinks ==========
|
|
434
|
+
{
|
|
435
|
+
name: 'LLM output in fetch URL',
|
|
436
|
+
pattern: /fetch\s*\(\s*(?:response|result|output|completion|aiUrl|generatedUrl|urlFromAi)(?:\.choices\[0\]\.message\.content|\.content|\.text)?/gi,
|
|
437
|
+
sinkType: 'code_execution', // SSRF is code-level risk
|
|
438
|
+
baseSeverity: 'critical',
|
|
439
|
+
description: 'AI-generated URL passed to fetch(). Attackers can manipulate the model to make requests to internal services (SSRF), exfiltrate data, or access localhost services.',
|
|
440
|
+
suggestedFix: 'Validate URL against allowlist: const allowed = ["api.example.com"]; if (!allowed.includes(new URL(url).host)) throw. Block private IP ranges.',
|
|
441
|
+
},
|
|
442
|
+
{
|
|
443
|
+
name: 'LLM output in axios request',
|
|
444
|
+
pattern: /axios\.(?:get|post|put|delete|patch|request)\s*\(\s*(?:response|result|output|completion|aiUrl|generatedUrl)(?:\.choices\[0\]\.message\.content|\.content|\.text)?/gi,
|
|
445
|
+
sinkType: 'code_execution',
|
|
446
|
+
baseSeverity: 'critical',
|
|
447
|
+
description: 'AI-generated URL passed to axios. This enables SSRF attacks where the model is manipulated to make requests to internal services.',
|
|
448
|
+
suggestedFix: 'Validate URL host against allowlist. Use axios interceptors to block private IPs and internal hosts.',
|
|
449
|
+
},
|
|
450
|
+
{
|
|
451
|
+
name: 'LLM output in axios config',
|
|
452
|
+
pattern: /axios\s*\(\s*\{[^}]*url:\s*(?:response|result|output|completion|aiUrl|generatedUrl)/gi,
|
|
453
|
+
sinkType: 'code_execution',
|
|
454
|
+
baseSeverity: 'critical',
|
|
455
|
+
description: 'AI-generated URL passed to axios via config object. SSRF risk.',
|
|
456
|
+
suggestedFix: 'Validate URL host against allowlist before passing to axios.',
|
|
457
|
+
},
|
|
458
|
+
{
|
|
459
|
+
name: 'LLM output in HTTP client',
|
|
460
|
+
pattern: /(?:got|request|superagent|ky|undici\.fetch)\s*\(\s*(?:response|result|output|completion|aiUrl)(?:\.choices\[0\]\.message\.content|\.content|\.text)?/gi,
|
|
461
|
+
sinkType: 'code_execution',
|
|
462
|
+
baseSeverity: 'critical',
|
|
463
|
+
description: 'AI-generated URL passed to HTTP client. Server-Side Request Forgery (SSRF) risk.',
|
|
464
|
+
suggestedFix: 'Validate URLs against allowlist of permitted hosts. Block internal IP ranges (10.x, 172.16-31.x, 192.168.x, 127.x, localhost).',
|
|
465
|
+
},
|
|
466
|
+
|
|
467
|
+
// ========== Phase 2: Redirect Sinks ==========
|
|
468
|
+
{
|
|
469
|
+
name: 'LLM output in server redirect',
|
|
470
|
+
pattern: /(?:res|response)\.redirect\s*\(\s*(?:response|result|output|completion|aiUrl|generatedUrl)(?:\.choices\[0\]\.message\.content|\.content|\.text)?/gi,
|
|
471
|
+
sinkType: 'template_render', // Open redirect is similar to XSS
|
|
472
|
+
baseSeverity: 'high',
|
|
473
|
+
description: 'AI-generated URL used in HTTP redirect. Attackers can craft prompts to redirect users to phishing sites or malicious pages.',
|
|
474
|
+
suggestedFix: 'Validate redirect URL against allowlist. Only allow redirects to same-origin or known safe domains. Use relative URLs where possible.',
|
|
475
|
+
},
|
|
476
|
+
{
|
|
477
|
+
name: 'LLM output in client redirect assignment',
|
|
478
|
+
pattern: /(?:window\.)?location\.href\s*=\s*(?:response|result|output|completion|aiUrl|generatedUrl)(?:\.choices\[0\]\.message\.content|\.content|\.text)?/gi,
|
|
479
|
+
sinkType: 'template_render',
|
|
480
|
+
baseSeverity: 'high',
|
|
481
|
+
description: 'AI-generated URL assigned to location.href. Enables open redirect attacks.',
|
|
482
|
+
suggestedFix: 'Validate URL before assignment. Prefer relative URLs or validate against allowlist: if (!url.startsWith("/") && !allowedHosts.includes(new URL(url).host)) throw',
|
|
483
|
+
},
|
|
484
|
+
{
|
|
485
|
+
name: 'LLM output in location.assign',
|
|
486
|
+
pattern: /location\.assign\s*\(\s*(?:response|result|output|completion|aiUrl|generatedUrl)(?:\.choices\[0\]\.message\.content|\.content|\.text)?/gi,
|
|
487
|
+
sinkType: 'template_render',
|
|
488
|
+
baseSeverity: 'high',
|
|
489
|
+
description: 'AI-generated URL passed to location.assign(). Enables open redirect attacks.',
|
|
490
|
+
suggestedFix: 'Validate URL before assignment. Only allow same-origin or allowlisted domains.',
|
|
491
|
+
},
|
|
492
|
+
{
|
|
493
|
+
name: 'LLM output in location.replace',
|
|
494
|
+
pattern: /location\.replace\s*\(\s*(?:response|result|output|completion|aiUrl|generatedUrl)(?:\.choices\[0\]\.message\.content|\.content|\.text)?/gi,
|
|
495
|
+
sinkType: 'template_render',
|
|
496
|
+
baseSeverity: 'high',
|
|
497
|
+
description: 'AI-generated URL passed to location.replace(). Enables open redirect attacks.',
|
|
498
|
+
suggestedFix: 'Validate URL before assignment. Only allow same-origin or allowlisted domains.',
|
|
499
|
+
},
|
|
500
|
+
{
|
|
501
|
+
name: 'LLM output in Next.js redirect',
|
|
502
|
+
pattern: /redirect\s*\(\s*(?:response|result|output|completion|aiUrl|generatedUrl)(?:\.choices\[0\]\.message\.content|\.content|\.text)?/gi,
|
|
503
|
+
sinkType: 'template_render',
|
|
504
|
+
baseSeverity: 'high',
|
|
505
|
+
description: 'AI-generated URL passed to Next.js redirect(). Enables open redirect attacks.',
|
|
506
|
+
suggestedFix: 'Validate URL before redirect. Only allow relative URLs or allowlisted domains.',
|
|
507
|
+
},
|
|
508
|
+
{
|
|
509
|
+
name: 'LLM output in meta refresh',
|
|
510
|
+
pattern: /<meta[^>]*http-equiv\s*=\s*['"`]refresh['"`][^>]*content\s*=\s*['"`][^'"]*url\s*=\s*(?:\$\{|<%=).*(?:response|output|completion)/gi,
|
|
511
|
+
sinkType: 'template_render',
|
|
512
|
+
baseSeverity: 'high',
|
|
513
|
+
description: 'AI-generated URL in meta refresh tag. Open redirect vulnerability.',
|
|
514
|
+
suggestedFix: 'Avoid meta refresh with dynamic URLs. Use server-side redirects with URL validation instead.',
|
|
515
|
+
},
|
|
516
|
+
|
|
517
|
+
// ========== Phase 2: Header Injection Sinks ==========
|
|
518
|
+
{
|
|
519
|
+
name: 'LLM output in response header',
|
|
520
|
+
pattern: /(?:res|response)\.(?:setHeader|set|header)\s*\(\s*['"][^'"]+['"]\s*,\s*(?:response|result|output|completion|aiValue)(?:\.choices\[0\]\.message\.content|\.content|\.text)?/gi,
|
|
521
|
+
sinkType: 'template_render',
|
|
522
|
+
baseSeverity: 'high',
|
|
523
|
+
description: 'AI-generated value used in HTTP response header. Enables header injection attacks (CRLF injection, cache poisoning).',
|
|
524
|
+
suggestedFix: 'Sanitize header values: remove CR/LF characters. Validate against expected format. Never use AI output directly in security-sensitive headers (Set-Cookie, Authorization).',
|
|
525
|
+
},
|
|
526
|
+
{
|
|
527
|
+
name: 'LLM output in cookie',
|
|
528
|
+
pattern: /(?:res|response)\.(?:cookie|setCookie)\s*\(\s*['"][^'"]+['"]\s*,\s*(?:response|result|output|completion)(?:\.choices\[0\]\.message\.content|\.content|\.text)?/gi,
|
|
529
|
+
sinkType: 'template_render',
|
|
530
|
+
baseSeverity: 'high',
|
|
531
|
+
description: 'AI-generated value set as cookie. Could enable session fixation or cookie injection attacks.',
|
|
532
|
+
suggestedFix: 'Never use AI output for cookie values. Generate tokens server-side with crypto.randomBytes(). Validate any user-facing values.',
|
|
533
|
+
},
|
|
534
|
+
{
|
|
535
|
+
name: 'LLM output in res.type',
|
|
536
|
+
pattern: /(?:res|response)\.type\s*\(\s*(?:response|result|output|completion)(?:\.choices\[0\]\.message\.content|\.content|\.text)?/gi,
|
|
537
|
+
sinkType: 'template_render',
|
|
538
|
+
baseSeverity: 'high',
|
|
539
|
+
description: 'AI-generated value used to set Content-Type. Could enable MIME confusion attacks.',
|
|
540
|
+
suggestedFix: 'Use allowlist for content types: const allowed = ["json", "html", "text"]; if (!allowed.includes(type)) throw',
|
|
541
|
+
},
|
|
542
|
+
|
|
543
|
+
// ========== Phase 3: Additional Code Execution Sinks ==========
|
|
544
|
+
{
|
|
545
|
+
name: 'LLM output to setTimeout/setInterval string',
|
|
546
|
+
pattern: /(?:setTimeout|setInterval)\s*\(\s*(?:response|result|output|completion)(?:\.choices\[0\]\.message\.content|\.content|\.text)?/gi,
|
|
547
|
+
sinkType: 'code_execution',
|
|
548
|
+
baseSeverity: 'high',
|
|
549
|
+
description: 'AI-generated string passed to setTimeout/setInterval. When passed a string, these functions act like eval().',
|
|
550
|
+
suggestedFix: 'Never pass strings to setTimeout/setInterval. Use arrow functions: setTimeout(() => doSomething(), 1000)',
|
|
551
|
+
},
|
|
552
|
+
{
|
|
553
|
+
name: 'LLM output to globalThis.eval',
|
|
554
|
+
pattern: /(?:globalThis|window)\[?['"]?eval['"]?\]?\s*\(\s*(?:response|result|output|completion)(?:\.choices\[0\]\.message\.content|\.content|\.text)?/gi,
|
|
555
|
+
sinkType: 'code_execution',
|
|
556
|
+
baseSeverity: 'critical',
|
|
557
|
+
description: 'AI-generated code passed to eval via globalThis/window. This is indirect eval() that enables arbitrary code execution.',
|
|
558
|
+
suggestedFix: 'Never eval() LLM output. Use structured output and validation.',
|
|
559
|
+
},
|
|
560
|
+
{
|
|
561
|
+
name: 'LLM output to execa',
|
|
562
|
+
pattern: /execa\s*\(\s*(?:response|result|output|completion)(?:\.choices\[0\]\.message\.content|\.content|\.text)?/gi,
|
|
563
|
+
sinkType: 'shell_command',
|
|
564
|
+
baseSeverity: 'critical',
|
|
565
|
+
description: 'AI-generated command passed to execa. This enables command injection attacks.',
|
|
566
|
+
suggestedFix: 'Never pass LLM output directly to shell. Use allowlists for permitted commands.',
|
|
567
|
+
},
|
|
568
|
+
|
|
569
|
+
// ========== Phase 3: Python-Specific Sinks ==========
|
|
570
|
+
{
|
|
571
|
+
name: 'LLM output to Python eval',
|
|
572
|
+
pattern: /eval\s*\(\s*(?:response|result|output|completion|code)(?:\[['"]?choices['"]?\]\[0\]\[['"]?message['"]?\]\[['"]?content['"]?\]|\.content|\.text)?/gi,
|
|
573
|
+
sinkType: 'code_execution',
|
|
574
|
+
baseSeverity: 'critical',
|
|
575
|
+
description: 'AI-generated code passed to Python eval(). Enables arbitrary code execution.',
|
|
576
|
+
suggestedFix: 'Never eval() LLM output. Use ast.literal_eval() for safe literal evaluation, or JSON parsing with schema validation.',
|
|
577
|
+
},
|
|
578
|
+
{
|
|
579
|
+
name: 'LLM output to Python exec',
|
|
580
|
+
pattern: /exec\s*\(\s*(?:response|result|output|completion)(?:\[['"]?choices['"]?\]\[0\]\[['"]?message['"]?\]\[['"]?content['"]?\]|\.content|\.text)?/gi,
|
|
581
|
+
sinkType: 'code_execution',
|
|
582
|
+
baseSeverity: 'critical',
|
|
583
|
+
description: 'AI-generated code passed to Python exec(). Enables arbitrary code execution.',
|
|
584
|
+
suggestedFix: 'Never exec() LLM output. Use structured output and validation instead.',
|
|
585
|
+
},
|
|
586
|
+
{
|
|
587
|
+
name: 'LLM output to pickle.loads',
|
|
588
|
+
pattern: /pickle\.loads?\s*\(\s*(?:response|result|output|completion|serialized)(?:\.encode\(\)|\.content|\.text)?/gi,
|
|
589
|
+
sinkType: 'code_execution',
|
|
590
|
+
baseSeverity: 'critical',
|
|
591
|
+
description: 'AI-generated data passed to pickle.loads(). Pickle deserialization can execute arbitrary code.',
|
|
592
|
+
suggestedFix: 'Never unpickle untrusted data. Use JSON or other safe serialization formats.',
|
|
593
|
+
},
|
|
594
|
+
{
|
|
595
|
+
name: 'LLM output to subprocess with shell=True',
|
|
596
|
+
pattern: /subprocess\.(?:run|call|Popen)\s*\(\s*(?:response|result|output|completion|ai_command|generated_cmd)(?:\.content|\.text)?[^)]*shell\s*=\s*True/gi,
|
|
597
|
+
sinkType: 'shell_command',
|
|
598
|
+
baseSeverity: 'critical',
|
|
599
|
+
description: 'AI-generated command passed to subprocess with shell=True. Enables command injection.',
|
|
600
|
+
suggestedFix: 'Never use shell=True with user/AI input. Use subprocess.run(["cmd", "arg1", "arg2"]) without shell.',
|
|
601
|
+
},
|
|
602
|
+
{
|
|
603
|
+
name: 'LLM output to os.system',
|
|
604
|
+
pattern: /os\.system\s*\(\s*(?:response|result|output|completion|generated_cmd|ai_command)(?:\.content|\.text)?/gi,
|
|
605
|
+
sinkType: 'shell_command',
|
|
606
|
+
baseSeverity: 'critical',
|
|
607
|
+
description: 'AI-generated command passed to os.system(). Enables command injection.',
|
|
608
|
+
suggestedFix: 'Use subprocess.run() with list arguments instead of os.system(). Never pass AI output to shell.',
|
|
609
|
+
},
|
|
610
|
+
{
|
|
611
|
+
name: 'Python SQL f-string injection',
|
|
612
|
+
pattern: /cursor\.execute\s*\(\s*f["'].*\{.*(?:response|result|output|completion)/gi,
|
|
613
|
+
sinkType: 'sql_builder',
|
|
614
|
+
baseSeverity: 'critical',
|
|
615
|
+
description: 'AI-generated value interpolated into SQL query via f-string. Enables SQL injection.',
|
|
616
|
+
suggestedFix: 'Use parameterized queries: cursor.execute("SELECT * FROM users WHERE id = ?", [user_id])',
|
|
617
|
+
},
|
|
402
618
|
]
|
|
403
619
|
|
|
620
|
+
// ============================================================================
|
|
621
|
+
// Phase 2: URL/Network Validation Detection
|
|
622
|
+
// ============================================================================
|
|
623
|
+
|
|
624
|
+
/**
|
|
625
|
+
* Check if URL validation is present (returns 'strong', 'weak', or 'none')
|
|
626
|
+
* Strong validation = skip finding entirely
|
|
627
|
+
* Weak validation = downgrade severity
|
|
628
|
+
*/
|
|
629
|
+
function getURLValidationLevel(content: string, lineNumber: number): 'strong' | 'weak' | 'none' {
|
|
630
|
+
const lines = content.split('\n')
|
|
631
|
+
const contextStart = Math.max(0, lineNumber - 15)
|
|
632
|
+
const contextEnd = Math.min(lines.length, lineNumber + 5)
|
|
633
|
+
const context = lines.slice(contextStart, contextEnd).join('\n')
|
|
634
|
+
|
|
635
|
+
// Strong validation - skip entirely
|
|
636
|
+
const strongValidationPatterns = [
|
|
637
|
+
/allowedHosts\.includes\s*\(\s*(?:new\s+URL)?/i, // Explicit allowlist check
|
|
638
|
+
/safeDomains\.includes\s*\(/i, // Safe domain allowlist
|
|
639
|
+
/allowedDomains\.includes\s*\(/i, // Allowed domain check
|
|
640
|
+
/if\s*\(\s*allowedHosts/i, // Conditional on allowlist
|
|
641
|
+
/if\s*\(\s*safeDomains/i, // Conditional on safe domains
|
|
642
|
+
/url\.origin\s*===\s*(?:window\.)?(?:location\.)?origin/i, // Same-origin check
|
|
643
|
+
/\.origin\s*===\s*origin/i, // Same-origin check
|
|
644
|
+
/\.startsWith\s*\(\s*['"]\/['"]?\s*\)\s*&&\s*!\s*\w+\.startsWith\s*\(\s*['"]\/\//i, // Relative URL with protocol-relative check
|
|
645
|
+
/if\s*\(\s*\w+\.startsWith\s*\(\s*['"]\/['"]?\s*\)\s*&&\s*!/i, // Relative URL validation
|
|
646
|
+
/blockedHosts\.includes\s*\(/i, // Block list check
|
|
647
|
+
/privateIpPatterns\.some\s*\(/i, // Private IP blocking
|
|
648
|
+
]
|
|
649
|
+
|
|
650
|
+
if (strongValidationPatterns.some(p => p.test(context))) {
|
|
651
|
+
return 'strong'
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
// Weak validation - downgrade severity
|
|
655
|
+
const weakValidationPatterns = [
|
|
656
|
+
/isValidUrl|validateUrl|isAllowedUrl/i,
|
|
657
|
+
/new\s+URL\s*\(.*\).*(?:host|hostname|origin)/i,
|
|
658
|
+
/allowedUrls|allowedHosts|allowedDomains|safeDomains/i,
|
|
659
|
+
/url\.startsWith\s*\(\s*['"`](?:https?:\/\/|\/[^\/])/i,
|
|
660
|
+
/sanitizeUrl|encodeURIComponent/i,
|
|
661
|
+
/blockedHosts|blockedDomains|privateIp/i,
|
|
662
|
+
/\.includes\s*\(\s*(?:new\s+URL\s*\()?.*\.host/i,
|
|
663
|
+
]
|
|
664
|
+
|
|
665
|
+
if (weakValidationPatterns.some(p => p.test(context))) {
|
|
666
|
+
return 'weak'
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
return 'none'
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
/**
|
|
673
|
+
* Legacy function for backward compatibility
|
|
674
|
+
*/
|
|
675
|
+
function hasURLValidation(content: string, lineNumber: number): boolean {
|
|
676
|
+
return getURLValidationLevel(content, lineNumber) !== 'none'
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
/**
|
|
680
|
+
* Check if DOM content is sanitized (e.g., DOMPurify)
|
|
681
|
+
*/
|
|
682
|
+
function isDOMSanitized(lineContent: string, surroundingContext: string): boolean {
|
|
683
|
+
const fullContext = lineContent + '\n' + surroundingContext
|
|
684
|
+
|
|
685
|
+
const sanitizationPatterns = [
|
|
686
|
+
/DOMPurify\.sanitize\s*\(/i,
|
|
687
|
+
/sanitizeHtml\s*\(/i,
|
|
688
|
+
/xss\s*\(/i,
|
|
689
|
+
/escapeHtml\s*\(/i,
|
|
690
|
+
/textContent\s*=/i, // textContent is safe
|
|
691
|
+
/innerText\s*=/i, // innerText is safe
|
|
692
|
+
/ReactMarkdown/i, // ReactMarkdown sanitizes by default
|
|
693
|
+
/<ReactMarkdown>/i, // JSX ReactMarkdown
|
|
694
|
+
]
|
|
695
|
+
|
|
696
|
+
return sanitizationPatterns.some(p => p.test(fullContext))
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
/**
|
|
700
|
+
* Check if file path is properly validated
|
|
701
|
+
*/
|
|
702
|
+
function isPathValidated(content: string, lineNumber: number): boolean {
|
|
703
|
+
const lines = content.split('\n')
|
|
704
|
+
const contextStart = Math.max(0, lineNumber - 15)
|
|
705
|
+
const contextEnd = Math.min(lines.length, lineNumber + 5)
|
|
706
|
+
const context = lines.slice(contextStart, contextEnd).join('\n')
|
|
707
|
+
|
|
708
|
+
const pathValidationPatterns = [
|
|
709
|
+
/path\.resolve\s*\([^)]*\).*startsWith/i, // Resolved path + startsWith check
|
|
710
|
+
/resolved\.startsWith\s*\(/i, // Common pattern: resolved.startsWith(baseDir)
|
|
711
|
+
/!.*startsWith.*throw/i, // Validation with throw on failure
|
|
712
|
+
/if\s*\(\s*!?\s*resolved\.startsWith/i, // Conditional path check
|
|
713
|
+
/allowedExtensions\.includes\s*\(/i, // Extension allowlist
|
|
714
|
+
/allowedPaths/i, // Path allowlist
|
|
715
|
+
/SAFE_BASE_DIR/i, // Common safe directory constant
|
|
716
|
+
/baseDir|safeDir|allowedDir/i, // Directory restriction variables
|
|
717
|
+
/path\.basename\s*\(/i, // Only using basename (no traversal)
|
|
718
|
+
/\.replace\s*\(/i, // Generic replace (likely sanitization)
|
|
719
|
+
]
|
|
720
|
+
|
|
721
|
+
return pathValidationPatterns.some(p => p.test(context))
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
/**
|
|
725
|
+
* Check if header value is sanitized
|
|
726
|
+
*/
|
|
727
|
+
function isHeaderSanitized(content: string, lineNumber: number): boolean {
|
|
728
|
+
const lines = content.split('\n')
|
|
729
|
+
const contextStart = Math.max(0, lineNumber - 15)
|
|
730
|
+
const contextEnd = Math.min(lines.length, lineNumber + 5)
|
|
731
|
+
const context = lines.slice(contextStart, contextEnd).join('\n')
|
|
732
|
+
|
|
733
|
+
const headerSanitizationPatterns = [
|
|
734
|
+
/\.replace\s*\(\s*\/\[\\r\\n\]/i, // CRLF removal
|
|
735
|
+
/\.replace\s*\(\s*\/\[\\\\r\\\\n\]/i, // CRLF removal (escaped)
|
|
736
|
+
/allowedTypes\.includes\s*\(/i, // Content-type allowlist
|
|
737
|
+
/allowed(?:Headers|Types|Values)\.includes\s*\(/i, // Generic allowlist
|
|
738
|
+
/if\s*\(\s*allowed\w*\.includes\s*\(/i, // Conditional allowlist
|
|
739
|
+
/crypto\.random/i, // Server-generated value (not AI)
|
|
740
|
+
/randomUUID/i, // UUID generation
|
|
741
|
+
/safeValue|sanitized/i, // Variable indicating sanitization
|
|
742
|
+
/\/\^?\[a-zA-Z0-9\-_\.\s\]\+\$?\/.*\.test\s*\(/i, // Regex validation (alphanumeric chars only)
|
|
743
|
+
/if\s*\(\/\^?\[a-zA-Z0-9/i, // Conditional with alphanumeric regex
|
|
744
|
+
]
|
|
745
|
+
|
|
746
|
+
return headerSanitizationPatterns.some(p => p.test(context))
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
/**
|
|
750
|
+
* Check for Python-specific safe patterns
|
|
751
|
+
*/
|
|
752
|
+
function isPythonSafe(lineContent: string, surroundingContext: string): boolean {
|
|
753
|
+
const fullContext = lineContent + '\n' + surroundingContext
|
|
754
|
+
|
|
755
|
+
const pythonSafePatterns = [
|
|
756
|
+
/ast\.literal_eval\s*\(/i, // Safe literal evaluation
|
|
757
|
+
/yaml\.(?:safe_load|SafeLoader)/i, // Safe YAML
|
|
758
|
+
/yaml\.load\s*\([^)]*Loader\s*=\s*yaml\.SafeLoader/i, // Explicit SafeLoader
|
|
759
|
+
/cursor\.execute\s*\([^,]+,\s*\[/i, // Parameterized query with list
|
|
760
|
+
/\?\s*,\s*\[/i, // SQL placeholder with params
|
|
761
|
+
/%s.*,\s*\[/i, // Python %s placeholder with list
|
|
762
|
+
/subprocess\.run\s*\(\s*\[/i, // subprocess with list (no shell)
|
|
763
|
+
/shell\s*=\s*False/i, // Explicit shell=False
|
|
764
|
+
]
|
|
765
|
+
|
|
766
|
+
return pythonSafePatterns.some(p => p.test(fullContext))
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
/**
|
|
770
|
+
* Check if SQL is using parameterized queries or ORM
|
|
771
|
+
*/
|
|
772
|
+
function isSQLParameterized(lineContent: string, surroundingContext: string): boolean {
|
|
773
|
+
const fullContext = lineContent + '\n' + surroundingContext
|
|
774
|
+
|
|
775
|
+
const parameterizedPatterns = [
|
|
776
|
+
/allowedColumns\.filter\s*\(/i, // Column allowlist
|
|
777
|
+
/safeColumns/i, // Safe column variable
|
|
778
|
+
/allowedColumns\.includes\s*\(/i, // Column allowlist check
|
|
779
|
+
/\.filter\s*\(\s*\w+\s*=>\s*allowed\w*\.includes/i, // Filter with allowlist
|
|
780
|
+
/schema\.parse\s*\(/i, // Zod schema validation
|
|
781
|
+
/z\.enum\s*\(\s*\[/i, // Zod enum (allowlist)
|
|
782
|
+
/prisma\.\w+\.(?:findMany|findUnique|create|update)/i, // Prisma ORM methods (not raw)
|
|
783
|
+
/\$\{.*\}.*WHERE.*=\s*\$\d/i, // Dynamic column but parameterized value
|
|
784
|
+
]
|
|
785
|
+
|
|
786
|
+
return parameterizedPatterns.some(p => p.test(fullContext))
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
/**
|
|
790
|
+
* Check if shell execution uses allowlist
|
|
791
|
+
*/
|
|
792
|
+
function isShellAllowlisted(content: string, lineNumber: number): boolean {
|
|
793
|
+
const lines = content.split('\n')
|
|
794
|
+
const contextStart = Math.max(0, lineNumber - 15)
|
|
795
|
+
const contextEnd = Math.min(lines.length, lineNumber + 5)
|
|
796
|
+
const context = lines.slice(contextStart, contextEnd).join('\n')
|
|
797
|
+
|
|
798
|
+
const shellAllowlistPatterns = [
|
|
799
|
+
/allowedArgs\.includes\s*\(/i, // Argument allowlist
|
|
800
|
+
/if\s*\(\s*allowedArgs\.includes/i, // Conditional on allowlist
|
|
801
|
+
/allowedCommands\.includes\s*\(/i, // Command allowlist
|
|
802
|
+
/execFile\s*\(\s*['"][^'"]+['"]/i, // execFile with hardcoded command (safe)
|
|
803
|
+
/\.replace\s*\(\s*\/\[^a-z0-9\]/gi, // Strict sanitization
|
|
804
|
+
/sanitized\s*=/i, // Sanitization variable
|
|
805
|
+
]
|
|
806
|
+
|
|
807
|
+
return shellAllowlistPatterns.some(p => p.test(context))
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
/**
|
|
811
|
+
* Check if dynamic import uses allowlist
|
|
812
|
+
*/
|
|
813
|
+
function isImportAllowlisted(content: string, lineNumber: number): boolean {
|
|
814
|
+
const lines = content.split('\n')
|
|
815
|
+
const contextStart = Math.max(0, lineNumber - 15)
|
|
816
|
+
const contextEnd = Math.min(lines.length, lineNumber + 5)
|
|
817
|
+
const context = lines.slice(contextStart, contextEnd).join('\n')
|
|
818
|
+
|
|
819
|
+
const importAllowlistPatterns = [
|
|
820
|
+
/ALLOWED_PLUGINS\s*[=:]/i, // Plugin allowlist
|
|
821
|
+
/importMap\s*[=:]/i, // Import map object
|
|
822
|
+
/allowedModules/i, // Module allowlist
|
|
823
|
+
/if\s*\(\s*\w+\s+in\s+importMap\)/i, // Key in import map
|
|
824
|
+
/if\s*\(\s*loader\)/i, // Loader function check (from allowlist)
|
|
825
|
+
/\[aiModule\]\s*$/i, // Array access into known object (allowlist lookup)
|
|
826
|
+
]
|
|
827
|
+
|
|
828
|
+
return importAllowlistPatterns.some(p => p.test(context))
|
|
829
|
+
}
|
|
830
|
+
|
|
404
831
|
// ============================================================================
|
|
405
832
|
// Main Detection Function
|
|
406
833
|
// ============================================================================
|
|
@@ -529,12 +956,86 @@ export function detectAIExecutionSinks(
|
|
|
529
956
|
const isSandboxed = isSandboxedExecution(content, lineNumber)
|
|
530
957
|
const hasValidation = hasOutputValidation(content, lineNumber)
|
|
531
958
|
|
|
959
|
+
// ===== SINK-SPECIFIC VALIDATION CHECKS =====
|
|
960
|
+
|
|
961
|
+
// Phase 2: Check for URL validation on network/redirect sinks (SSRF, Open Redirect)
|
|
962
|
+
const isNetworkSink = pattern.name.includes('fetch') || pattern.name.includes('axios') ||
|
|
963
|
+
pattern.name.includes('HTTP') || pattern.name.includes('redirect') ||
|
|
964
|
+
pattern.name.includes('location') || pattern.name.includes('got')
|
|
965
|
+
if (isNetworkSink) {
|
|
966
|
+
const urlValidLevel = getURLValidationLevel(content, lineNumber)
|
|
967
|
+
if (urlValidLevel === 'strong') {
|
|
968
|
+
continue // Skip - strong URL validation present
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
// Phase 3: Check for DOM sanitization on template_render sinks
|
|
973
|
+
const hasDOMSanitization = pattern.sinkType === 'template_render'
|
|
974
|
+
? isDOMSanitized(lineContent, surroundingContext)
|
|
975
|
+
: false
|
|
976
|
+
|
|
977
|
+
// Skip DOM findings if sanitized
|
|
978
|
+
if (hasDOMSanitization && pattern.sinkType === 'template_render') {
|
|
979
|
+
continue
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
// Check for header sanitization
|
|
983
|
+
const isHeaderSink = pattern.name.includes('header') || pattern.name.includes('cookie') ||
|
|
984
|
+
pattern.name.includes('res.type')
|
|
985
|
+
if (isHeaderSink && isHeaderSanitized(content, lineNumber)) {
|
|
986
|
+
continue // Skip - header value is sanitized
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
// Check for path validation on file system sinks
|
|
990
|
+
const isFileSink = pattern.name.includes('file path') || pattern.name.includes('fs operation') ||
|
|
991
|
+
pattern.name.includes('path.join')
|
|
992
|
+
if (isFileSink && isPathValidated(content, lineNumber)) {
|
|
993
|
+
continue // Skip - path is validated
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
// Check for SQL parameterization
|
|
997
|
+
const isSQLSink = pattern.sinkType === 'sql_builder'
|
|
998
|
+
if (isSQLSink && isSQLParameterized(lineContent, surroundingContext)) {
|
|
999
|
+
continue // Skip - SQL is parameterized or uses allowlist
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
// Check for shell allowlist
|
|
1003
|
+
const isShellSink = pattern.sinkType === 'shell_command'
|
|
1004
|
+
if (isShellSink && isShellAllowlisted(content, lineNumber)) {
|
|
1005
|
+
continue // Skip - shell command uses allowlist
|
|
1006
|
+
}
|
|
1007
|
+
|
|
1008
|
+
// Check for import allowlist
|
|
1009
|
+
const isImportSink = pattern.name.includes('import') || pattern.name.includes('require')
|
|
1010
|
+
if (isImportSink && isImportAllowlisted(content, lineNumber)) {
|
|
1011
|
+
continue // Skip - import uses allowlist
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
// Check for Python-specific safe patterns
|
|
1015
|
+
const isPythonSink = pattern.name.includes('Python') || pattern.name.includes('pickle') ||
|
|
1016
|
+
pattern.name.includes('subprocess') || pattern.name.includes('os.system')
|
|
1017
|
+
if (isPythonSink && isPythonSafe(lineContent, surroundingContext)) {
|
|
1018
|
+
continue // Skip - Python code uses safe patterns
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
// Check for ast.literal_eval (Python safe eval) - this is a safe alternative to eval()
|
|
1022
|
+
// It matches the eval pattern because literal_eval contains "eval("
|
|
1023
|
+
if (pattern.name.includes('eval') && /ast\.literal_eval\s*\(/i.test(lineContent)) {
|
|
1024
|
+
continue // Skip - ast.literal_eval is safe, only evaluates literals
|
|
1025
|
+
}
|
|
1026
|
+
|
|
1027
|
+
// Check URL validation level for severity adjustment
|
|
1028
|
+
const hasURLValid = isNetworkSink ? getURLValidationLevel(content, lineNumber) !== 'none' : false
|
|
1029
|
+
|
|
1030
|
+
// Combine validation checks (URL validation counts as validation for network sinks)
|
|
1031
|
+
const effectiveValidation = hasValidation || hasURLValid
|
|
1032
|
+
|
|
532
1033
|
// Calculate final severity
|
|
533
1034
|
const severity = calculateSeverity(
|
|
534
1035
|
pattern.baseSeverity,
|
|
535
1036
|
pattern.sinkType,
|
|
536
1037
|
isSandboxed,
|
|
537
|
-
|
|
1038
|
+
effectiveValidation,
|
|
538
1039
|
isTestFile,
|
|
539
1040
|
isExample,
|
|
540
1041
|
isLibrary
|
|
@@ -548,6 +1049,9 @@ export function detectAIExecutionSinks(
|
|
|
548
1049
|
if (hasValidation) {
|
|
549
1050
|
description += ' (Some validation detected nearby.)'
|
|
550
1051
|
}
|
|
1052
|
+
if (hasURLValid && !hasValidation) {
|
|
1053
|
+
description += ' (URL validation detected nearby.)'
|
|
1054
|
+
}
|
|
551
1055
|
if (isTestFile) {
|
|
552
1056
|
description += ' (In test file.)'
|
|
553
1057
|
} else if (isExample) {
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import type { Vulnerability, VulnerabilitySeverity } from '../types'
|
|
7
|
-
import { isExampleFile, isTestOrMockFile, isPlaceholderValue } from '../utils/context-helpers'
|
|
7
|
+
import { isExampleFile, isTestOrMockFile, isPlaceholderValue, isScannerOrFixtureFile } from '../utils/context-helpers'
|
|
8
8
|
|
|
9
9
|
interface AIFingerprint {
|
|
10
10
|
name: string
|
|
@@ -635,6 +635,10 @@ export function detectAIFingerprints(
|
|
|
635
635
|
filePath: string
|
|
636
636
|
): Vulnerability[] {
|
|
637
637
|
const vulnerabilities: Vulnerability[] = []
|
|
638
|
+
|
|
639
|
+
// Skip scanner/fixture files to avoid self-detection
|
|
640
|
+
if (isScannerOrFixtureFile(filePath)) return vulnerabilities
|
|
641
|
+
|
|
638
642
|
const lines = content.split('\n')
|
|
639
643
|
|
|
640
644
|
// Skip example/demo files entirely - they contain placeholder code by design
|