@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,439 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Refactor Safety Snapshot Tests for dangerous-functions.ts
|
|
3
|
+
*
|
|
4
|
+
* These tests capture the current behavior of detectDangerousFunctions()
|
|
5
|
+
* to ensure refactoring doesn't change any detection behavior.
|
|
6
|
+
*
|
|
7
|
+
* DO NOT modify these tests unless intentionally changing detection behavior.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { detectDangerousFunctions } from '../../layer2/dangerous-functions'
|
|
11
|
+
|
|
12
|
+
describe('Refactor Safety - dangerous-functions.ts', () => {
|
|
13
|
+
describe('eval/Function detection', () => {
|
|
14
|
+
it('should detect eval() usage', () => {
|
|
15
|
+
const code = `
|
|
16
|
+
const result = eval(userInput);
|
|
17
|
+
const computed = eval('2 + 2');
|
|
18
|
+
`
|
|
19
|
+
const result = detectDangerousFunctions(code, 'src/handler.ts')
|
|
20
|
+
expect(result).toMatchSnapshot()
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
it('should detect Function constructor', () => {
|
|
24
|
+
const code = `
|
|
25
|
+
const fn = new Function('a', 'b', 'return a + b');
|
|
26
|
+
const dynamic = new Function(userCode);
|
|
27
|
+
`
|
|
28
|
+
const result = detectDangerousFunctions(code, 'src/handler.ts')
|
|
29
|
+
expect(result).toMatchSnapshot()
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
it('should detect setTimeout/setInterval with string', () => {
|
|
33
|
+
const code = `
|
|
34
|
+
setTimeout('alert("hello")', 1000);
|
|
35
|
+
setInterval('console.log("tick")', 500);
|
|
36
|
+
`
|
|
37
|
+
const result = detectDangerousFunctions(code, 'src/handler.ts')
|
|
38
|
+
expect(result).toMatchSnapshot()
|
|
39
|
+
})
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
describe('child_process detection', () => {
|
|
43
|
+
it('should detect exec with user input', () => {
|
|
44
|
+
const code = `
|
|
45
|
+
import { exec } from 'child_process';
|
|
46
|
+
exec(\`ls \${userInput}\`, callback);
|
|
47
|
+
`
|
|
48
|
+
const result = detectDangerousFunctions(code, 'src/handler.ts')
|
|
49
|
+
expect(result).toMatchSnapshot()
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
it('should detect execSync with static command', () => {
|
|
53
|
+
const code = `
|
|
54
|
+
const { execSync } = require('child_process');
|
|
55
|
+
const output = execSync('ls -la');
|
|
56
|
+
`
|
|
57
|
+
const result = detectDangerousFunctions(code, 'src/handler.ts')
|
|
58
|
+
expect(result).toMatchSnapshot()
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
it('should NOT flag RegExp.exec as child_process', () => {
|
|
62
|
+
const code = `
|
|
63
|
+
const regex = /pattern/g;
|
|
64
|
+
const match = regex.exec(text);
|
|
65
|
+
`
|
|
66
|
+
const result = detectDangerousFunctions(code, 'src/handler.ts')
|
|
67
|
+
expect(result).toMatchSnapshot()
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
it('should detect spawn with dynamic args', () => {
|
|
71
|
+
const code = `
|
|
72
|
+
import { spawn } from 'child_process';
|
|
73
|
+
spawn('node', [userInput]);
|
|
74
|
+
`
|
|
75
|
+
const result = detectDangerousFunctions(code, 'src/handler.ts')
|
|
76
|
+
expect(result).toMatchSnapshot()
|
|
77
|
+
})
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
describe('SQL injection detection', () => {
|
|
81
|
+
it('should detect raw SQL with template literal', () => {
|
|
82
|
+
const code = `
|
|
83
|
+
const query = \`SELECT * FROM users WHERE id = \${userId}\`;
|
|
84
|
+
db.query(query);
|
|
85
|
+
`
|
|
86
|
+
const result = detectDangerousFunctions(code, 'src/db.ts')
|
|
87
|
+
expect(result).toMatchSnapshot()
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
it('should detect SQL string concatenation', () => {
|
|
91
|
+
const code = `
|
|
92
|
+
const query = "SELECT * FROM users WHERE name = '" + userName + "'";
|
|
93
|
+
`
|
|
94
|
+
const result = detectDangerousFunctions(code, 'src/db.ts')
|
|
95
|
+
expect(result).toMatchSnapshot()
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
it('should detect SQL with whitelist validation as lower severity', () => {
|
|
99
|
+
const code = `
|
|
100
|
+
const allowedColumns = ['name', 'email', 'age'];
|
|
101
|
+
if (!allowedColumns.includes(sortBy)) throw new Error('Invalid column');
|
|
102
|
+
const query = \`SELECT * FROM users ORDER BY \${sortBy}\`;
|
|
103
|
+
`
|
|
104
|
+
const result = detectDangerousFunctions(code, 'src/db.ts')
|
|
105
|
+
expect(result).toMatchSnapshot()
|
|
106
|
+
})
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
describe('innerHTML/XSS detection', () => {
|
|
110
|
+
it('should detect innerHTML with dynamic content', () => {
|
|
111
|
+
const code = `
|
|
112
|
+
element.innerHTML = userContent;
|
|
113
|
+
div.innerHTML = \`<div>\${data}</div>\`;
|
|
114
|
+
`
|
|
115
|
+
const result = detectDangerousFunctions(code, 'src/component.ts')
|
|
116
|
+
expect(result).toMatchSnapshot()
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
it('should detect innerHTML with static content as lower severity', () => {
|
|
120
|
+
const code = `
|
|
121
|
+
element.innerHTML = '<div class="container">Static content</div>';
|
|
122
|
+
`
|
|
123
|
+
const result = detectDangerousFunctions(code, 'src/component.ts')
|
|
124
|
+
expect(result).toMatchSnapshot()
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
it('should detect dangerouslySetInnerHTML', () => {
|
|
128
|
+
const code = `
|
|
129
|
+
<div dangerouslySetInnerHTML={{ __html: userHtml }} />
|
|
130
|
+
`
|
|
131
|
+
const result = detectDangerousFunctions(code, 'src/component.tsx')
|
|
132
|
+
expect(result).toMatchSnapshot()
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
it('should detect innerHTML with DOMPurify as lower severity', () => {
|
|
136
|
+
const code = `
|
|
137
|
+
import DOMPurify from 'dompurify';
|
|
138
|
+
const sanitized = DOMPurify.sanitize(userHtml);
|
|
139
|
+
element.innerHTML = sanitized;
|
|
140
|
+
`
|
|
141
|
+
const result = detectDangerousFunctions(code, 'src/component.ts')
|
|
142
|
+
expect(result).toMatchSnapshot()
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
it('should skip style element innerHTML', () => {
|
|
146
|
+
const code = `
|
|
147
|
+
const styleElement = document.createElement('style');
|
|
148
|
+
styleElement.innerHTML = '.class { color: red; }';
|
|
149
|
+
`
|
|
150
|
+
const result = detectDangerousFunctions(code, 'src/styles.ts')
|
|
151
|
+
expect(result).toMatchSnapshot()
|
|
152
|
+
})
|
|
153
|
+
|
|
154
|
+
it('should detect document.write', () => {
|
|
155
|
+
const code = `
|
|
156
|
+
document.write('<script>alert(1)</script>');
|
|
157
|
+
`
|
|
158
|
+
const result = detectDangerousFunctions(code, 'src/legacy.ts')
|
|
159
|
+
expect(result).toMatchSnapshot()
|
|
160
|
+
})
|
|
161
|
+
})
|
|
162
|
+
|
|
163
|
+
describe('JSON.parse detection', () => {
|
|
164
|
+
it('should detect JSON.parse on user input', () => {
|
|
165
|
+
const code = `
|
|
166
|
+
const data = JSON.parse(req.body);
|
|
167
|
+
`
|
|
168
|
+
const result = detectDangerousFunctions(code, 'src/api/handler.ts')
|
|
169
|
+
expect(result).toMatchSnapshot()
|
|
170
|
+
})
|
|
171
|
+
|
|
172
|
+
it('should detect JSON.parse on localStorage', () => {
|
|
173
|
+
const code = `
|
|
174
|
+
const settings = JSON.parse(localStorage.getItem('settings'));
|
|
175
|
+
`
|
|
176
|
+
const result = detectDangerousFunctions(code, 'src/settings.ts')
|
|
177
|
+
expect(result).toMatchSnapshot()
|
|
178
|
+
})
|
|
179
|
+
|
|
180
|
+
it('should suppress JSON.parse with try-catch on internal data', () => {
|
|
181
|
+
const code = `
|
|
182
|
+
try {
|
|
183
|
+
const config = JSON.parse(fileContent);
|
|
184
|
+
} catch (e) {
|
|
185
|
+
console.error(e);
|
|
186
|
+
}
|
|
187
|
+
`
|
|
188
|
+
const result = detectDangerousFunctions(code, 'src/config.ts')
|
|
189
|
+
expect(result).toMatchSnapshot()
|
|
190
|
+
})
|
|
191
|
+
|
|
192
|
+
it('should detect JSON.parse with schema validation as handled', () => {
|
|
193
|
+
const code = `
|
|
194
|
+
const raw = JSON.parse(input);
|
|
195
|
+
const validated = schema.parse(raw);
|
|
196
|
+
`
|
|
197
|
+
const result = detectDangerousFunctions(code, 'src/api/handler.ts')
|
|
198
|
+
expect(result).toMatchSnapshot()
|
|
199
|
+
})
|
|
200
|
+
|
|
201
|
+
it('should skip JSON.parse in test files', () => {
|
|
202
|
+
const code = `
|
|
203
|
+
const mockData = JSON.parse(fixture);
|
|
204
|
+
`
|
|
205
|
+
const result = detectDangerousFunctions(code, 'src/__tests__/handler.test.ts')
|
|
206
|
+
expect(result).toMatchSnapshot()
|
|
207
|
+
})
|
|
208
|
+
|
|
209
|
+
it('should skip trusted SDK response parsing', () => {
|
|
210
|
+
const code = `
|
|
211
|
+
const response = await openai.chat.completions.create({});
|
|
212
|
+
const content = JSON.parse(response.choices[0].content);
|
|
213
|
+
`
|
|
214
|
+
const result = detectDangerousFunctions(code, 'src/ai/chat.ts')
|
|
215
|
+
expect(result).toMatchSnapshot()
|
|
216
|
+
})
|
|
217
|
+
})
|
|
218
|
+
|
|
219
|
+
describe('Math.random detection', () => {
|
|
220
|
+
it('should detect Math.random for security tokens as high', () => {
|
|
221
|
+
const code = `
|
|
222
|
+
function generateToken() {
|
|
223
|
+
return Math.random().toString(36);
|
|
224
|
+
}
|
|
225
|
+
`
|
|
226
|
+
const result = detectDangerousFunctions(code, 'src/auth.ts')
|
|
227
|
+
expect(result).toMatchSnapshot()
|
|
228
|
+
})
|
|
229
|
+
|
|
230
|
+
it('should detect Math.random in security context as high', () => {
|
|
231
|
+
const code = `
|
|
232
|
+
const secret = Math.random().toString(36).substring(2);
|
|
233
|
+
`
|
|
234
|
+
const result = detectDangerousFunctions(code, 'src/crypto.ts')
|
|
235
|
+
expect(result).toMatchSnapshot()
|
|
236
|
+
})
|
|
237
|
+
|
|
238
|
+
it('should classify Math.random for UI IDs as info', () => {
|
|
239
|
+
const code = `
|
|
240
|
+
const key = Math.random().toString(36).substring(2, 9);
|
|
241
|
+
`
|
|
242
|
+
const result = detectDangerousFunctions(code, 'src/components/list.tsx')
|
|
243
|
+
expect(result).toMatchSnapshot()
|
|
244
|
+
})
|
|
245
|
+
|
|
246
|
+
it('should skip Math.random in seed files', () => {
|
|
247
|
+
const code = `
|
|
248
|
+
const randomAge = Math.random() * 100;
|
|
249
|
+
`
|
|
250
|
+
const result = detectDangerousFunctions(code, 'src/seed/users.ts')
|
|
251
|
+
expect(result).toMatchSnapshot()
|
|
252
|
+
})
|
|
253
|
+
|
|
254
|
+
it('should skip cosmetic Math.random (CSS values)', () => {
|
|
255
|
+
const code = `
|
|
256
|
+
const opacity = Math.random() * 0.5 + 0.5;
|
|
257
|
+
element.style.opacity = opacity;
|
|
258
|
+
`
|
|
259
|
+
const result = detectDangerousFunctions(code, 'src/animation.ts')
|
|
260
|
+
expect(result).toMatchSnapshot()
|
|
261
|
+
})
|
|
262
|
+
|
|
263
|
+
it('should classify business IDs as low severity', () => {
|
|
264
|
+
const code = `
|
|
265
|
+
const orderId = 'ORD-' + Math.random().toString(36).substring(2, 12);
|
|
266
|
+
`
|
|
267
|
+
const result = detectDangerousFunctions(code, 'src/orders.ts')
|
|
268
|
+
expect(result).toMatchSnapshot()
|
|
269
|
+
})
|
|
270
|
+
})
|
|
271
|
+
|
|
272
|
+
describe('File path detection', () => {
|
|
273
|
+
it('should detect dynamic file path in API handlers', () => {
|
|
274
|
+
const code = `
|
|
275
|
+
const file = fs.readFileSync(req.params.filename);
|
|
276
|
+
`
|
|
277
|
+
const result = detectDangerousFunctions(code, 'src/api/files.ts')
|
|
278
|
+
expect(result).toMatchSnapshot()
|
|
279
|
+
})
|
|
280
|
+
|
|
281
|
+
it('should detect path traversal risk', () => {
|
|
282
|
+
const code = `
|
|
283
|
+
const fullPath = path.join(uploadDir, req.query.path);
|
|
284
|
+
`
|
|
285
|
+
const result = detectDangerousFunctions(code, 'src/api/upload.ts')
|
|
286
|
+
expect(result).toMatchSnapshot()
|
|
287
|
+
})
|
|
288
|
+
|
|
289
|
+
it('should skip dynamic paths in CLI tools', () => {
|
|
290
|
+
const code = `
|
|
291
|
+
const content = fs.readFileSync(filePath);
|
|
292
|
+
`
|
|
293
|
+
const result = detectDangerousFunctions(code, 'src/cli/scan.ts')
|
|
294
|
+
expect(result).toMatchSnapshot()
|
|
295
|
+
})
|
|
296
|
+
|
|
297
|
+
it('should detect path traversal with sanitization as lower severity', () => {
|
|
298
|
+
const code = `
|
|
299
|
+
if (filename.includes('..')) throw new Error('Invalid path');
|
|
300
|
+
const fullPath = path.resolve(baseDir, filename);
|
|
301
|
+
if (!fullPath.startsWith(baseDir)) throw new Error('Access denied');
|
|
302
|
+
fs.readFileSync(fullPath);
|
|
303
|
+
`
|
|
304
|
+
const result = detectDangerousFunctions(code, 'src/api/files.ts')
|
|
305
|
+
expect(result).toMatchSnapshot()
|
|
306
|
+
})
|
|
307
|
+
})
|
|
308
|
+
|
|
309
|
+
describe('request.json() validation detection', () => {
|
|
310
|
+
it('should suggest schema validation for request.json()', () => {
|
|
311
|
+
const code = `
|
|
312
|
+
export async function POST(request: Request) {
|
|
313
|
+
const body = await request.json();
|
|
314
|
+
return Response.json({ ok: true });
|
|
315
|
+
}
|
|
316
|
+
`
|
|
317
|
+
const result = detectDangerousFunctions(code, 'src/app/api/create/route.ts')
|
|
318
|
+
expect(result).toMatchSnapshot()
|
|
319
|
+
})
|
|
320
|
+
|
|
321
|
+
it('should skip when schema validation is present', () => {
|
|
322
|
+
const code = `
|
|
323
|
+
import { z } from 'zod';
|
|
324
|
+
const schema = z.object({ name: z.string() });
|
|
325
|
+
|
|
326
|
+
export async function POST(request: Request) {
|
|
327
|
+
const body = await request.json();
|
|
328
|
+
const data = schema.parse(body);
|
|
329
|
+
return Response.json({ data });
|
|
330
|
+
}
|
|
331
|
+
`
|
|
332
|
+
const result = detectDangerousFunctions(code, 'src/app/api/create/route.ts')
|
|
333
|
+
expect(result).toMatchSnapshot()
|
|
334
|
+
})
|
|
335
|
+
|
|
336
|
+
it('should skip when throwing auth helper is present', () => {
|
|
337
|
+
const code = `
|
|
338
|
+
export async function POST(request: Request) {
|
|
339
|
+
const userId = await getCurrentUserId();
|
|
340
|
+
const body = await request.json();
|
|
341
|
+
return Response.json({ ok: true });
|
|
342
|
+
}
|
|
343
|
+
`
|
|
344
|
+
const result = detectDangerousFunctions(code, 'src/app/api/create/route.ts')
|
|
345
|
+
expect(result).toMatchSnapshot()
|
|
346
|
+
})
|
|
347
|
+
})
|
|
348
|
+
|
|
349
|
+
describe('Deserialization detection', () => {
|
|
350
|
+
it('should detect pickle.loads (Python)', () => {
|
|
351
|
+
const code = `
|
|
352
|
+
data = pickle.loads(user_data)
|
|
353
|
+
`
|
|
354
|
+
const result = detectDangerousFunctions(code, 'handler.py')
|
|
355
|
+
expect(result).toMatchSnapshot()
|
|
356
|
+
})
|
|
357
|
+
|
|
358
|
+
it('should detect yaml.load without safe loader', () => {
|
|
359
|
+
const code = `
|
|
360
|
+
config = yaml.load(file_content)
|
|
361
|
+
`
|
|
362
|
+
const result = detectDangerousFunctions(code, 'config.py')
|
|
363
|
+
expect(result).toMatchSnapshot()
|
|
364
|
+
})
|
|
365
|
+
})
|
|
366
|
+
|
|
367
|
+
describe('Prototype pollution detection', () => {
|
|
368
|
+
it('should detect Object.assign with user input', () => {
|
|
369
|
+
const code = `
|
|
370
|
+
const merged = Object.assign({}, req.body);
|
|
371
|
+
`
|
|
372
|
+
const result = detectDangerousFunctions(code, 'src/api/merge.ts')
|
|
373
|
+
expect(result).toMatchSnapshot()
|
|
374
|
+
})
|
|
375
|
+
|
|
376
|
+
it('should detect spread operator with user input', () => {
|
|
377
|
+
const code = `
|
|
378
|
+
const user = { ...req.body, createdAt: new Date() };
|
|
379
|
+
`
|
|
380
|
+
const result = detectDangerousFunctions(code, 'src/api/create.ts')
|
|
381
|
+
expect(result).toMatchSnapshot()
|
|
382
|
+
})
|
|
383
|
+
})
|
|
384
|
+
|
|
385
|
+
describe('Regex DoS detection', () => {
|
|
386
|
+
it('should detect dynamic regex construction', () => {
|
|
387
|
+
const code = `
|
|
388
|
+
const pattern = new RegExp(userPattern);
|
|
389
|
+
`
|
|
390
|
+
const result = detectDangerousFunctions(code, 'src/search.ts')
|
|
391
|
+
expect(result).toMatchSnapshot()
|
|
392
|
+
})
|
|
393
|
+
})
|
|
394
|
+
|
|
395
|
+
describe('Test file handling', () => {
|
|
396
|
+
it('should downgrade severity in test files', () => {
|
|
397
|
+
const code = `
|
|
398
|
+
eval(testCode);
|
|
399
|
+
element.innerHTML = testHtml;
|
|
400
|
+
`
|
|
401
|
+
const result = detectDangerousFunctions(code, 'src/__tests__/handler.test.ts')
|
|
402
|
+
expect(result).toMatchSnapshot()
|
|
403
|
+
})
|
|
404
|
+
|
|
405
|
+
it('should skip eval entirely in test files', () => {
|
|
406
|
+
const code = `
|
|
407
|
+
it('should handle eval', () => {
|
|
408
|
+
expect(eval('2 + 2')).toBe(4);
|
|
409
|
+
});
|
|
410
|
+
`
|
|
411
|
+
const result = detectDangerousFunctions(code, 'src/__tests__/eval.test.ts')
|
|
412
|
+
expect(result).toMatchSnapshot()
|
|
413
|
+
})
|
|
414
|
+
})
|
|
415
|
+
|
|
416
|
+
describe('LLM prompt context detection', () => {
|
|
417
|
+
it('should detect LLM prompt injection risk instead of XSS', () => {
|
|
418
|
+
const code = `
|
|
419
|
+
const prompt = \`Answer this: \${userQuestion}\`;
|
|
420
|
+
const response = await openai.chat.completions.create({
|
|
421
|
+
messages: [{ role: 'user', content: prompt }]
|
|
422
|
+
});
|
|
423
|
+
`
|
|
424
|
+
const result = detectDangerousFunctions(code, 'src/ai/chat.ts')
|
|
425
|
+
expect(result).toMatchSnapshot()
|
|
426
|
+
})
|
|
427
|
+
})
|
|
428
|
+
|
|
429
|
+
describe('Static bootstrap script detection', () => {
|
|
430
|
+
it('should detect static bootstrap as lower severity', () => {
|
|
431
|
+
const code = `
|
|
432
|
+
const theme = localStorage.getItem('theme');
|
|
433
|
+
document.documentElement.setAttribute('data-theme', theme || 'light');
|
|
434
|
+
`
|
|
435
|
+
const result = detectDangerousFunctions(code, 'src/bootstrap.ts')
|
|
436
|
+
expect(result).toMatchSnapshot()
|
|
437
|
+
})
|
|
438
|
+
})
|
|
439
|
+
})
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Baseline Diff Computation Tests
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { computeDiff, hasNewBlockingIssues, formatDiffSummary } from '../diff'
|
|
6
|
+
import type { Vulnerability } from '../../types'
|
|
7
|
+
import type { BaselineData } from '../types'
|
|
8
|
+
|
|
9
|
+
describe('Baseline Diff Computation', () => {
|
|
10
|
+
// Helper to create a test vulnerability
|
|
11
|
+
const createVuln = (
|
|
12
|
+
filePath: string,
|
|
13
|
+
lineNumber: number,
|
|
14
|
+
category: string,
|
|
15
|
+
severity: 'critical' | 'high' | 'medium' | 'low' | 'info' = 'medium',
|
|
16
|
+
title: string = 'Test finding'
|
|
17
|
+
): Vulnerability => ({
|
|
18
|
+
filePath,
|
|
19
|
+
lineNumber,
|
|
20
|
+
category: category as any,
|
|
21
|
+
severity,
|
|
22
|
+
title,
|
|
23
|
+
description: 'Test description',
|
|
24
|
+
confidence: 'high',
|
|
25
|
+
layer: 1,
|
|
26
|
+
lineContent: `Line ${lineNumber} content`,
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
// Helper to create a baseline
|
|
30
|
+
const createBaseline = (findings: Array<{
|
|
31
|
+
hash: string
|
|
32
|
+
filePath: string
|
|
33
|
+
lineNumber: number
|
|
34
|
+
category: string
|
|
35
|
+
severity: string
|
|
36
|
+
title: string
|
|
37
|
+
}>): BaselineData => ({
|
|
38
|
+
version: 1,
|
|
39
|
+
createdAt: '2024-01-15T10:00:00.000Z',
|
|
40
|
+
findings: findings as any,
|
|
41
|
+
stats: {
|
|
42
|
+
total: findings.length,
|
|
43
|
+
critical: findings.filter(f => f.severity === 'critical').length,
|
|
44
|
+
high: findings.filter(f => f.severity === 'high').length,
|
|
45
|
+
medium: findings.filter(f => f.severity === 'medium').length,
|
|
46
|
+
low: findings.filter(f => f.severity === 'low').length,
|
|
47
|
+
info: findings.filter(f => f.severity === 'info').length,
|
|
48
|
+
},
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
describe('computeDiff', () => {
|
|
52
|
+
it('should identify all findings as new when baseline is empty', () => {
|
|
53
|
+
const current = [
|
|
54
|
+
createVuln('src/a.ts', 10, 'hardcoded_secret', 'critical'),
|
|
55
|
+
createVuln('src/b.ts', 20, 'sql_injection', 'high'),
|
|
56
|
+
]
|
|
57
|
+
const baseline = createBaseline([])
|
|
58
|
+
|
|
59
|
+
const diff = computeDiff(current, baseline)
|
|
60
|
+
|
|
61
|
+
expect(diff.new).toHaveLength(2)
|
|
62
|
+
expect(diff.fixed).toHaveLength(0)
|
|
63
|
+
expect(diff.existing).toHaveLength(0)
|
|
64
|
+
expect(diff.stats.newCount).toBe(2)
|
|
65
|
+
expect(diff.stats.fixedCount).toBe(0)
|
|
66
|
+
expect(diff.stats.existingCount).toBe(0)
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
it('should identify all findings as existing when current matches baseline', () => {
|
|
70
|
+
const current = [
|
|
71
|
+
createVuln('src/a.ts', 10, 'hardcoded_secret', 'critical', 'API key'),
|
|
72
|
+
]
|
|
73
|
+
|
|
74
|
+
// Need to compute the hash that would match
|
|
75
|
+
const { computeFindingHash } = require('../../suppression/hash')
|
|
76
|
+
const hash = computeFindingHash(current[0])
|
|
77
|
+
|
|
78
|
+
const baseline = createBaseline([
|
|
79
|
+
{
|
|
80
|
+
hash,
|
|
81
|
+
filePath: 'src/a.ts',
|
|
82
|
+
lineNumber: 10,
|
|
83
|
+
category: 'hardcoded_secret',
|
|
84
|
+
severity: 'critical',
|
|
85
|
+
title: 'API key',
|
|
86
|
+
},
|
|
87
|
+
])
|
|
88
|
+
|
|
89
|
+
const diff = computeDiff(current, baseline)
|
|
90
|
+
|
|
91
|
+
expect(diff.new).toHaveLength(0)
|
|
92
|
+
expect(diff.fixed).toHaveLength(0)
|
|
93
|
+
expect(diff.existing).toHaveLength(1)
|
|
94
|
+
expect(diff.stats.existingCount).toBe(1)
|
|
95
|
+
})
|
|
96
|
+
|
|
97
|
+
it('should identify fixed findings when they exist in baseline but not current', () => {
|
|
98
|
+
const current: Vulnerability[] = []
|
|
99
|
+
const baseline = createBaseline([
|
|
100
|
+
{
|
|
101
|
+
hash: 'fixedhash1234567',
|
|
102
|
+
filePath: 'src/fixed.ts',
|
|
103
|
+
lineNumber: 5,
|
|
104
|
+
category: 'hardcoded_secret',
|
|
105
|
+
severity: 'high',
|
|
106
|
+
title: 'Fixed finding',
|
|
107
|
+
},
|
|
108
|
+
])
|
|
109
|
+
|
|
110
|
+
const diff = computeDiff(current, baseline)
|
|
111
|
+
|
|
112
|
+
expect(diff.new).toHaveLength(0)
|
|
113
|
+
expect(diff.fixed).toHaveLength(1)
|
|
114
|
+
expect(diff.fixed[0].title).toBe('Fixed finding')
|
|
115
|
+
expect(diff.existing).toHaveLength(0)
|
|
116
|
+
expect(diff.stats.fixedCount).toBe(1)
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
it('should handle mixed scenario with new, fixed, and existing findings', () => {
|
|
120
|
+
// Create a finding that will be in both current and baseline
|
|
121
|
+
const existingVuln = createVuln('src/existing.ts', 10, 'xss', 'medium', 'Existing XSS')
|
|
122
|
+
const { computeFindingHash } = require('../../suppression/hash')
|
|
123
|
+
const existingHash = computeFindingHash(existingVuln)
|
|
124
|
+
|
|
125
|
+
// Current: one existing + one new
|
|
126
|
+
const current = [
|
|
127
|
+
existingVuln,
|
|
128
|
+
createVuln('src/new.ts', 20, 'sql_injection', 'high', 'New SQL injection'),
|
|
129
|
+
]
|
|
130
|
+
|
|
131
|
+
// Baseline: one existing + one fixed
|
|
132
|
+
const baseline = createBaseline([
|
|
133
|
+
{
|
|
134
|
+
hash: existingHash,
|
|
135
|
+
filePath: 'src/existing.ts',
|
|
136
|
+
lineNumber: 10,
|
|
137
|
+
category: 'xss',
|
|
138
|
+
severity: 'medium',
|
|
139
|
+
title: 'Existing XSS',
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
hash: 'fixedhash9999999',
|
|
143
|
+
filePath: 'src/old.ts',
|
|
144
|
+
lineNumber: 30,
|
|
145
|
+
category: 'hardcoded_secret',
|
|
146
|
+
severity: 'critical',
|
|
147
|
+
title: 'Fixed secret',
|
|
148
|
+
},
|
|
149
|
+
])
|
|
150
|
+
|
|
151
|
+
const diff = computeDiff(current, baseline)
|
|
152
|
+
|
|
153
|
+
expect(diff.new).toHaveLength(1)
|
|
154
|
+
expect(diff.new[0].title).toBe('New SQL injection')
|
|
155
|
+
expect(diff.fixed).toHaveLength(1)
|
|
156
|
+
expect(diff.fixed[0].title).toBe('Fixed secret')
|
|
157
|
+
expect(diff.existing).toHaveLength(1)
|
|
158
|
+
expect(diff.existing[0].title).toBe('Existing XSS')
|
|
159
|
+
|
|
160
|
+
expect(diff.stats.newCount).toBe(1)
|
|
161
|
+
expect(diff.stats.fixedCount).toBe(1)
|
|
162
|
+
expect(diff.stats.existingCount).toBe(1)
|
|
163
|
+
})
|
|
164
|
+
|
|
165
|
+
it('should compute severity counts correctly', () => {
|
|
166
|
+
const current = [
|
|
167
|
+
createVuln('src/a.ts', 1, 'sql_injection', 'critical', 'Crit 1'),
|
|
168
|
+
createVuln('src/b.ts', 2, 'xss', 'high', 'High 1'),
|
|
169
|
+
createVuln('src/c.ts', 3, 'xss', 'high', 'High 2'),
|
|
170
|
+
createVuln('src/d.ts', 4, 'data_exposure', 'medium', 'Med 1'),
|
|
171
|
+
]
|
|
172
|
+
const baseline = createBaseline([])
|
|
173
|
+
|
|
174
|
+
const diff = computeDiff(current, baseline)
|
|
175
|
+
|
|
176
|
+
expect(diff.stats.newBySeverity.critical).toBe(1)
|
|
177
|
+
expect(diff.stats.newBySeverity.high).toBe(2)
|
|
178
|
+
expect(diff.stats.newBySeverity.medium).toBe(1)
|
|
179
|
+
expect(diff.stats.newBySeverity.low).toBe(0)
|
|
180
|
+
expect(diff.stats.newBySeverity.info).toBe(0)
|
|
181
|
+
})
|
|
182
|
+
})
|
|
183
|
+
|
|
184
|
+
describe('hasNewBlockingIssues', () => {
|
|
185
|
+
it('should return true when there are new critical findings', () => {
|
|
186
|
+
const diff = computeDiff(
|
|
187
|
+
[createVuln('src/a.ts', 1, 'hardcoded_secret', 'critical')],
|
|
188
|
+
createBaseline([])
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
expect(hasNewBlockingIssues(diff)).toBe(true)
|
|
192
|
+
})
|
|
193
|
+
|
|
194
|
+
it('should return true when there are new high findings', () => {
|
|
195
|
+
const diff = computeDiff(
|
|
196
|
+
[createVuln('src/a.ts', 1, 'sql_injection', 'high')],
|
|
197
|
+
createBaseline([])
|
|
198
|
+
)
|
|
199
|
+
|
|
200
|
+
expect(hasNewBlockingIssues(diff)).toBe(true)
|
|
201
|
+
})
|
|
202
|
+
|
|
203
|
+
it('should return false when there are only new medium/low/info findings', () => {
|
|
204
|
+
const diff = computeDiff(
|
|
205
|
+
[
|
|
206
|
+
createVuln('src/a.ts', 1, 'xss', 'medium'),
|
|
207
|
+
createVuln('src/b.ts', 2, 'data_exposure', 'low'),
|
|
208
|
+
createVuln('src/c.ts', 3, 'ai_pattern', 'info'),
|
|
209
|
+
],
|
|
210
|
+
createBaseline([])
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
expect(hasNewBlockingIssues(diff)).toBe(false)
|
|
214
|
+
})
|
|
215
|
+
|
|
216
|
+
it('should return false when there are no new findings', () => {
|
|
217
|
+
const diff = computeDiff([], createBaseline([]))
|
|
218
|
+
|
|
219
|
+
expect(hasNewBlockingIssues(diff)).toBe(false)
|
|
220
|
+
})
|
|
221
|
+
})
|
|
222
|
+
|
|
223
|
+
describe('formatDiffSummary', () => {
|
|
224
|
+
it('should format a summary with all categories', () => {
|
|
225
|
+
const diff = computeDiff(
|
|
226
|
+
[
|
|
227
|
+
createVuln('src/a.ts', 1, 'hardcoded_secret', 'critical'),
|
|
228
|
+
createVuln('src/b.ts', 2, 'xss', 'high'),
|
|
229
|
+
],
|
|
230
|
+
createBaseline([
|
|
231
|
+
{
|
|
232
|
+
hash: 'fixedhash1234567',
|
|
233
|
+
filePath: 'src/fixed.ts',
|
|
234
|
+
lineNumber: 5,
|
|
235
|
+
category: 'sql_injection',
|
|
236
|
+
severity: 'high',
|
|
237
|
+
title: 'Fixed finding',
|
|
238
|
+
},
|
|
239
|
+
])
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
const summary = formatDiffSummary(diff)
|
|
243
|
+
|
|
244
|
+
expect(summary).toContain('2 new')
|
|
245
|
+
expect(summary).toContain('1 fixed')
|
|
246
|
+
})
|
|
247
|
+
|
|
248
|
+
it('should omit zero counts', () => {
|
|
249
|
+
const diff = computeDiff(
|
|
250
|
+
[createVuln('src/a.ts', 1, 'hardcoded_secret', 'critical')],
|
|
251
|
+
createBaseline([])
|
|
252
|
+
)
|
|
253
|
+
|
|
254
|
+
const summary = formatDiffSummary(diff)
|
|
255
|
+
|
|
256
|
+
expect(summary).toContain('1 new')
|
|
257
|
+
expect(summary).not.toContain('fixed')
|
|
258
|
+
expect(summary).not.toContain('existing')
|
|
259
|
+
})
|
|
260
|
+
})
|
|
261
|
+
})
|