@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
|
@@ -0,0 +1,689 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for Framework-Aware Fix Suggestions (PRO-83)
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
FRAMEWORK_FIX_REGISTRY,
|
|
7
|
+
getFrameworkFix,
|
|
8
|
+
} from '../framework-fixes'
|
|
9
|
+
import type { FrameworkContext, DataAccessContext } from '../../utils/project-context-builder'
|
|
10
|
+
import type { VulnerabilityCategory } from '../../types'
|
|
11
|
+
|
|
12
|
+
// Helper to create a minimal FrameworkContext
|
|
13
|
+
function createFrameworkContext(overrides: Partial<FrameworkContext> = {}): FrameworkContext {
|
|
14
|
+
return {
|
|
15
|
+
primary: undefined,
|
|
16
|
+
frontend: undefined,
|
|
17
|
+
isMonorepo: false,
|
|
18
|
+
usesTypeScript: false,
|
|
19
|
+
usesServerComponents: false,
|
|
20
|
+
usesServerActions: false,
|
|
21
|
+
...overrides,
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Helper to create a minimal DataAccessContext
|
|
26
|
+
function createDataAccessContext(overrides: Partial<DataAccessContext> = {}): DataAccessContext {
|
|
27
|
+
return {
|
|
28
|
+
usesParameterizedQueries: false,
|
|
29
|
+
hasRLS: false,
|
|
30
|
+
...overrides,
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
describe('Framework-Aware Fix Suggestions', () => {
|
|
35
|
+
describe('FRAMEWORK_FIX_REGISTRY coverage', () => {
|
|
36
|
+
it('should have sql_injection fixes for major ORMs', () => {
|
|
37
|
+
const sqlFixes = FRAMEWORK_FIX_REGISTRY.sql_injection
|
|
38
|
+
expect(sqlFixes).toBeDefined()
|
|
39
|
+
|
|
40
|
+
expect(sqlFixes!.prisma).toBeDefined()
|
|
41
|
+
expect(sqlFixes!.drizzle).toBeDefined()
|
|
42
|
+
expect(sqlFixes!.supabase).toBeDefined()
|
|
43
|
+
expect(sqlFixes!.mongoose).toBeDefined()
|
|
44
|
+
expect(sqlFixes!.knex).toBeDefined()
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
it('should have missing_auth fixes for backend frameworks', () => {
|
|
48
|
+
const authFixes = FRAMEWORK_FIX_REGISTRY.missing_auth
|
|
49
|
+
expect(authFixes).toBeDefined()
|
|
50
|
+
|
|
51
|
+
expect(authFixes!.nextjs).toBeDefined()
|
|
52
|
+
expect(authFixes!.express).toBeDefined()
|
|
53
|
+
expect(authFixes!.fastify).toBeDefined()
|
|
54
|
+
expect(authFixes!.nestjs).toBeDefined()
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
it('should have xss fixes for frontend frameworks', () => {
|
|
58
|
+
const xssFixes = FRAMEWORK_FIX_REGISTRY.xss
|
|
59
|
+
expect(xssFixes).toBeDefined()
|
|
60
|
+
|
|
61
|
+
expect(xssFixes!.react).toBeDefined()
|
|
62
|
+
expect(xssFixes!.vue).toBeDefined()
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
it('should have hardcoded_secret fixes for common frameworks', () => {
|
|
66
|
+
const secretFixes = FRAMEWORK_FIX_REGISTRY.hardcoded_secret
|
|
67
|
+
expect(secretFixes).toBeDefined()
|
|
68
|
+
|
|
69
|
+
expect(secretFixes!.nextjs).toBeDefined()
|
|
70
|
+
expect(secretFixes!.express).toBeDefined()
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
it('should have cors_misconfiguration fixes', () => {
|
|
74
|
+
const corsFixes = FRAMEWORK_FIX_REGISTRY.cors_misconfiguration
|
|
75
|
+
expect(corsFixes).toBeDefined()
|
|
76
|
+
|
|
77
|
+
expect(corsFixes!.nextjs).toBeDefined()
|
|
78
|
+
expect(corsFixes!.express).toBeDefined()
|
|
79
|
+
expect(corsFixes!.fastify).toBeDefined()
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
it('should have dangerous_function fixes for frontend frameworks', () => {
|
|
83
|
+
const dangerousFixes = FRAMEWORK_FIX_REGISTRY.dangerous_function
|
|
84
|
+
expect(dangerousFixes).toBeDefined()
|
|
85
|
+
|
|
86
|
+
expect(dangerousFixes!.react).toBeDefined()
|
|
87
|
+
expect(dangerousFixes!.vue).toBeDefined()
|
|
88
|
+
})
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
describe('Fix content quality', () => {
|
|
92
|
+
it('all fixes should have non-empty fixSteps', () => {
|
|
93
|
+
for (const [_category, frameworkFixes] of Object.entries(FRAMEWORK_FIX_REGISTRY)) {
|
|
94
|
+
for (const [_framework, fix] of Object.entries(frameworkFixes || {})) {
|
|
95
|
+
expect(fix.fixSteps).toBeDefined()
|
|
96
|
+
expect(Array.isArray(fix.fixSteps)).toBe(true)
|
|
97
|
+
expect(fix.fixSteps.length).toBeGreaterThan(0)
|
|
98
|
+
|
|
99
|
+
// Each step should be meaningful (at least 10 characters)
|
|
100
|
+
for (const step of fix.fixSteps) {
|
|
101
|
+
expect(step.length).toBeGreaterThan(10)
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
it('code examples should be present for key fixes', () => {
|
|
108
|
+
// SQL injection fixes should have code examples
|
|
109
|
+
const sqlFixes = FRAMEWORK_FIX_REGISTRY.sql_injection
|
|
110
|
+
expect(sqlFixes!.prisma!.codeExample).toBeDefined()
|
|
111
|
+
expect(sqlFixes!.drizzle!.codeExample).toBeDefined()
|
|
112
|
+
expect(sqlFixes!.supabase!.codeExample).toBeDefined()
|
|
113
|
+
expect(sqlFixes!.mongoose!.codeExample).toBeDefined()
|
|
114
|
+
expect(sqlFixes!.knex!.codeExample).toBeDefined()
|
|
115
|
+
|
|
116
|
+
// Auth fixes should have code examples
|
|
117
|
+
const authFixes = FRAMEWORK_FIX_REGISTRY.missing_auth
|
|
118
|
+
expect(authFixes!.nextjs!.codeExample).toBeDefined()
|
|
119
|
+
expect(authFixes!.express!.codeExample).toBeDefined()
|
|
120
|
+
expect(authFixes!.fastify!.codeExample).toBeDefined()
|
|
121
|
+
expect(authFixes!.nestjs!.codeExample).toBeDefined()
|
|
122
|
+
|
|
123
|
+
// XSS fixes should have code examples
|
|
124
|
+
const xssFixes = FRAMEWORK_FIX_REGISTRY.xss
|
|
125
|
+
expect(xssFixes!.react!.codeExample).toBeDefined()
|
|
126
|
+
expect(xssFixes!.vue!.codeExample).toBeDefined()
|
|
127
|
+
|
|
128
|
+
// CORS fixes should have code examples
|
|
129
|
+
const corsFixes = FRAMEWORK_FIX_REGISTRY.cors_misconfiguration
|
|
130
|
+
expect(corsFixes!.nextjs!.codeExample).toBeDefined()
|
|
131
|
+
expect(corsFixes!.express!.codeExample).toBeDefined()
|
|
132
|
+
expect(corsFixes!.fastify!.codeExample).toBeDefined()
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
it('fix steps should be actionable (start with verbs)', () => {
|
|
136
|
+
for (const [_category, frameworkFixes] of Object.entries(FRAMEWORK_FIX_REGISTRY)) {
|
|
137
|
+
for (const [_framework, fix] of Object.entries(frameworkFixes || {})) {
|
|
138
|
+
for (const step of fix.fixSteps) {
|
|
139
|
+
// Each step should start with a capital letter (typically a verb)
|
|
140
|
+
expect(step[0]).toBe(step[0].toUpperCase())
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
it('code examples should contain actual code patterns', () => {
|
|
147
|
+
// Prisma examples should reference prisma client
|
|
148
|
+
expect(FRAMEWORK_FIX_REGISTRY.sql_injection!.prisma!.codeExample).toMatch(/prisma\./i)
|
|
149
|
+
|
|
150
|
+
// React examples should reference JSX or React patterns
|
|
151
|
+
expect(FRAMEWORK_FIX_REGISTRY.xss!.react!.codeExample).toMatch(/dangerouslySetInnerHTML|<div>|DOMPurify/i)
|
|
152
|
+
|
|
153
|
+
// Express examples should reference middleware patterns
|
|
154
|
+
expect(FRAMEWORK_FIX_REGISTRY.missing_auth!.express!.codeExample).toMatch(/middleware|req|res|next/i)
|
|
155
|
+
})
|
|
156
|
+
|
|
157
|
+
it('fix steps should mention framework-specific concepts', () => {
|
|
158
|
+
// Prisma fixes should mention Prisma-specific concepts
|
|
159
|
+
const prismaFix = FRAMEWORK_FIX_REGISTRY.sql_injection!.prisma!
|
|
160
|
+
const prismaText = prismaFix.fixSteps.join(' ')
|
|
161
|
+
expect(prismaText).toMatch(/\$queryRaw|findUnique|Prisma/i)
|
|
162
|
+
|
|
163
|
+
// Next.js auth fixes should mention middleware.ts
|
|
164
|
+
const nextAuthFix = FRAMEWORK_FIX_REGISTRY.missing_auth!.nextjs!
|
|
165
|
+
const nextAuthText = nextAuthFix.fixSteps.join(' ')
|
|
166
|
+
expect(nextAuthText).toMatch(/middleware\.ts|auth\(\)|getServerSession/i)
|
|
167
|
+
|
|
168
|
+
// React XSS fixes should mention JSX auto-escaping
|
|
169
|
+
const reactXssFix = FRAMEWORK_FIX_REGISTRY.xss!.react!
|
|
170
|
+
const reactXssText = reactXssFix.fixSteps.join(' ')
|
|
171
|
+
expect(reactXssText).toMatch(/JSX|auto-escape|dangerouslySetInnerHTML/i)
|
|
172
|
+
})
|
|
173
|
+
})
|
|
174
|
+
|
|
175
|
+
describe('getFrameworkFix', () => {
|
|
176
|
+
describe('ORM priority for sql_injection', () => {
|
|
177
|
+
it('should return Prisma fix when Prisma is detected', () => {
|
|
178
|
+
const frameworks = createFrameworkContext({ primary: 'nextjs', frontend: 'react' })
|
|
179
|
+
const dataAccess = createDataAccessContext({ orm: 'prisma' })
|
|
180
|
+
|
|
181
|
+
const fix = getFrameworkFix('sql_injection', frameworks, dataAccess)
|
|
182
|
+
|
|
183
|
+
expect(fix).toBeDefined()
|
|
184
|
+
expect(fix!.fixSteps[0]).toContain('Prisma')
|
|
185
|
+
})
|
|
186
|
+
|
|
187
|
+
it('should return Drizzle fix when Drizzle is detected', () => {
|
|
188
|
+
const frameworks = createFrameworkContext({ primary: 'nextjs' })
|
|
189
|
+
const dataAccess = createDataAccessContext({ orm: 'drizzle' })
|
|
190
|
+
|
|
191
|
+
const fix = getFrameworkFix('sql_injection', frameworks, dataAccess)
|
|
192
|
+
|
|
193
|
+
expect(fix).toBeDefined()
|
|
194
|
+
expect(fix!.fixSteps[0]).toContain('Drizzle')
|
|
195
|
+
})
|
|
196
|
+
|
|
197
|
+
it('should return Supabase fix when Supabase is detected', () => {
|
|
198
|
+
const frameworks = createFrameworkContext({ primary: 'nextjs' })
|
|
199
|
+
const dataAccess = createDataAccessContext({ orm: 'supabase', hasRLS: true })
|
|
200
|
+
|
|
201
|
+
const fix = getFrameworkFix('sql_injection', frameworks, dataAccess)
|
|
202
|
+
|
|
203
|
+
expect(fix).toBeDefined()
|
|
204
|
+
expect(fix!.fixSteps[0]).toContain('Supabase')
|
|
205
|
+
})
|
|
206
|
+
|
|
207
|
+
it('should return Mongoose fix when Mongoose is detected', () => {
|
|
208
|
+
const frameworks = createFrameworkContext({ primary: 'express' })
|
|
209
|
+
const dataAccess = createDataAccessContext({ orm: 'mongoose' })
|
|
210
|
+
|
|
211
|
+
const fix = getFrameworkFix('sql_injection', frameworks, dataAccess)
|
|
212
|
+
|
|
213
|
+
expect(fix).toBeDefined()
|
|
214
|
+
expect(fix!.fixSteps[0]).toContain('Mongoose')
|
|
215
|
+
})
|
|
216
|
+
|
|
217
|
+
it('should return Knex fix when Knex is detected', () => {
|
|
218
|
+
const frameworks = createFrameworkContext({ primary: 'express' })
|
|
219
|
+
const dataAccess = createDataAccessContext({ orm: 'knex' })
|
|
220
|
+
|
|
221
|
+
const fix = getFrameworkFix('sql_injection', frameworks, dataAccess)
|
|
222
|
+
|
|
223
|
+
expect(fix).toBeDefined()
|
|
224
|
+
expect(fix!.fixSteps[0]).toContain('Knex')
|
|
225
|
+
})
|
|
226
|
+
})
|
|
227
|
+
|
|
228
|
+
describe('frontend framework priority for XSS', () => {
|
|
229
|
+
it('should return React fix for xss when React is detected', () => {
|
|
230
|
+
const frameworks = createFrameworkContext({ primary: 'nextjs', frontend: 'react' })
|
|
231
|
+
|
|
232
|
+
const fix = getFrameworkFix('xss', frameworks)
|
|
233
|
+
|
|
234
|
+
expect(fix).toBeDefined()
|
|
235
|
+
expect(fix!.fixSteps[0]).toContain('JSX')
|
|
236
|
+
})
|
|
237
|
+
|
|
238
|
+
it('should return Vue fix for xss when Vue is detected', () => {
|
|
239
|
+
const frameworks = createFrameworkContext({ primary: 'express', frontend: 'vue' })
|
|
240
|
+
|
|
241
|
+
const fix = getFrameworkFix('xss', frameworks)
|
|
242
|
+
|
|
243
|
+
expect(fix).toBeDefined()
|
|
244
|
+
expect(fix!.fixSteps[0]).toContain('v-text')
|
|
245
|
+
})
|
|
246
|
+
|
|
247
|
+
it('should return React fix for dangerous_function when React is detected', () => {
|
|
248
|
+
const frameworks = createFrameworkContext({ frontend: 'react' })
|
|
249
|
+
|
|
250
|
+
const fix = getFrameworkFix('dangerous_function', frameworks)
|
|
251
|
+
|
|
252
|
+
expect(fix).toBeDefined()
|
|
253
|
+
expect(fix!.fixSteps[0]).toContain('innerHTML')
|
|
254
|
+
})
|
|
255
|
+
|
|
256
|
+
it('should return Vue fix for dangerous_function when Vue is detected', () => {
|
|
257
|
+
const frameworks = createFrameworkContext({ frontend: 'vue' })
|
|
258
|
+
|
|
259
|
+
const fix = getFrameworkFix('dangerous_function', frameworks)
|
|
260
|
+
|
|
261
|
+
expect(fix).toBeDefined()
|
|
262
|
+
expect(fix!.fixSteps[0]).toContain('Vue')
|
|
263
|
+
})
|
|
264
|
+
})
|
|
265
|
+
|
|
266
|
+
describe('backend framework fixes', () => {
|
|
267
|
+
it('should return Next.js fix for missing_auth', () => {
|
|
268
|
+
const frameworks = createFrameworkContext({ primary: 'nextjs', frontend: 'react' })
|
|
269
|
+
|
|
270
|
+
const fix = getFrameworkFix('missing_auth', frameworks)
|
|
271
|
+
|
|
272
|
+
expect(fix).toBeDefined()
|
|
273
|
+
expect(fix!.fixSteps[0]).toContain('middleware.ts')
|
|
274
|
+
})
|
|
275
|
+
|
|
276
|
+
it('should return Express fix for missing_auth', () => {
|
|
277
|
+
const frameworks = createFrameworkContext({ primary: 'express' })
|
|
278
|
+
|
|
279
|
+
const fix = getFrameworkFix('missing_auth', frameworks)
|
|
280
|
+
|
|
281
|
+
expect(fix).toBeDefined()
|
|
282
|
+
expect(fix!.fixSteps[0]).toContain('middleware')
|
|
283
|
+
})
|
|
284
|
+
|
|
285
|
+
it('should return Fastify fix for missing_auth', () => {
|
|
286
|
+
const frameworks = createFrameworkContext({ primary: 'fastify' })
|
|
287
|
+
|
|
288
|
+
const fix = getFrameworkFix('missing_auth', frameworks)
|
|
289
|
+
|
|
290
|
+
expect(fix).toBeDefined()
|
|
291
|
+
expect(fix!.fixSteps[0]).toContain('@fastify')
|
|
292
|
+
})
|
|
293
|
+
|
|
294
|
+
it('should return NestJS fix for missing_auth', () => {
|
|
295
|
+
const frameworks = createFrameworkContext({ primary: 'nestjs' })
|
|
296
|
+
|
|
297
|
+
const fix = getFrameworkFix('missing_auth', frameworks)
|
|
298
|
+
|
|
299
|
+
expect(fix).toBeDefined()
|
|
300
|
+
expect(fix!.fixSteps[0]).toContain('@nestjs')
|
|
301
|
+
})
|
|
302
|
+
|
|
303
|
+
it('should return Next.js fix for hardcoded_secret', () => {
|
|
304
|
+
const frameworks = createFrameworkContext({ primary: 'nextjs' })
|
|
305
|
+
|
|
306
|
+
const fix = getFrameworkFix('hardcoded_secret', frameworks)
|
|
307
|
+
|
|
308
|
+
expect(fix).toBeDefined()
|
|
309
|
+
expect(fix!.fixSteps[0]).toContain('.env.local')
|
|
310
|
+
})
|
|
311
|
+
|
|
312
|
+
it('should return Express fix for hardcoded_secret', () => {
|
|
313
|
+
const frameworks = createFrameworkContext({ primary: 'express' })
|
|
314
|
+
|
|
315
|
+
const fix = getFrameworkFix('hardcoded_secret', frameworks)
|
|
316
|
+
|
|
317
|
+
expect(fix).toBeDefined()
|
|
318
|
+
expect(fix!.fixSteps[0]).toContain('dotenv')
|
|
319
|
+
})
|
|
320
|
+
|
|
321
|
+
it('should return Next.js fix for cors_misconfiguration', () => {
|
|
322
|
+
const frameworks = createFrameworkContext({ primary: 'nextjs' })
|
|
323
|
+
|
|
324
|
+
const fix = getFrameworkFix('cors_misconfiguration', frameworks)
|
|
325
|
+
|
|
326
|
+
expect(fix).toBeDefined()
|
|
327
|
+
expect(fix!.fixSteps[0]).toContain('next.config')
|
|
328
|
+
})
|
|
329
|
+
|
|
330
|
+
it('should return Express fix for cors_misconfiguration', () => {
|
|
331
|
+
const frameworks = createFrameworkContext({ primary: 'express' })
|
|
332
|
+
|
|
333
|
+
const fix = getFrameworkFix('cors_misconfiguration', frameworks)
|
|
334
|
+
|
|
335
|
+
expect(fix).toBeDefined()
|
|
336
|
+
expect(fix!.fixSteps[0]).toContain('cors')
|
|
337
|
+
})
|
|
338
|
+
|
|
339
|
+
it('should return Fastify fix for cors_misconfiguration', () => {
|
|
340
|
+
const frameworks = createFrameworkContext({ primary: 'fastify' })
|
|
341
|
+
|
|
342
|
+
const fix = getFrameworkFix('cors_misconfiguration', frameworks)
|
|
343
|
+
|
|
344
|
+
expect(fix).toBeDefined()
|
|
345
|
+
expect(fix!.fixSteps[0]).toContain('@fastify/cors')
|
|
346
|
+
})
|
|
347
|
+
})
|
|
348
|
+
|
|
349
|
+
describe('fallback behavior', () => {
|
|
350
|
+
it('should return undefined for categories without framework fixes', () => {
|
|
351
|
+
const frameworks = createFrameworkContext({ primary: 'nextjs' })
|
|
352
|
+
|
|
353
|
+
// These categories have no framework-specific fixes
|
|
354
|
+
const categoriesWithoutFixes: VulnerabilityCategory[] = [
|
|
355
|
+
'suspicious_package',
|
|
356
|
+
'root_container',
|
|
357
|
+
'weak_crypto',
|
|
358
|
+
'command_injection',
|
|
359
|
+
'security_bypass',
|
|
360
|
+
'insecure_config',
|
|
361
|
+
'high_entropy_string',
|
|
362
|
+
'sensitive_variable',
|
|
363
|
+
'dangerous_file',
|
|
364
|
+
'data_exposure',
|
|
365
|
+
'ai_pattern',
|
|
366
|
+
'ai_prompt_injection',
|
|
367
|
+
'ai_unsafe_execution',
|
|
368
|
+
'ai_overpermissive_tool',
|
|
369
|
+
'ai_rag_exfiltration',
|
|
370
|
+
'ai_endpoint_unprotected',
|
|
371
|
+
'ai_schema_mismatch',
|
|
372
|
+
]
|
|
373
|
+
|
|
374
|
+
for (const category of categoriesWithoutFixes) {
|
|
375
|
+
const fix = getFrameworkFix(category, frameworks)
|
|
376
|
+
expect(fix).toBeUndefined()
|
|
377
|
+
}
|
|
378
|
+
})
|
|
379
|
+
|
|
380
|
+
it('should return undefined when no framework is detected', () => {
|
|
381
|
+
const frameworks = createFrameworkContext() // All undefined
|
|
382
|
+
|
|
383
|
+
const fix = getFrameworkFix('missing_auth', frameworks)
|
|
384
|
+
|
|
385
|
+
expect(fix).toBeUndefined()
|
|
386
|
+
})
|
|
387
|
+
|
|
388
|
+
it('should return undefined when framework has no fix for category', () => {
|
|
389
|
+
const frameworks = createFrameworkContext({ primary: 'nestjs' })
|
|
390
|
+
|
|
391
|
+
// NestJS has missing_auth fix but no hardcoded_secret fix
|
|
392
|
+
const fix = getFrameworkFix('hardcoded_secret', frameworks)
|
|
393
|
+
|
|
394
|
+
expect(fix).toBeUndefined()
|
|
395
|
+
})
|
|
396
|
+
|
|
397
|
+
it('should return undefined for sql_injection when ORM has no fix', () => {
|
|
398
|
+
const frameworks = createFrameworkContext({ primary: 'express' })
|
|
399
|
+
const dataAccess = createDataAccessContext({ orm: 'sequelize' }) // Not in registry
|
|
400
|
+
|
|
401
|
+
const fix = getFrameworkFix('sql_injection', frameworks, dataAccess)
|
|
402
|
+
|
|
403
|
+
expect(fix).toBeUndefined()
|
|
404
|
+
})
|
|
405
|
+
|
|
406
|
+
it('should return undefined for sql_injection when no ORM detected', () => {
|
|
407
|
+
const frameworks = createFrameworkContext({ primary: 'express' })
|
|
408
|
+
const dataAccess = createDataAccessContext({ orm: undefined })
|
|
409
|
+
|
|
410
|
+
const fix = getFrameworkFix('sql_injection', frameworks, dataAccess)
|
|
411
|
+
|
|
412
|
+
expect(fix).toBeUndefined()
|
|
413
|
+
})
|
|
414
|
+
|
|
415
|
+
it('should return undefined for sql_injection when raw_sql is detected', () => {
|
|
416
|
+
const frameworks = createFrameworkContext({ primary: 'express' })
|
|
417
|
+
const dataAccess = createDataAccessContext({ orm: 'raw_sql' })
|
|
418
|
+
|
|
419
|
+
const fix = getFrameworkFix('sql_injection', frameworks, dataAccess)
|
|
420
|
+
|
|
421
|
+
// raw_sql is not in the registry (no framework-specific fix)
|
|
422
|
+
expect(fix).toBeUndefined()
|
|
423
|
+
})
|
|
424
|
+
})
|
|
425
|
+
|
|
426
|
+
describe('priority ordering', () => {
|
|
427
|
+
it('should prioritize ORM over backend framework for sql_injection', () => {
|
|
428
|
+
const frameworks = createFrameworkContext({ primary: 'express' }) // Express has no sql_injection fix
|
|
429
|
+
const dataAccess = createDataAccessContext({ orm: 'prisma' }) // Prisma has sql_injection fix
|
|
430
|
+
|
|
431
|
+
const fix = getFrameworkFix('sql_injection', frameworks, dataAccess)
|
|
432
|
+
|
|
433
|
+
// Should return Prisma fix, not undefined/fallback
|
|
434
|
+
expect(fix).toBeDefined()
|
|
435
|
+
expect(fix!.fixSteps[0]).toContain('Prisma')
|
|
436
|
+
})
|
|
437
|
+
|
|
438
|
+
it('should prioritize frontend framework for xss over backend', () => {
|
|
439
|
+
const frameworks = createFrameworkContext({ primary: 'express', frontend: 'react' })
|
|
440
|
+
|
|
441
|
+
const fix = getFrameworkFix('xss', frameworks)
|
|
442
|
+
|
|
443
|
+
// Should return React fix since frontend takes priority for XSS
|
|
444
|
+
expect(fix).toBeDefined()
|
|
445
|
+
expect(fix!.fixSteps[0]).toContain('JSX')
|
|
446
|
+
})
|
|
447
|
+
|
|
448
|
+
it('should use backend framework when no frontend detected for xss', () => {
|
|
449
|
+
const frameworks = createFrameworkContext({ primary: 'express', frontend: undefined })
|
|
450
|
+
|
|
451
|
+
const fix = getFrameworkFix('xss', frameworks)
|
|
452
|
+
|
|
453
|
+
// Express has no XSS fix, so should return undefined
|
|
454
|
+
expect(fix).toBeUndefined()
|
|
455
|
+
})
|
|
456
|
+
|
|
457
|
+
it('should prioritize frontend for dangerous_function over backend', () => {
|
|
458
|
+
const frameworks = createFrameworkContext({ primary: 'nextjs', frontend: 'vue' })
|
|
459
|
+
|
|
460
|
+
const fix = getFrameworkFix('dangerous_function', frameworks)
|
|
461
|
+
|
|
462
|
+
// Should return Vue fix since frontend takes priority
|
|
463
|
+
expect(fix).toBeDefined()
|
|
464
|
+
expect(fix!.fixSteps[0]).toContain('Vue')
|
|
465
|
+
})
|
|
466
|
+
})
|
|
467
|
+
|
|
468
|
+
describe('edge cases', () => {
|
|
469
|
+
it('should handle empty dataAccess object', () => {
|
|
470
|
+
const frameworks = createFrameworkContext({ primary: 'nextjs' })
|
|
471
|
+
const dataAccess = createDataAccessContext()
|
|
472
|
+
|
|
473
|
+
const fix = getFrameworkFix('sql_injection', frameworks, dataAccess)
|
|
474
|
+
|
|
475
|
+
// No ORM specified, should return undefined
|
|
476
|
+
expect(fix).toBeUndefined()
|
|
477
|
+
})
|
|
478
|
+
|
|
479
|
+
it('should handle undefined dataAccess', () => {
|
|
480
|
+
const frameworks = createFrameworkContext({ primary: 'nextjs' })
|
|
481
|
+
|
|
482
|
+
const fix = getFrameworkFix('sql_injection', frameworks, undefined)
|
|
483
|
+
|
|
484
|
+
// No dataAccess at all, should return undefined
|
|
485
|
+
expect(fix).toBeUndefined()
|
|
486
|
+
})
|
|
487
|
+
|
|
488
|
+
it('should handle unknown ORM type gracefully', () => {
|
|
489
|
+
const frameworks = createFrameworkContext({ primary: 'express' })
|
|
490
|
+
const dataAccess = createDataAccessContext({ orm: 'typeorm' as any }) // Not in registry
|
|
491
|
+
|
|
492
|
+
const fix = getFrameworkFix('sql_injection', frameworks, dataAccess)
|
|
493
|
+
|
|
494
|
+
expect(fix).toBeUndefined()
|
|
495
|
+
})
|
|
496
|
+
|
|
497
|
+
it('should handle unknown framework type gracefully', () => {
|
|
498
|
+
const frameworks = createFrameworkContext({ primary: 'django' as any }) // Not in registry
|
|
499
|
+
|
|
500
|
+
const fix = getFrameworkFix('missing_auth', frameworks)
|
|
501
|
+
|
|
502
|
+
expect(fix).toBeUndefined()
|
|
503
|
+
})
|
|
504
|
+
|
|
505
|
+
it('should handle unknown frontend framework gracefully', () => {
|
|
506
|
+
const frameworks = createFrameworkContext({ frontend: 'svelte' as any }) // Not in XSS registry
|
|
507
|
+
|
|
508
|
+
const fix = getFrameworkFix('xss', frameworks)
|
|
509
|
+
|
|
510
|
+
expect(fix).toBeUndefined()
|
|
511
|
+
})
|
|
512
|
+
|
|
513
|
+
it('should work with only frontend defined (no primary)', () => {
|
|
514
|
+
const frameworks = createFrameworkContext({ frontend: 'react' })
|
|
515
|
+
|
|
516
|
+
const fix = getFrameworkFix('xss', frameworks)
|
|
517
|
+
|
|
518
|
+
expect(fix).toBeDefined()
|
|
519
|
+
expect(fix!.fixSteps[0]).toContain('JSX')
|
|
520
|
+
})
|
|
521
|
+
|
|
522
|
+
it('should work with only ORM defined (no frameworks)', () => {
|
|
523
|
+
const frameworks = createFrameworkContext()
|
|
524
|
+
const dataAccess = createDataAccessContext({ orm: 'prisma' })
|
|
525
|
+
|
|
526
|
+
const fix = getFrameworkFix('sql_injection', frameworks, dataAccess)
|
|
527
|
+
|
|
528
|
+
expect(fix).toBeDefined()
|
|
529
|
+
expect(fix!.fixSteps[0]).toContain('Prisma')
|
|
530
|
+
})
|
|
531
|
+
|
|
532
|
+
it('should not throw on invalid category', () => {
|
|
533
|
+
const frameworks = createFrameworkContext({ primary: 'nextjs' })
|
|
534
|
+
|
|
535
|
+
// @ts-expect-error - testing invalid input
|
|
536
|
+
const fix = getFrameworkFix('invalid_category', frameworks)
|
|
537
|
+
|
|
538
|
+
expect(fix).toBeUndefined()
|
|
539
|
+
})
|
|
540
|
+
|
|
541
|
+
it('should handle category that exists in registry but has no matching framework', () => {
|
|
542
|
+
// hardcoded_secret has nextjs and express, but not fastify
|
|
543
|
+
const frameworks = createFrameworkContext({ primary: 'fastify' })
|
|
544
|
+
|
|
545
|
+
const fix = getFrameworkFix('hardcoded_secret', frameworks)
|
|
546
|
+
|
|
547
|
+
expect(fix).toBeUndefined()
|
|
548
|
+
})
|
|
549
|
+
})
|
|
550
|
+
|
|
551
|
+
describe('real-world scenarios', () => {
|
|
552
|
+
it('Next.js + Prisma + React stack', () => {
|
|
553
|
+
const frameworks = createFrameworkContext({
|
|
554
|
+
primary: 'nextjs',
|
|
555
|
+
frontend: 'react',
|
|
556
|
+
usesTypeScript: true,
|
|
557
|
+
usesServerComponents: true,
|
|
558
|
+
})
|
|
559
|
+
const dataAccess = createDataAccessContext({
|
|
560
|
+
orm: 'prisma',
|
|
561
|
+
usesParameterizedQueries: true,
|
|
562
|
+
})
|
|
563
|
+
|
|
564
|
+
// SQL injection should use Prisma fix
|
|
565
|
+
const sqlFix = getFrameworkFix('sql_injection', frameworks, dataAccess)
|
|
566
|
+
expect(sqlFix).toBeDefined()
|
|
567
|
+
expect(sqlFix!.fixSteps[0]).toContain('Prisma')
|
|
568
|
+
|
|
569
|
+
// XSS should use React fix
|
|
570
|
+
const xssFix = getFrameworkFix('xss', frameworks)
|
|
571
|
+
expect(xssFix).toBeDefined()
|
|
572
|
+
expect(xssFix!.fixSteps[0]).toContain('JSX')
|
|
573
|
+
|
|
574
|
+
// Auth should use Next.js fix
|
|
575
|
+
const authFix = getFrameworkFix('missing_auth', frameworks)
|
|
576
|
+
expect(authFix).toBeDefined()
|
|
577
|
+
expect(authFix!.fixSteps[0]).toContain('middleware.ts')
|
|
578
|
+
|
|
579
|
+
// Secrets should use Next.js fix
|
|
580
|
+
const secretFix = getFrameworkFix('hardcoded_secret', frameworks)
|
|
581
|
+
expect(secretFix).toBeDefined()
|
|
582
|
+
expect(secretFix!.fixSteps[0]).toContain('.env.local')
|
|
583
|
+
})
|
|
584
|
+
|
|
585
|
+
it('Express + Mongoose + Vue stack', () => {
|
|
586
|
+
const frameworks = createFrameworkContext({
|
|
587
|
+
primary: 'express',
|
|
588
|
+
frontend: 'vue',
|
|
589
|
+
usesTypeScript: false,
|
|
590
|
+
})
|
|
591
|
+
const dataAccess = createDataAccessContext({
|
|
592
|
+
orm: 'mongoose',
|
|
593
|
+
databaseType: 'mongodb',
|
|
594
|
+
})
|
|
595
|
+
|
|
596
|
+
// SQL injection should use Mongoose fix
|
|
597
|
+
const sqlFix = getFrameworkFix('sql_injection', frameworks, dataAccess)
|
|
598
|
+
expect(sqlFix).toBeDefined()
|
|
599
|
+
expect(sqlFix!.fixSteps[0]).toContain('Mongoose')
|
|
600
|
+
|
|
601
|
+
// XSS should use Vue fix
|
|
602
|
+
const xssFix = getFrameworkFix('xss', frameworks)
|
|
603
|
+
expect(xssFix).toBeDefined()
|
|
604
|
+
expect(xssFix!.fixSteps[0]).toContain('v-text')
|
|
605
|
+
|
|
606
|
+
// Auth should use Express fix
|
|
607
|
+
const authFix = getFrameworkFix('missing_auth', frameworks)
|
|
608
|
+
expect(authFix).toBeDefined()
|
|
609
|
+
expect(authFix!.fixSteps[0]).toContain('middleware')
|
|
610
|
+
|
|
611
|
+
// CORS should use Express fix
|
|
612
|
+
const corsFix = getFrameworkFix('cors_misconfiguration', frameworks)
|
|
613
|
+
expect(corsFix).toBeDefined()
|
|
614
|
+
expect(corsFix!.fixSteps[0]).toContain('cors')
|
|
615
|
+
})
|
|
616
|
+
|
|
617
|
+
it('Supabase + Next.js stack with RLS', () => {
|
|
618
|
+
const frameworks = createFrameworkContext({
|
|
619
|
+
primary: 'nextjs',
|
|
620
|
+
frontend: 'react',
|
|
621
|
+
})
|
|
622
|
+
const dataAccess = createDataAccessContext({
|
|
623
|
+
orm: 'supabase',
|
|
624
|
+
hasRLS: true,
|
|
625
|
+
databaseType: 'postgres',
|
|
626
|
+
})
|
|
627
|
+
|
|
628
|
+
// SQL injection should use Supabase fix
|
|
629
|
+
const sqlFix = getFrameworkFix('sql_injection', frameworks, dataAccess)
|
|
630
|
+
expect(sqlFix).toBeDefined()
|
|
631
|
+
expect(sqlFix!.fixSteps[0]).toContain('Supabase')
|
|
632
|
+
// Supabase fix should mention RLS
|
|
633
|
+
expect(sqlFix!.fixSteps.join(' ')).toContain('Row Level Security')
|
|
634
|
+
})
|
|
635
|
+
|
|
636
|
+
it('NestJS + TypeORM stack (partial coverage)', () => {
|
|
637
|
+
const frameworks = createFrameworkContext({
|
|
638
|
+
primary: 'nestjs',
|
|
639
|
+
usesTypeScript: true,
|
|
640
|
+
})
|
|
641
|
+
const dataAccess = createDataAccessContext({
|
|
642
|
+
orm: 'typeorm', // Not in registry
|
|
643
|
+
})
|
|
644
|
+
|
|
645
|
+
// SQL injection should return undefined (TypeORM not supported)
|
|
646
|
+
const sqlFix = getFrameworkFix('sql_injection', frameworks, dataAccess)
|
|
647
|
+
expect(sqlFix).toBeUndefined()
|
|
648
|
+
|
|
649
|
+
// Auth should use NestJS fix
|
|
650
|
+
const authFix = getFrameworkFix('missing_auth', frameworks)
|
|
651
|
+
expect(authFix).toBeDefined()
|
|
652
|
+
expect(authFix!.fixSteps[0]).toContain('@nestjs')
|
|
653
|
+
|
|
654
|
+
// Secrets should return undefined (NestJS not supported)
|
|
655
|
+
const secretFix = getFrameworkFix('hardcoded_secret', frameworks)
|
|
656
|
+
expect(secretFix).toBeUndefined()
|
|
657
|
+
})
|
|
658
|
+
|
|
659
|
+
it('API-only backend (no frontend framework)', () => {
|
|
660
|
+
const frameworks = createFrameworkContext({
|
|
661
|
+
primary: 'fastify',
|
|
662
|
+
frontend: undefined,
|
|
663
|
+
})
|
|
664
|
+
const dataAccess = createDataAccessContext({
|
|
665
|
+
orm: 'drizzle',
|
|
666
|
+
})
|
|
667
|
+
|
|
668
|
+
// SQL injection should use Drizzle fix
|
|
669
|
+
const sqlFix = getFrameworkFix('sql_injection', frameworks, dataAccess)
|
|
670
|
+
expect(sqlFix).toBeDefined()
|
|
671
|
+
expect(sqlFix!.fixSteps[0]).toContain('Drizzle')
|
|
672
|
+
|
|
673
|
+
// XSS should return undefined (no frontend)
|
|
674
|
+
const xssFix = getFrameworkFix('xss', frameworks)
|
|
675
|
+
expect(xssFix).toBeUndefined()
|
|
676
|
+
|
|
677
|
+
// Auth should use Fastify fix
|
|
678
|
+
const authFix = getFrameworkFix('missing_auth', frameworks)
|
|
679
|
+
expect(authFix).toBeDefined()
|
|
680
|
+
expect(authFix!.fixSteps[0]).toContain('@fastify')
|
|
681
|
+
|
|
682
|
+
// CORS should use Fastify fix
|
|
683
|
+
const corsFix = getFrameworkFix('cors_misconfiguration', frameworks)
|
|
684
|
+
expect(corsFix).toBeDefined()
|
|
685
|
+
expect(corsFix!.fixSteps[0]).toContain('@fastify/cors')
|
|
686
|
+
})
|
|
687
|
+
})
|
|
688
|
+
})
|
|
689
|
+
})
|