@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,218 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for Rule Metadata Registry (PRO-82)
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { RULE_REGISTRY, getRuleMetadata, getAllCategories, hasMetadata, type RuleMetadata } from '../metadata'
|
|
6
|
+
import type { VulnerabilityCategory } from '../../types'
|
|
7
|
+
|
|
8
|
+
// All vulnerability categories that should have metadata
|
|
9
|
+
const ALL_CATEGORIES: VulnerabilityCategory[] = [
|
|
10
|
+
'hardcoded_secret',
|
|
11
|
+
'high_entropy_string',
|
|
12
|
+
'sensitive_variable',
|
|
13
|
+
'security_bypass',
|
|
14
|
+
'dangerous_function',
|
|
15
|
+
'sql_injection',
|
|
16
|
+
'xss',
|
|
17
|
+
'command_injection',
|
|
18
|
+
'insecure_config',
|
|
19
|
+
'missing_auth',
|
|
20
|
+
'suspicious_package',
|
|
21
|
+
'cors_misconfiguration',
|
|
22
|
+
'root_container',
|
|
23
|
+
'dangerous_file',
|
|
24
|
+
'ai_pattern',
|
|
25
|
+
'sensitive_url',
|
|
26
|
+
'weak_crypto',
|
|
27
|
+
'data_exposure',
|
|
28
|
+
'ai_prompt_injection',
|
|
29
|
+
'ai_unsafe_execution',
|
|
30
|
+
'ai_overpermissive_tool',
|
|
31
|
+
'ai_rag_exfiltration',
|
|
32
|
+
'ai_endpoint_unprotected',
|
|
33
|
+
'ai_schema_mismatch',
|
|
34
|
+
// AI Detection Roadmap Phase 1
|
|
35
|
+
'ai_package_hallucination',
|
|
36
|
+
'ai_rag_corpus_poisoning',
|
|
37
|
+
'ai_rag_pii_leakage',
|
|
38
|
+
'ai_mcp_tool_poisoning',
|
|
39
|
+
'ai_mcp_credential_issue',
|
|
40
|
+
'ai_mcp_confused_deputy',
|
|
41
|
+
// Phase 1 Enhancement Backlog
|
|
42
|
+
'ai_mcp_description_injection',
|
|
43
|
+
'ai_mcp_server_shadowing',
|
|
44
|
+
'ai_mcp_config_secrets',
|
|
45
|
+
'ai_mcp_config_permissions',
|
|
46
|
+
'ai_rag_query_injection',
|
|
47
|
+
'ai_rag_embedding_poisoning',
|
|
48
|
+
'ai_rag_chunk_injection',
|
|
49
|
+
'ai_package_typosquat',
|
|
50
|
+
'ai_package_malicious',
|
|
51
|
+
// AI Detection Roadmap Phase 2
|
|
52
|
+
'ai_unsafe_model_load',
|
|
53
|
+
'ai_unverified_model',
|
|
54
|
+
'ai_unsafe_finetuning',
|
|
55
|
+
'ai_excessive_agency',
|
|
56
|
+
]
|
|
57
|
+
|
|
58
|
+
describe('Rule Metadata Registry', () => {
|
|
59
|
+
describe('RULE_REGISTRY coverage', () => {
|
|
60
|
+
it('should have metadata for all vulnerability categories', () => {
|
|
61
|
+
for (const category of ALL_CATEGORIES) {
|
|
62
|
+
expect(RULE_REGISTRY[category]).toBeDefined()
|
|
63
|
+
expect(RULE_REGISTRY[category].name).toBeTruthy()
|
|
64
|
+
}
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
it('should have all required fields for each category', () => {
|
|
68
|
+
for (const category of ALL_CATEGORIES) {
|
|
69
|
+
const metadata = RULE_REGISTRY[category]
|
|
70
|
+
|
|
71
|
+
expect(metadata.name).toBeDefined()
|
|
72
|
+
expect(typeof metadata.name).toBe('string')
|
|
73
|
+
expect(metadata.name.length).toBeGreaterThan(0)
|
|
74
|
+
|
|
75
|
+
expect(metadata.whyItMatters).toBeDefined()
|
|
76
|
+
expect(typeof metadata.whyItMatters).toBe('string')
|
|
77
|
+
expect(metadata.whyItMatters.length).toBeGreaterThan(20)
|
|
78
|
+
|
|
79
|
+
expect(metadata.fixSteps).toBeDefined()
|
|
80
|
+
expect(Array.isArray(metadata.fixSteps)).toBe(true)
|
|
81
|
+
expect(metadata.fixSteps.length).toBeGreaterThan(0)
|
|
82
|
+
|
|
83
|
+
expect(metadata.evidence).toBeDefined()
|
|
84
|
+
expect(typeof metadata.evidence).toBe('string')
|
|
85
|
+
expect(metadata.evidence.length).toBeGreaterThan(0)
|
|
86
|
+
|
|
87
|
+
expect(metadata.references).toBeDefined()
|
|
88
|
+
expect(Array.isArray(metadata.references)).toBe(true)
|
|
89
|
+
expect(metadata.references.length).toBeGreaterThan(0)
|
|
90
|
+
}
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
it('should have valid OWASP/CWE reference URLs', () => {
|
|
94
|
+
for (const category of ALL_CATEGORIES) {
|
|
95
|
+
const metadata = RULE_REGISTRY[category]
|
|
96
|
+
|
|
97
|
+
for (const ref of metadata.references) {
|
|
98
|
+
expect(ref).toMatch(/^https?:\/\//)
|
|
99
|
+
// Should be OWASP, CWE, or other recognized security references
|
|
100
|
+
expect(
|
|
101
|
+
ref.includes('owasp.org') ||
|
|
102
|
+
ref.includes('cwe.mitre.org') ||
|
|
103
|
+
ref.includes('docker.com') ||
|
|
104
|
+
ref.includes('genai.owasp.org') ||
|
|
105
|
+
ref.includes('arxiv.org') || // Academic research
|
|
106
|
+
ref.includes('modelcontextprotocol.io') || // MCP official docs
|
|
107
|
+
ref.includes('invariantlabs.ai') || // Security research
|
|
108
|
+
ref.includes('snyk.io') || // Dependency security
|
|
109
|
+
ref.includes('osv.dev') || // Open Source Vulnerabilities
|
|
110
|
+
ref.includes('socket.dev') || // npm malware research
|
|
111
|
+
ref.includes('huggingface.co') || // ML model security docs
|
|
112
|
+
ref.includes('atlas.mitre.org') || // MITRE ATLAS (AI threat matrix)
|
|
113
|
+
ref.includes('docs.crewai.com') // CrewAI security docs
|
|
114
|
+
).toBe(true)
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
})
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
describe('getRuleMetadata', () => {
|
|
121
|
+
it('should return metadata for valid category', () => {
|
|
122
|
+
const metadata = getRuleMetadata('sql_injection')
|
|
123
|
+
|
|
124
|
+
expect(metadata).toBeDefined()
|
|
125
|
+
expect(metadata?.name).toBe('SQL Injection')
|
|
126
|
+
expect(metadata?.whyItMatters).toContain('database')
|
|
127
|
+
expect(metadata?.fixSteps.length).toBeGreaterThan(0)
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
it('should return undefined for invalid category', () => {
|
|
131
|
+
// @ts-expect-error - testing invalid input
|
|
132
|
+
const metadata = getRuleMetadata('invalid_category')
|
|
133
|
+
expect(metadata).toBeUndefined()
|
|
134
|
+
})
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
describe('getAllCategories', () => {
|
|
138
|
+
it('should return all categories', () => {
|
|
139
|
+
const categories = getAllCategories()
|
|
140
|
+
|
|
141
|
+
expect(categories.length).toBe(ALL_CATEGORIES.length)
|
|
142
|
+
for (const category of ALL_CATEGORIES) {
|
|
143
|
+
expect(categories).toContain(category)
|
|
144
|
+
}
|
|
145
|
+
})
|
|
146
|
+
})
|
|
147
|
+
|
|
148
|
+
describe('hasMetadata', () => {
|
|
149
|
+
it('should return true for valid categories', () => {
|
|
150
|
+
expect(hasMetadata('hardcoded_secret')).toBe(true)
|
|
151
|
+
expect(hasMetadata('ai_prompt_injection')).toBe(true)
|
|
152
|
+
})
|
|
153
|
+
|
|
154
|
+
it('should return false for invalid categories', () => {
|
|
155
|
+
// @ts-expect-error - testing invalid input
|
|
156
|
+
expect(hasMetadata('invalid_category')).toBe(false)
|
|
157
|
+
})
|
|
158
|
+
})
|
|
159
|
+
|
|
160
|
+
describe('metadata quality', () => {
|
|
161
|
+
it('whyItMatters should explain business impact', () => {
|
|
162
|
+
// Check a few key categories have meaningful impact descriptions
|
|
163
|
+
const hardcodedSecret = RULE_REGISTRY.hardcoded_secret
|
|
164
|
+
expect(hardcodedSecret.whyItMatters).toMatch(/unauthorized|extract|access|credential/i)
|
|
165
|
+
|
|
166
|
+
const sqlInjection = RULE_REGISTRY.sql_injection
|
|
167
|
+
expect(sqlInjection.whyItMatters).toMatch(/read|modify|delete|database/i)
|
|
168
|
+
|
|
169
|
+
const missingAuth = RULE_REGISTRY.missing_auth
|
|
170
|
+
expect(missingAuth.whyItMatters).toMatch(/access|authentication|anyone/i)
|
|
171
|
+
})
|
|
172
|
+
|
|
173
|
+
it('fixSteps should be actionable', () => {
|
|
174
|
+
// Check fix steps are actionable (start with verbs)
|
|
175
|
+
for (const category of ALL_CATEGORIES) {
|
|
176
|
+
const metadata = RULE_REGISTRY[category]
|
|
177
|
+
|
|
178
|
+
for (const step of metadata.fixSteps) {
|
|
179
|
+
// Each step should start with an action verb (capitalized)
|
|
180
|
+
expect(step[0]).toBe(step[0].toUpperCase())
|
|
181
|
+
expect(step.length).toBeGreaterThan(10)
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
})
|
|
185
|
+
|
|
186
|
+
it('AI-specific categories should have OWASP LLM Top 10 references', () => {
|
|
187
|
+
const aiCategories: VulnerabilityCategory[] = [
|
|
188
|
+
'ai_prompt_injection',
|
|
189
|
+
'ai_unsafe_execution',
|
|
190
|
+
'ai_overpermissive_tool',
|
|
191
|
+
'ai_rag_exfiltration',
|
|
192
|
+
'ai_endpoint_unprotected',
|
|
193
|
+
'ai_schema_mismatch',
|
|
194
|
+
// AI Detection Roadmap Phase 1
|
|
195
|
+
'ai_rag_corpus_poisoning',
|
|
196
|
+
'ai_rag_pii_leakage',
|
|
197
|
+
'ai_mcp_tool_poisoning',
|
|
198
|
+
'ai_mcp_credential_issue',
|
|
199
|
+
'ai_mcp_confused_deputy',
|
|
200
|
+
// AI Detection Roadmap Phase 2
|
|
201
|
+
'ai_unsafe_model_load',
|
|
202
|
+
'ai_unverified_model',
|
|
203
|
+
'ai_unsafe_finetuning',
|
|
204
|
+
'ai_excessive_agency',
|
|
205
|
+
]
|
|
206
|
+
|
|
207
|
+
for (const category of aiCategories) {
|
|
208
|
+
const metadata = RULE_REGISTRY[category]
|
|
209
|
+
const hasLlmReference = metadata.references.some(
|
|
210
|
+
ref => ref.includes('genai.owasp.org') ||
|
|
211
|
+
ref.includes('large-language-model') ||
|
|
212
|
+
ref.includes('modelcontextprotocol.io') // MCP has its own docs
|
|
213
|
+
)
|
|
214
|
+
expect(hasLlmReference).toBe(true)
|
|
215
|
+
}
|
|
216
|
+
})
|
|
217
|
+
})
|
|
218
|
+
})
|
|
@@ -0,0 +1,470 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Framework-Aware Fix Suggestions Registry (PRO-83)
|
|
3
|
+
*
|
|
4
|
+
* Provides framework-specific fix suggestions that transform generic advice
|
|
5
|
+
* into actionable guidance based on the user's detected tech stack.
|
|
6
|
+
*
|
|
7
|
+
* When a Next.js + Prisma project has a SQL injection finding, this registry
|
|
8
|
+
* provides Prisma-specific fixes instead of generic SQL advice.
|
|
9
|
+
*
|
|
10
|
+
* Falls back gracefully to generic fixes (from metadata.ts) when no match.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import type { VulnerabilityCategory } from '../types'
|
|
14
|
+
import type { FrameworkContext, DataAccessContext } from '../utils/project-context-builder'
|
|
15
|
+
|
|
16
|
+
// ============================================================================
|
|
17
|
+
// Types
|
|
18
|
+
// ============================================================================
|
|
19
|
+
|
|
20
|
+
export type FrameworkKey =
|
|
21
|
+
| 'nextjs' | 'express' | 'fastify' | 'nestjs'
|
|
22
|
+
| 'prisma' | 'drizzle' | 'supabase' | 'mongoose' | 'knex'
|
|
23
|
+
| 'react' | 'vue'
|
|
24
|
+
|
|
25
|
+
export interface FrameworkFix {
|
|
26
|
+
/** Step-by-step fix instructions specific to this framework */
|
|
27
|
+
fixSteps: string[]
|
|
28
|
+
/** Optional code example demonstrating the fix */
|
|
29
|
+
codeExample?: string
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// ============================================================================
|
|
33
|
+
// Framework Fix Registry
|
|
34
|
+
// ============================================================================
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Registry mapping vulnerability categories to framework-specific fixes.
|
|
38
|
+
*
|
|
39
|
+
* Structure: category -> framework -> fix
|
|
40
|
+
*
|
|
41
|
+
* Priority categories (Tier 1):
|
|
42
|
+
* - sql_injection: Fixes vary dramatically by ORM
|
|
43
|
+
* - missing_auth: Framework-specific middleware patterns
|
|
44
|
+
* - xss: React/Vue/vanilla have different approaches
|
|
45
|
+
* - hardcoded_secret: Framework-specific env handling
|
|
46
|
+
* - cors_misconfiguration: Very framework-specific
|
|
47
|
+
*/
|
|
48
|
+
export const FRAMEWORK_FIX_REGISTRY: Partial<Record<VulnerabilityCategory, Partial<Record<FrameworkKey, FrameworkFix>>>> = {
|
|
49
|
+
// ==========================================================================
|
|
50
|
+
// SQL Injection - Fixes vary dramatically by ORM
|
|
51
|
+
// ==========================================================================
|
|
52
|
+
sql_injection: {
|
|
53
|
+
prisma: {
|
|
54
|
+
fixSteps: [
|
|
55
|
+
'Use Prisma query methods: prisma.user.findUnique({ where: { id } })',
|
|
56
|
+
'For raw SQL, use Prisma.$queryRaw with template literals (auto-parameterized)',
|
|
57
|
+
'Never concatenate user input into query strings',
|
|
58
|
+
'Validate input with Zod before passing to queries',
|
|
59
|
+
],
|
|
60
|
+
codeExample: `// Safe: Prisma parameterized query
|
|
61
|
+
const user = await prisma.user.findUnique({
|
|
62
|
+
where: { email: userInput }
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
// Safe: Raw query with parameters
|
|
66
|
+
await prisma.$queryRaw\`SELECT * FROM users WHERE id = \${userId}\``,
|
|
67
|
+
},
|
|
68
|
+
drizzle: {
|
|
69
|
+
fixSteps: [
|
|
70
|
+
'Use Drizzle query builder with eq(), and(), or() operators',
|
|
71
|
+
'For raw SQL, use sql`` template literal (auto-parameterized)',
|
|
72
|
+
'Never concatenate user input into query strings',
|
|
73
|
+
'Validate input with Zod before passing to queries',
|
|
74
|
+
],
|
|
75
|
+
codeExample: `// Safe: Drizzle query builder
|
|
76
|
+
const users = await db.select().from(usersTable)
|
|
77
|
+
.where(eq(usersTable.email, userInput))
|
|
78
|
+
|
|
79
|
+
// Safe: Raw query with parameters
|
|
80
|
+
await db.execute(sql\`SELECT * FROM users WHERE id = \${userId}\`)`,
|
|
81
|
+
},
|
|
82
|
+
supabase: {
|
|
83
|
+
fixSteps: [
|
|
84
|
+
'Use Supabase client methods: supabase.from("users").select().eq("id", userId)',
|
|
85
|
+
'Rely on Row Level Security (RLS) for access control',
|
|
86
|
+
'For complex queries, use stored procedures with parameters',
|
|
87
|
+
'Never use .rpc() with string concatenation for query building',
|
|
88
|
+
],
|
|
89
|
+
codeExample: `// Safe: Supabase client with RLS
|
|
90
|
+
const { data } = await supabase
|
|
91
|
+
.from('users')
|
|
92
|
+
.select('*')
|
|
93
|
+
.eq('email', userInput)
|
|
94
|
+
.single()`,
|
|
95
|
+
},
|
|
96
|
+
mongoose: {
|
|
97
|
+
fixSteps: [
|
|
98
|
+
'Use Mongoose query methods with conditions object: User.find({ email: userInput })',
|
|
99
|
+
'Avoid $where with user input - it allows JavaScript execution',
|
|
100
|
+
'Validate ObjectIds before querying: mongoose.Types.ObjectId.isValid(id)',
|
|
101
|
+
'Use schema validation to ensure expected data types',
|
|
102
|
+
],
|
|
103
|
+
codeExample: `// Safe: Mongoose query with conditions
|
|
104
|
+
const user = await User.findOne({ email: userInput })
|
|
105
|
+
|
|
106
|
+
// Validate ObjectId before use
|
|
107
|
+
if (!mongoose.Types.ObjectId.isValid(id)) {
|
|
108
|
+
throw new Error('Invalid ID')
|
|
109
|
+
}`,
|
|
110
|
+
},
|
|
111
|
+
knex: {
|
|
112
|
+
fixSteps: [
|
|
113
|
+
'Use Knex query builder: knex("users").where({ email: userInput })',
|
|
114
|
+
'For raw SQL, use knex.raw() with parameter binding: knex.raw("SELECT * FROM users WHERE id = ?", [userId])',
|
|
115
|
+
'Never use string concatenation in queries',
|
|
116
|
+
'Validate input types before passing to queries',
|
|
117
|
+
],
|
|
118
|
+
codeExample: `// Safe: Knex query builder
|
|
119
|
+
const user = await knex('users')
|
|
120
|
+
.where({ email: userInput })
|
|
121
|
+
.first()
|
|
122
|
+
|
|
123
|
+
// Safe: Raw query with parameters
|
|
124
|
+
await knex.raw('SELECT * FROM users WHERE id = ?', [userId])`,
|
|
125
|
+
},
|
|
126
|
+
},
|
|
127
|
+
|
|
128
|
+
// ==========================================================================
|
|
129
|
+
// Missing Authentication - Framework-specific middleware patterns
|
|
130
|
+
// ==========================================================================
|
|
131
|
+
missing_auth: {
|
|
132
|
+
nextjs: {
|
|
133
|
+
fixSteps: [
|
|
134
|
+
'Add auth check in middleware.ts with matcher config for protected routes',
|
|
135
|
+
'Call auth() or getServerSession() at the start of route handlers',
|
|
136
|
+
'Return 401/redirect for unauthenticated requests',
|
|
137
|
+
'Use throwing auth helpers (getCurrentUserId) that guarantee authenticated context',
|
|
138
|
+
],
|
|
139
|
+
codeExample: `// middleware.ts
|
|
140
|
+
export { auth as middleware } from '@/auth'
|
|
141
|
+
export const config = { matcher: ['/dashboard/:path*', '/api/:path*'] }
|
|
142
|
+
|
|
143
|
+
// In route handler
|
|
144
|
+
import { auth } from '@/auth'
|
|
145
|
+
|
|
146
|
+
export async function GET() {
|
|
147
|
+
const session = await auth()
|
|
148
|
+
if (!session) {
|
|
149
|
+
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
|
150
|
+
}
|
|
151
|
+
// ... authenticated code
|
|
152
|
+
}`,
|
|
153
|
+
},
|
|
154
|
+
express: {
|
|
155
|
+
fixSteps: [
|
|
156
|
+
'Use authentication middleware (passport, express-session, or custom)',
|
|
157
|
+
'Apply middleware to routes: app.use("/api", authMiddleware)',
|
|
158
|
+
'Verify JWT tokens or session cookies in middleware',
|
|
159
|
+
'Return 401 status for unauthenticated requests',
|
|
160
|
+
],
|
|
161
|
+
codeExample: `// authMiddleware.js
|
|
162
|
+
const authMiddleware = (req, res, next) => {
|
|
163
|
+
const token = req.headers.authorization?.split(' ')[1]
|
|
164
|
+
if (!token) {
|
|
165
|
+
return res.status(401).json({ error: 'Unauthorized' })
|
|
166
|
+
}
|
|
167
|
+
try {
|
|
168
|
+
req.user = jwt.verify(token, process.env.JWT_SECRET)
|
|
169
|
+
next()
|
|
170
|
+
} catch {
|
|
171
|
+
return res.status(401).json({ error: 'Invalid token' })
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Apply to routes
|
|
176
|
+
app.use('/api', authMiddleware)`,
|
|
177
|
+
},
|
|
178
|
+
fastify: {
|
|
179
|
+
fixSteps: [
|
|
180
|
+
'Use @fastify/auth or @fastify/jwt for authentication',
|
|
181
|
+
'Register auth plugin and apply to routes with onRequest hook',
|
|
182
|
+
'Decorate request with user info after authentication',
|
|
183
|
+
'Return 401 status for unauthenticated requests',
|
|
184
|
+
],
|
|
185
|
+
codeExample: `// Register JWT plugin
|
|
186
|
+
await fastify.register(fastifyJwt, { secret: process.env.JWT_SECRET })
|
|
187
|
+
|
|
188
|
+
// Auth decorator
|
|
189
|
+
fastify.decorate('authenticate', async (request, reply) => {
|
|
190
|
+
try {
|
|
191
|
+
await request.jwtVerify()
|
|
192
|
+
} catch (err) {
|
|
193
|
+
reply.code(401).send({ error: 'Unauthorized' })
|
|
194
|
+
}
|
|
195
|
+
})
|
|
196
|
+
|
|
197
|
+
// Protected route
|
|
198
|
+
fastify.get('/api/data', { onRequest: [fastify.authenticate] }, handler)`,
|
|
199
|
+
},
|
|
200
|
+
nestjs: {
|
|
201
|
+
fixSteps: [
|
|
202
|
+
'Use @nestjs/passport with Guards for authentication',
|
|
203
|
+
'Apply AuthGuard to controllers or routes with @UseGuards()',
|
|
204
|
+
'Implement custom guards for role-based access control',
|
|
205
|
+
'Use @Public() decorator for intentionally public endpoints',
|
|
206
|
+
],
|
|
207
|
+
codeExample: `// auth.guard.ts
|
|
208
|
+
@Injectable()
|
|
209
|
+
export class JwtAuthGuard extends AuthGuard('jwt') {}
|
|
210
|
+
|
|
211
|
+
// controller.ts
|
|
212
|
+
@Controller('api')
|
|
213
|
+
@UseGuards(JwtAuthGuard)
|
|
214
|
+
export class ApiController {
|
|
215
|
+
@Get('data')
|
|
216
|
+
getData(@Request() req) {
|
|
217
|
+
return this.service.getData(req.user.id)
|
|
218
|
+
}
|
|
219
|
+
}`,
|
|
220
|
+
},
|
|
221
|
+
},
|
|
222
|
+
|
|
223
|
+
// ==========================================================================
|
|
224
|
+
// XSS - React/Vue/vanilla have different approaches
|
|
225
|
+
// ==========================================================================
|
|
226
|
+
xss: {
|
|
227
|
+
react: {
|
|
228
|
+
fixSteps: [
|
|
229
|
+
'Use JSX expressions {variable} - React auto-escapes by default',
|
|
230
|
+
'Avoid dangerouslySetInnerHTML unless absolutely necessary',
|
|
231
|
+
'If you must use dangerouslySetInnerHTML, sanitize with DOMPurify first',
|
|
232
|
+
'Validate and sanitize user input on the server side as well',
|
|
233
|
+
],
|
|
234
|
+
codeExample: `// Safe: JSX auto-escapes
|
|
235
|
+
function Comment({ text }) {
|
|
236
|
+
return <div>{text}</div> // Safe - auto-escaped
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// If HTML is required, sanitize first
|
|
240
|
+
import DOMPurify from 'dompurify'
|
|
241
|
+
|
|
242
|
+
function RichContent({ html }) {
|
|
243
|
+
const clean = DOMPurify.sanitize(html)
|
|
244
|
+
return <div dangerouslySetInnerHTML={{ __html: clean }} />
|
|
245
|
+
}`,
|
|
246
|
+
},
|
|
247
|
+
vue: {
|
|
248
|
+
fixSteps: [
|
|
249
|
+
'Use v-text or {{ }} interpolation - Vue auto-escapes by default',
|
|
250
|
+
'Avoid v-html with user-controlled content',
|
|
251
|
+
'If you must use v-html, sanitize with DOMPurify first',
|
|
252
|
+
'Use Content Security Policy headers as additional protection',
|
|
253
|
+
],
|
|
254
|
+
codeExample: `<!-- Safe: Vue interpolation auto-escapes -->
|
|
255
|
+
<template>
|
|
256
|
+
<div>{{ userContent }}</div>
|
|
257
|
+
</template>
|
|
258
|
+
|
|
259
|
+
<!-- If HTML is required, sanitize first -->
|
|
260
|
+
<script setup>
|
|
261
|
+
import DOMPurify from 'dompurify'
|
|
262
|
+
const sanitizedHtml = computed(() => DOMPurify.sanitize(props.html))
|
|
263
|
+
</script>
|
|
264
|
+
|
|
265
|
+
<template>
|
|
266
|
+
<div v-html="sanitizedHtml"></div>
|
|
267
|
+
</template>`,
|
|
268
|
+
},
|
|
269
|
+
},
|
|
270
|
+
|
|
271
|
+
// ==========================================================================
|
|
272
|
+
// Hardcoded Secrets - Framework-specific env handling
|
|
273
|
+
// ==========================================================================
|
|
274
|
+
hardcoded_secret: {
|
|
275
|
+
nextjs: {
|
|
276
|
+
fixSteps: [
|
|
277
|
+
'Store secrets in .env.local (gitignored by default)',
|
|
278
|
+
'Access via process.env.SECRET_NAME in server components/API routes',
|
|
279
|
+
'Only use NEXT_PUBLIC_ prefix for intentionally client-exposed values',
|
|
280
|
+
'Use Vercel Environment Variables for production deployments',
|
|
281
|
+
],
|
|
282
|
+
codeExample: `// .env.local
|
|
283
|
+
DATABASE_URL=postgres://...
|
|
284
|
+
API_SECRET=your-secret-here
|
|
285
|
+
|
|
286
|
+
// Server-side code (API route, server component)
|
|
287
|
+
const secret = process.env.API_SECRET
|
|
288
|
+
|
|
289
|
+
// Client-safe public values only
|
|
290
|
+
// NEXT_PUBLIC_ANALYTICS_ID=xxx`,
|
|
291
|
+
},
|
|
292
|
+
express: {
|
|
293
|
+
fixSteps: [
|
|
294
|
+
'Use dotenv package to load from .env files',
|
|
295
|
+
'Add .env to .gitignore immediately',
|
|
296
|
+
'Access secrets via process.env.SECRET_NAME',
|
|
297
|
+
'Use different .env files per environment (.env.production)',
|
|
298
|
+
],
|
|
299
|
+
codeExample: `// At app entry point
|
|
300
|
+
import 'dotenv/config'
|
|
301
|
+
|
|
302
|
+
// .env (gitignored)
|
|
303
|
+
DATABASE_URL=postgres://...
|
|
304
|
+
JWT_SECRET=your-secret-here
|
|
305
|
+
|
|
306
|
+
// Access in code
|
|
307
|
+
const jwtSecret = process.env.JWT_SECRET
|
|
308
|
+
if (!jwtSecret) throw new Error('JWT_SECRET required')`,
|
|
309
|
+
},
|
|
310
|
+
},
|
|
311
|
+
|
|
312
|
+
// ==========================================================================
|
|
313
|
+
// CORS Misconfiguration - Very framework-specific
|
|
314
|
+
// ==========================================================================
|
|
315
|
+
cors_misconfiguration: {
|
|
316
|
+
nextjs: {
|
|
317
|
+
fixSteps: [
|
|
318
|
+
'Configure CORS in next.config.js headers or middleware',
|
|
319
|
+
'Specify exact allowed origins instead of wildcard "*"',
|
|
320
|
+
'Use environment variables for origin configuration',
|
|
321
|
+
'Consider using middleware for dynamic origin validation',
|
|
322
|
+
],
|
|
323
|
+
codeExample: `// next.config.js
|
|
324
|
+
module.exports = {
|
|
325
|
+
async headers() {
|
|
326
|
+
return [{
|
|
327
|
+
source: '/api/:path*',
|
|
328
|
+
headers: [
|
|
329
|
+
{ key: 'Access-Control-Allow-Origin', value: process.env.ALLOWED_ORIGIN },
|
|
330
|
+
{ key: 'Access-Control-Allow-Methods', value: 'GET,POST,PUT,DELETE' },
|
|
331
|
+
{ key: 'Access-Control-Allow-Headers', value: 'Content-Type,Authorization' },
|
|
332
|
+
],
|
|
333
|
+
}]
|
|
334
|
+
},
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// Or in middleware.ts for dynamic validation
|
|
338
|
+
const allowedOrigins = ['https://app.example.com', 'https://admin.example.com']`,
|
|
339
|
+
},
|
|
340
|
+
express: {
|
|
341
|
+
fixSteps: [
|
|
342
|
+
'Use the cors package with specific origin configuration',
|
|
343
|
+
'Replace origin: "*" with an allowlist of origins',
|
|
344
|
+
'Use a function for dynamic origin validation',
|
|
345
|
+
'Disable credentials for cross-origin requests unless necessary',
|
|
346
|
+
],
|
|
347
|
+
codeExample: `import cors from 'cors'
|
|
348
|
+
|
|
349
|
+
const allowedOrigins = ['https://app.example.com', 'https://admin.example.com']
|
|
350
|
+
|
|
351
|
+
app.use(cors({
|
|
352
|
+
origin: (origin, callback) => {
|
|
353
|
+
if (!origin || allowedOrigins.includes(origin)) {
|
|
354
|
+
callback(null, true)
|
|
355
|
+
} else {
|
|
356
|
+
callback(new Error('Not allowed by CORS'))
|
|
357
|
+
}
|
|
358
|
+
},
|
|
359
|
+
credentials: true, // Only if you need cookies/auth headers
|
|
360
|
+
}))`,
|
|
361
|
+
},
|
|
362
|
+
fastify: {
|
|
363
|
+
fixSteps: [
|
|
364
|
+
'Use @fastify/cors with specific origin configuration',
|
|
365
|
+
'Specify allowed origins instead of true/wildcard',
|
|
366
|
+
'Use a function for dynamic origin validation',
|
|
367
|
+
'Configure allowed methods and headers explicitly',
|
|
368
|
+
],
|
|
369
|
+
codeExample: `import cors from '@fastify/cors'
|
|
370
|
+
|
|
371
|
+
const allowedOrigins = ['https://app.example.com']
|
|
372
|
+
|
|
373
|
+
await fastify.register(cors, {
|
|
374
|
+
origin: (origin, cb) => {
|
|
375
|
+
if (!origin || allowedOrigins.includes(origin)) {
|
|
376
|
+
cb(null, true)
|
|
377
|
+
} else {
|
|
378
|
+
cb(new Error('Not allowed'), false)
|
|
379
|
+
}
|
|
380
|
+
},
|
|
381
|
+
credentials: true,
|
|
382
|
+
})`,
|
|
383
|
+
},
|
|
384
|
+
},
|
|
385
|
+
|
|
386
|
+
// ==========================================================================
|
|
387
|
+
// Dangerous Function (eval, innerHTML) - Context-specific
|
|
388
|
+
// ==========================================================================
|
|
389
|
+
dangerous_function: {
|
|
390
|
+
react: {
|
|
391
|
+
fixSteps: [
|
|
392
|
+
'Replace innerHTML with React JSX (auto-escapes content)',
|
|
393
|
+
'Use textContent or innerText for plain text updates',
|
|
394
|
+
'If dynamic HTML is required, use DOMPurify before dangerouslySetInnerHTML',
|
|
395
|
+
'Consider using a markdown renderer (react-markdown) for rich content',
|
|
396
|
+
],
|
|
397
|
+
codeExample: `// Instead of innerHTML
|
|
398
|
+
const element = document.getElementById('content')
|
|
399
|
+
element.innerHTML = userInput // Dangerous!
|
|
400
|
+
|
|
401
|
+
// Use React JSX
|
|
402
|
+
function SafeContent({ content }) {
|
|
403
|
+
return <div>{content}</div> // Safe - auto-escaped
|
|
404
|
+
}`,
|
|
405
|
+
},
|
|
406
|
+
vue: {
|
|
407
|
+
fixSteps: [
|
|
408
|
+
'Use Vue template interpolation {{ }} instead of v-html',
|
|
409
|
+
'If HTML rendering is required, sanitize with DOMPurify first',
|
|
410
|
+
'Consider component-based approaches for dynamic content',
|
|
411
|
+
'Validate and sanitize on the server before sending to client',
|
|
412
|
+
],
|
|
413
|
+
},
|
|
414
|
+
},
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
// ============================================================================
|
|
418
|
+
// Lookup Function
|
|
419
|
+
// ============================================================================
|
|
420
|
+
|
|
421
|
+
/**
|
|
422
|
+
* Get framework-specific fix for a vulnerability category.
|
|
423
|
+
*
|
|
424
|
+
* Priority order for ORM-related categories (sql_injection):
|
|
425
|
+
* 1. ORM (prisma, drizzle, supabase, mongoose, knex)
|
|
426
|
+
* 2. Backend framework (nextjs, express, fastify, nestjs)
|
|
427
|
+
*
|
|
428
|
+
* Priority order for other categories:
|
|
429
|
+
* 1. Frontend framework for XSS (react, vue)
|
|
430
|
+
* 2. Backend framework for auth/cors/secrets
|
|
431
|
+
*
|
|
432
|
+
* @returns FrameworkFix if a match is found, undefined otherwise (falls back to generic)
|
|
433
|
+
*/
|
|
434
|
+
export function getFrameworkFix(
|
|
435
|
+
category: VulnerabilityCategory,
|
|
436
|
+
frameworks: FrameworkContext,
|
|
437
|
+
dataAccess?: DataAccessContext
|
|
438
|
+
): FrameworkFix | undefined {
|
|
439
|
+
const categoryFixes = FRAMEWORK_FIX_REGISTRY[category]
|
|
440
|
+
if (!categoryFixes) {
|
|
441
|
+
return undefined
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
// For SQL injection, prioritize ORM-specific fixes
|
|
445
|
+
if (category === 'sql_injection' && dataAccess?.orm) {
|
|
446
|
+
const ormFix = categoryFixes[dataAccess.orm as FrameworkKey]
|
|
447
|
+
if (ormFix) {
|
|
448
|
+
return ormFix
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
// For XSS and dangerous_function, prioritize frontend framework
|
|
453
|
+
if ((category === 'xss' || category === 'dangerous_function') && frameworks.frontend) {
|
|
454
|
+
const frontendFix = categoryFixes[frameworks.frontend as FrameworkKey]
|
|
455
|
+
if (frontendFix) {
|
|
456
|
+
return frontendFix
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
// For other categories, try backend framework
|
|
461
|
+
if (frameworks.primary) {
|
|
462
|
+
const backendFix = categoryFixes[frameworks.primary as FrameworkKey]
|
|
463
|
+
if (backendFix) {
|
|
464
|
+
return backendFix
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
// No framework-specific fix found
|
|
469
|
+
return undefined
|
|
470
|
+
}
|