@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,384 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OSV Check Unit Tests
|
|
3
|
+
*
|
|
4
|
+
* Tests for the OSV.dev security advisory integration.
|
|
5
|
+
* Mocks fetch to avoid live API calls during tests.
|
|
6
|
+
*
|
|
7
|
+
* Run: npx jest src/layer3/__tests__/osv-check.test.ts
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import {
|
|
11
|
+
checkPackageAdvisories,
|
|
12
|
+
queryOSV,
|
|
13
|
+
queryOSVBatch,
|
|
14
|
+
mapOSVSeverity,
|
|
15
|
+
isMaliciousPackage,
|
|
16
|
+
getCacheKey,
|
|
17
|
+
advisoryCache,
|
|
18
|
+
} from '../osv-check'
|
|
19
|
+
|
|
20
|
+
// Mock fetch globally
|
|
21
|
+
const mockFetch = jest.fn()
|
|
22
|
+
global.fetch = mockFetch
|
|
23
|
+
|
|
24
|
+
// Helper to create OSV vulnerability response
|
|
25
|
+
function createOSVVuln(options: {
|
|
26
|
+
id: string
|
|
27
|
+
summary?: string
|
|
28
|
+
cvssScore?: number
|
|
29
|
+
malicious?: boolean
|
|
30
|
+
dbSeverity?: string
|
|
31
|
+
}) {
|
|
32
|
+
return {
|
|
33
|
+
id: options.id,
|
|
34
|
+
summary: options.summary || 'Test vulnerability',
|
|
35
|
+
severity: options.cvssScore
|
|
36
|
+
? [{ type: 'CVSS_V3', score: options.cvssScore.toString() }]
|
|
37
|
+
: undefined,
|
|
38
|
+
database_specific: {
|
|
39
|
+
malicious: options.malicious,
|
|
40
|
+
severity: options.dbSeverity,
|
|
41
|
+
},
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
describe('OSV Check', () => {
|
|
46
|
+
beforeEach(() => {
|
|
47
|
+
// Clear cache and mocks before each test
|
|
48
|
+
advisoryCache.clear()
|
|
49
|
+
mockFetch.mockReset()
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
describe('mapOSVSeverity()', () => {
|
|
53
|
+
it('returns critical for malicious packages', () => {
|
|
54
|
+
const vuln = createOSVVuln({ id: 'MAL-123', malicious: true })
|
|
55
|
+
expect(mapOSVSeverity(vuln)).toBe('critical')
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
it('maps CVSS scores correctly', () => {
|
|
59
|
+
expect(mapOSVSeverity(createOSVVuln({ id: 'CVE-1', cvssScore: 9.5 }))).toBe('critical')
|
|
60
|
+
expect(mapOSVSeverity(createOSVVuln({ id: 'CVE-2', cvssScore: 9.0 }))).toBe('critical')
|
|
61
|
+
expect(mapOSVSeverity(createOSVVuln({ id: 'CVE-3', cvssScore: 7.5 }))).toBe('high')
|
|
62
|
+
expect(mapOSVSeverity(createOSVVuln({ id: 'CVE-4', cvssScore: 7.0 }))).toBe('high')
|
|
63
|
+
expect(mapOSVSeverity(createOSVVuln({ id: 'CVE-5', cvssScore: 5.0 }))).toBe('medium')
|
|
64
|
+
expect(mapOSVSeverity(createOSVVuln({ id: 'CVE-6', cvssScore: 4.0 }))).toBe('medium')
|
|
65
|
+
expect(mapOSVSeverity(createOSVVuln({ id: 'CVE-7', cvssScore: 2.0 }))).toBe('low')
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
it('maps database_specific severity when no CVSS', () => {
|
|
69
|
+
expect(mapOSVSeverity(createOSVVuln({ id: 'X-1', dbSeverity: 'critical' }))).toBe('critical')
|
|
70
|
+
expect(mapOSVSeverity(createOSVVuln({ id: 'X-2', dbSeverity: 'high' }))).toBe('high')
|
|
71
|
+
expect(mapOSVSeverity(createOSVVuln({ id: 'X-3', dbSeverity: 'moderate' }))).toBe('medium')
|
|
72
|
+
expect(mapOSVSeverity(createOSVVuln({ id: 'X-4', dbSeverity: 'medium' }))).toBe('medium')
|
|
73
|
+
expect(mapOSVSeverity(createOSVVuln({ id: 'X-5', dbSeverity: 'low' }))).toBe('low')
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
it('defaults to high for unknown severity', () => {
|
|
77
|
+
const vuln = createOSVVuln({ id: 'UNKNOWN-1' })
|
|
78
|
+
expect(mapOSVSeverity(vuln)).toBe('high')
|
|
79
|
+
})
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
describe('isMaliciousPackage()', () => {
|
|
83
|
+
it('detects MAL- prefixed IDs', () => {
|
|
84
|
+
const vuln = createOSVVuln({ id: 'MAL-2023-1234' })
|
|
85
|
+
expect(isMaliciousPackage(vuln)).toBe(true)
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
it('detects malicious flag in database_specific', () => {
|
|
89
|
+
const vuln = createOSVVuln({ id: 'GHSA-xxxx', malicious: true })
|
|
90
|
+
expect(isMaliciousPackage(vuln)).toBe(true)
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
it('detects "malicious" in summary', () => {
|
|
94
|
+
const vuln = createOSVVuln({
|
|
95
|
+
id: 'GHSA-xxxx',
|
|
96
|
+
summary: 'This package contains malicious code',
|
|
97
|
+
})
|
|
98
|
+
expect(isMaliciousPackage(vuln)).toBe(true)
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
it('returns false for normal vulnerabilities', () => {
|
|
102
|
+
const vuln = createOSVVuln({
|
|
103
|
+
id: 'CVE-2024-1234',
|
|
104
|
+
summary: 'SQL injection vulnerability',
|
|
105
|
+
})
|
|
106
|
+
expect(isMaliciousPackage(vuln)).toBe(false)
|
|
107
|
+
})
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
describe('getCacheKey()', () => {
|
|
111
|
+
it('creates cache keys with lowercase package names', () => {
|
|
112
|
+
expect(getCacheKey('React', 'npm')).toBe('npm:react')
|
|
113
|
+
expect(getCacheKey('Django', 'PyPI')).toBe('PyPI:django')
|
|
114
|
+
})
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
describe('queryOSV()', () => {
|
|
118
|
+
it('returns cached results on subsequent calls', async () => {
|
|
119
|
+
// First call - API returns vulns
|
|
120
|
+
mockFetch.mockResolvedValueOnce({
|
|
121
|
+
ok: true,
|
|
122
|
+
json: async () => ({ vulns: [createOSVVuln({ id: 'CVE-1' })] }),
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
const first = await queryOSV('test-package', 'npm')
|
|
126
|
+
expect(first).toHaveLength(1)
|
|
127
|
+
expect(mockFetch).toHaveBeenCalledTimes(1)
|
|
128
|
+
|
|
129
|
+
// Second call - should use cache
|
|
130
|
+
const second = await queryOSV('test-package', 'npm')
|
|
131
|
+
expect(second).toHaveLength(1)
|
|
132
|
+
expect(mockFetch).toHaveBeenCalledTimes(1) // Still 1, used cache
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
it('returns empty array when API returns non-ok response', async () => {
|
|
136
|
+
mockFetch.mockResolvedValueOnce({
|
|
137
|
+
ok: false,
|
|
138
|
+
status: 500,
|
|
139
|
+
})
|
|
140
|
+
|
|
141
|
+
const result = await queryOSV('error-package', 'npm')
|
|
142
|
+
expect(result).toEqual([])
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
it('returns empty array on network error', async () => {
|
|
146
|
+
mockFetch.mockRejectedValueOnce(new Error('Network error'))
|
|
147
|
+
|
|
148
|
+
const result = await queryOSV('network-error-package', 'npm')
|
|
149
|
+
expect(result).toEqual([])
|
|
150
|
+
})
|
|
151
|
+
})
|
|
152
|
+
|
|
153
|
+
describe('queryOSVBatch()', () => {
|
|
154
|
+
it('uses cache for previously queried packages', async () => {
|
|
155
|
+
// Pre-populate cache
|
|
156
|
+
advisoryCache.set('npm:cached-pkg', {
|
|
157
|
+
timestamp: Date.now(),
|
|
158
|
+
advisories: [createOSVVuln({ id: 'CACHED-1' })],
|
|
159
|
+
})
|
|
160
|
+
|
|
161
|
+
mockFetch.mockResolvedValueOnce({
|
|
162
|
+
ok: true,
|
|
163
|
+
json: async () => ({
|
|
164
|
+
results: [{ vulns: [createOSVVuln({ id: 'NEW-1' })] }],
|
|
165
|
+
}),
|
|
166
|
+
})
|
|
167
|
+
|
|
168
|
+
const results = await queryOSVBatch([
|
|
169
|
+
{ name: 'cached-pkg', ecosystem: 'npm' },
|
|
170
|
+
{ name: 'new-pkg', ecosystem: 'npm' },
|
|
171
|
+
])
|
|
172
|
+
|
|
173
|
+
// Should have results for both
|
|
174
|
+
expect(results.get('npm:cached-pkg')).toHaveLength(1)
|
|
175
|
+
expect(results.get('npm:new-pkg')).toHaveLength(1)
|
|
176
|
+
|
|
177
|
+
// API should only be called once (for new-pkg batch)
|
|
178
|
+
expect(mockFetch).toHaveBeenCalledTimes(1)
|
|
179
|
+
})
|
|
180
|
+
|
|
181
|
+
it('returns empty arrays on API error', async () => {
|
|
182
|
+
mockFetch.mockResolvedValueOnce({
|
|
183
|
+
ok: false,
|
|
184
|
+
status: 503,
|
|
185
|
+
})
|
|
186
|
+
|
|
187
|
+
const results = await queryOSVBatch([
|
|
188
|
+
{ name: 'pkg1', ecosystem: 'npm' },
|
|
189
|
+
{ name: 'pkg2', ecosystem: 'npm' },
|
|
190
|
+
])
|
|
191
|
+
|
|
192
|
+
expect(results.get('npm:pkg1')).toEqual([])
|
|
193
|
+
expect(results.get('npm:pkg2')).toEqual([])
|
|
194
|
+
})
|
|
195
|
+
})
|
|
196
|
+
|
|
197
|
+
describe('checkPackageAdvisories()', () => {
|
|
198
|
+
it('returns vulnerabilities for packages with advisories', async () => {
|
|
199
|
+
// Mock OSV batch response
|
|
200
|
+
mockFetch.mockResolvedValueOnce({
|
|
201
|
+
ok: true,
|
|
202
|
+
json: async () => ({
|
|
203
|
+
results: [
|
|
204
|
+
{
|
|
205
|
+
vulns: [
|
|
206
|
+
createOSVVuln({
|
|
207
|
+
id: 'MAL-2023-1234',
|
|
208
|
+
summary: 'Malicious package',
|
|
209
|
+
malicious: true,
|
|
210
|
+
}),
|
|
211
|
+
],
|
|
212
|
+
},
|
|
213
|
+
],
|
|
214
|
+
}),
|
|
215
|
+
})
|
|
216
|
+
|
|
217
|
+
const packageJson = JSON.stringify({
|
|
218
|
+
dependencies: {
|
|
219
|
+
'malicious-pkg': '1.0.0',
|
|
220
|
+
},
|
|
221
|
+
})
|
|
222
|
+
|
|
223
|
+
const findings = await checkPackageAdvisories(packageJson, 'package.json')
|
|
224
|
+
|
|
225
|
+
expect(findings).toHaveLength(1)
|
|
226
|
+
expect(findings[0].category).toBe('ai_package_malicious')
|
|
227
|
+
expect(findings[0].severity).toBe('critical')
|
|
228
|
+
expect(findings[0].title).toContain('Malicious package')
|
|
229
|
+
})
|
|
230
|
+
|
|
231
|
+
it('returns empty array for clean packages', async () => {
|
|
232
|
+
// Mock OSV batch response - no vulns
|
|
233
|
+
mockFetch.mockResolvedValueOnce({
|
|
234
|
+
ok: true,
|
|
235
|
+
json: async () => ({
|
|
236
|
+
results: [{ vulns: [] }],
|
|
237
|
+
}),
|
|
238
|
+
})
|
|
239
|
+
|
|
240
|
+
const packageJson = JSON.stringify({
|
|
241
|
+
dependencies: {
|
|
242
|
+
'clean-pkg': '1.0.0',
|
|
243
|
+
},
|
|
244
|
+
})
|
|
245
|
+
|
|
246
|
+
const findings = await checkPackageAdvisories(packageJson, 'package.json')
|
|
247
|
+
expect(findings).toHaveLength(0)
|
|
248
|
+
})
|
|
249
|
+
|
|
250
|
+
it('handles requirements.txt files', async () => {
|
|
251
|
+
mockFetch.mockResolvedValueOnce({
|
|
252
|
+
ok: true,
|
|
253
|
+
json: async () => ({
|
|
254
|
+
results: [
|
|
255
|
+
{
|
|
256
|
+
vulns: [
|
|
257
|
+
createOSVVuln({
|
|
258
|
+
id: 'CVE-2024-1234',
|
|
259
|
+
summary: 'Critical vulnerability',
|
|
260
|
+
cvssScore: 9.8,
|
|
261
|
+
}),
|
|
262
|
+
],
|
|
263
|
+
},
|
|
264
|
+
],
|
|
265
|
+
}),
|
|
266
|
+
})
|
|
267
|
+
|
|
268
|
+
const requirements = `requests==2.28.0
|
|
269
|
+
flask>=2.0.0`
|
|
270
|
+
|
|
271
|
+
const findings = await checkPackageAdvisories(requirements, 'requirements.txt')
|
|
272
|
+
|
|
273
|
+
expect(findings).toHaveLength(1)
|
|
274
|
+
expect(findings[0].category).toBe('suspicious_package')
|
|
275
|
+
expect(findings[0].severity).toBe('critical')
|
|
276
|
+
})
|
|
277
|
+
|
|
278
|
+
it('returns empty for unsupported file types', async () => {
|
|
279
|
+
const findings = await checkPackageAdvisories('some content', 'random.txt')
|
|
280
|
+
expect(findings).toHaveLength(0)
|
|
281
|
+
})
|
|
282
|
+
|
|
283
|
+
it('categorizes vulnerable (non-malicious) packages correctly', async () => {
|
|
284
|
+
mockFetch.mockResolvedValueOnce({
|
|
285
|
+
ok: true,
|
|
286
|
+
json: async () => ({
|
|
287
|
+
results: [
|
|
288
|
+
{
|
|
289
|
+
vulns: [
|
|
290
|
+
createOSVVuln({
|
|
291
|
+
id: 'CVE-2024-5678',
|
|
292
|
+
summary: 'XSS vulnerability in template rendering',
|
|
293
|
+
cvssScore: 7.5,
|
|
294
|
+
}),
|
|
295
|
+
],
|
|
296
|
+
},
|
|
297
|
+
],
|
|
298
|
+
}),
|
|
299
|
+
})
|
|
300
|
+
|
|
301
|
+
const packageJson = JSON.stringify({
|
|
302
|
+
dependencies: {
|
|
303
|
+
'vuln-pkg': '1.0.0',
|
|
304
|
+
},
|
|
305
|
+
})
|
|
306
|
+
|
|
307
|
+
const findings = await checkPackageAdvisories(packageJson, 'package.json')
|
|
308
|
+
|
|
309
|
+
expect(findings).toHaveLength(1)
|
|
310
|
+
expect(findings[0].category).toBe('suspicious_package')
|
|
311
|
+
expect(findings[0].severity).toBe('high')
|
|
312
|
+
expect(findings[0].title).toContain('Vulnerable package')
|
|
313
|
+
})
|
|
314
|
+
|
|
315
|
+
it('sets requiresAIValidation to false (authoritative data)', async () => {
|
|
316
|
+
mockFetch.mockResolvedValueOnce({
|
|
317
|
+
ok: true,
|
|
318
|
+
json: async () => ({
|
|
319
|
+
results: [
|
|
320
|
+
{
|
|
321
|
+
vulns: [createOSVVuln({ id: 'CVE-1', cvssScore: 5.0 })],
|
|
322
|
+
},
|
|
323
|
+
],
|
|
324
|
+
}),
|
|
325
|
+
})
|
|
326
|
+
|
|
327
|
+
const packageJson = JSON.stringify({
|
|
328
|
+
dependencies: {
|
|
329
|
+
'test-pkg': '1.0.0',
|
|
330
|
+
},
|
|
331
|
+
})
|
|
332
|
+
|
|
333
|
+
const findings = await checkPackageAdvisories(packageJson, 'package.json')
|
|
334
|
+
|
|
335
|
+
expect(findings).toHaveLength(1)
|
|
336
|
+
expect(findings[0].requiresAIValidation).toBe(false)
|
|
337
|
+
})
|
|
338
|
+
|
|
339
|
+
it('gracefully handles API failures', async () => {
|
|
340
|
+
mockFetch.mockRejectedValueOnce(new Error('Network error'))
|
|
341
|
+
|
|
342
|
+
const packageJson = JSON.stringify({
|
|
343
|
+
dependencies: {
|
|
344
|
+
'some-pkg': '1.0.0',
|
|
345
|
+
},
|
|
346
|
+
})
|
|
347
|
+
|
|
348
|
+
const findings = await checkPackageAdvisories(packageJson, 'package.json')
|
|
349
|
+
expect(findings).toHaveLength(0) // Graceful degradation
|
|
350
|
+
})
|
|
351
|
+
|
|
352
|
+
it('aggregates multiple advisories for same package', async () => {
|
|
353
|
+
mockFetch.mockResolvedValueOnce({
|
|
354
|
+
ok: true,
|
|
355
|
+
json: async () => ({
|
|
356
|
+
results: [
|
|
357
|
+
{
|
|
358
|
+
vulns: [
|
|
359
|
+
createOSVVuln({ id: 'CVE-1', cvssScore: 5.0, summary: 'Low risk' }),
|
|
360
|
+
createOSVVuln({ id: 'CVE-2', cvssScore: 9.0, summary: 'High risk' }),
|
|
361
|
+
createOSVVuln({ id: 'CVE-3', cvssScore: 7.0, summary: 'Medium risk' }),
|
|
362
|
+
],
|
|
363
|
+
},
|
|
364
|
+
],
|
|
365
|
+
}),
|
|
366
|
+
})
|
|
367
|
+
|
|
368
|
+
const packageJson = JSON.stringify({
|
|
369
|
+
dependencies: {
|
|
370
|
+
'multi-vuln-pkg': '1.0.0',
|
|
371
|
+
},
|
|
372
|
+
})
|
|
373
|
+
|
|
374
|
+
const findings = await checkPackageAdvisories(packageJson, 'package.json')
|
|
375
|
+
|
|
376
|
+
expect(findings).toHaveLength(1) // Single aggregated finding
|
|
377
|
+
expect(findings[0].severity).toBe('critical') // Uses highest severity
|
|
378
|
+
expect(findings[0].title).toContain('3 advisories')
|
|
379
|
+
expect(findings[0].description).toContain('CVE-1')
|
|
380
|
+
expect(findings[0].description).toContain('CVE-2')
|
|
381
|
+
expect(findings[0].description).toContain('CVE-3')
|
|
382
|
+
})
|
|
383
|
+
})
|
|
384
|
+
})
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Smart Auto-Dismiss Rules
|
|
3
|
+
*
|
|
4
|
+
* Instant filtering rules that don't require AI validation.
|
|
5
|
+
* These rules catch obvious false positives before sending to AI.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { Vulnerability } from '../../types'
|
|
9
|
+
import type { AutoDismissRule } from './types'
|
|
10
|
+
import {
|
|
11
|
+
isTestOrMockFile,
|
|
12
|
+
isExampleFile,
|
|
13
|
+
isScannerOrFixtureFile,
|
|
14
|
+
isEnvVarReference,
|
|
15
|
+
isPublicEndpoint,
|
|
16
|
+
isComment,
|
|
17
|
+
} from '../../utils/context-helpers'
|
|
18
|
+
import { getTierForCategory } from '../../tiers'
|
|
19
|
+
|
|
20
|
+
// ============================================================================
|
|
21
|
+
// Auto-Dismiss Rules
|
|
22
|
+
// ============================================================================
|
|
23
|
+
|
|
24
|
+
const AUTO_DISMISS_RULES: AutoDismissRule[] = [
|
|
25
|
+
// Test files - often contain intentional "vulnerable" patterns for testing
|
|
26
|
+
{
|
|
27
|
+
name: 'test_file',
|
|
28
|
+
check: (finding) => isTestOrMockFile(finding.filePath),
|
|
29
|
+
reason: 'Finding in test/mock file',
|
|
30
|
+
},
|
|
31
|
+
|
|
32
|
+
// Example/demo code - not production code
|
|
33
|
+
{
|
|
34
|
+
name: 'example_file',
|
|
35
|
+
check: (finding) => isExampleFile(finding.filePath),
|
|
36
|
+
reason: 'Finding in example/demo file',
|
|
37
|
+
},
|
|
38
|
+
|
|
39
|
+
// Documentation files
|
|
40
|
+
{
|
|
41
|
+
name: 'documentation_file',
|
|
42
|
+
check: (finding) => /\.(md|mdx|txt|rst)$/i.test(finding.filePath),
|
|
43
|
+
reason: 'Finding in documentation file',
|
|
44
|
+
},
|
|
45
|
+
|
|
46
|
+
// Scanner/security tool code itself
|
|
47
|
+
{
|
|
48
|
+
name: 'scanner_code',
|
|
49
|
+
check: (finding) => isScannerOrFixtureFile(finding.filePath),
|
|
50
|
+
reason: 'Finding in scanner/fixture code',
|
|
51
|
+
},
|
|
52
|
+
|
|
53
|
+
// Environment variable references (not hardcoded secrets)
|
|
54
|
+
{
|
|
55
|
+
name: 'env_var_reference',
|
|
56
|
+
check: (finding) => {
|
|
57
|
+
if (finding.category !== 'hardcoded_secret' && finding.category !== 'high_entropy_string') {
|
|
58
|
+
return false
|
|
59
|
+
}
|
|
60
|
+
return isEnvVarReference(finding.lineContent)
|
|
61
|
+
},
|
|
62
|
+
reason: 'Uses environment variable (not hardcoded)',
|
|
63
|
+
},
|
|
64
|
+
|
|
65
|
+
// Public health check endpoints don't need auth
|
|
66
|
+
{
|
|
67
|
+
name: 'health_check_endpoint',
|
|
68
|
+
check: (finding) => {
|
|
69
|
+
if (finding.category !== 'missing_auth') return false
|
|
70
|
+
return isPublicEndpoint(finding.lineContent, finding.filePath)
|
|
71
|
+
},
|
|
72
|
+
reason: 'Public health check endpoint (auth not required)',
|
|
73
|
+
},
|
|
74
|
+
|
|
75
|
+
// CSS/Tailwind classes flagged as high entropy
|
|
76
|
+
{
|
|
77
|
+
name: 'css_classes',
|
|
78
|
+
check: (finding) => {
|
|
79
|
+
if (finding.category !== 'high_entropy_string') return false
|
|
80
|
+
const cssIndicators = ['flex', 'grid', 'text-', 'bg-', 'px-', 'py-', 'rounded', 'shadow', 'hover:', 'dark:']
|
|
81
|
+
const lowerLine = finding.lineContent.toLowerCase()
|
|
82
|
+
const matchCount = cssIndicators.filter(ind => lowerLine.includes(ind)).length
|
|
83
|
+
return matchCount >= 2
|
|
84
|
+
},
|
|
85
|
+
reason: 'CSS/Tailwind classes (not a secret)',
|
|
86
|
+
},
|
|
87
|
+
|
|
88
|
+
// Comment lines shouldn't be flagged for most categories
|
|
89
|
+
{
|
|
90
|
+
name: 'comment_line',
|
|
91
|
+
check: (finding) => {
|
|
92
|
+
// Some categories are valid in comments (e.g., TODO security)
|
|
93
|
+
if (finding.category === 'ai_pattern') return false
|
|
94
|
+
return isComment(finding.lineContent)
|
|
95
|
+
},
|
|
96
|
+
reason: 'Code comment (not executable)',
|
|
97
|
+
},
|
|
98
|
+
|
|
99
|
+
// Info severity already - no need to validate
|
|
100
|
+
// BUT: Only auto-dismiss info-severity for Tier A (core) findings
|
|
101
|
+
// Tier B (ai_assisted) findings MUST go through AI validation even at info severity
|
|
102
|
+
// because detectors may have pre-downgraded them based on partial context
|
|
103
|
+
{
|
|
104
|
+
name: 'info_severity_core_only',
|
|
105
|
+
check: (finding) => {
|
|
106
|
+
if (finding.severity !== 'info') return false
|
|
107
|
+
// Only auto-dismiss info-severity for Tier A (core) findings
|
|
108
|
+
// Tier B should always go through AI for proper validation
|
|
109
|
+
const tier = getTierForCategory(finding.category, finding.layer)
|
|
110
|
+
return tier === 'core'
|
|
111
|
+
},
|
|
112
|
+
reason: 'Already info severity for core detector (low priority)',
|
|
113
|
+
},
|
|
114
|
+
|
|
115
|
+
// Generic success/error messages in ai_pattern
|
|
116
|
+
{
|
|
117
|
+
name: 'generic_message',
|
|
118
|
+
check: (finding) => {
|
|
119
|
+
if (finding.category !== 'ai_pattern') return false
|
|
120
|
+
const genericPatterns = [
|
|
121
|
+
/['"`](success|done|ok|completed|finished|saved|updated|deleted|created)['"`]/i,
|
|
122
|
+
/['"`]something went wrong['"`]/i,
|
|
123
|
+
/['"`]an error occurred['"`]/i,
|
|
124
|
+
/console\.(log|info|debug)\s*\(\s*['"`][^'"]+['"`]\s*\)/i,
|
|
125
|
+
]
|
|
126
|
+
return genericPatterns.some(p => p.test(finding.lineContent))
|
|
127
|
+
},
|
|
128
|
+
reason: 'Generic UI message (not security-relevant)',
|
|
129
|
+
},
|
|
130
|
+
|
|
131
|
+
// Type definitions with 'any' - often necessary for third-party libs
|
|
132
|
+
{
|
|
133
|
+
name: 'type_definition_any',
|
|
134
|
+
check: (finding) => {
|
|
135
|
+
if (finding.category !== 'ai_pattern') return false
|
|
136
|
+
if (!finding.title.toLowerCase().includes('any')) return false
|
|
137
|
+
// Check if it's in a .d.ts file or type definition context
|
|
138
|
+
if (finding.filePath.includes('.d.ts')) return true
|
|
139
|
+
const typeDefPatterns = [/^type\s+\w+\s*=/, /^interface\s+\w+/, /declare\s+(const|let|var|function|class)/]
|
|
140
|
+
return typeDefPatterns.some(p => p.test(finding.lineContent.trim()))
|
|
141
|
+
},
|
|
142
|
+
reason: 'Type definition (not runtime code)',
|
|
143
|
+
},
|
|
144
|
+
|
|
145
|
+
// setTimeout/setInterval magic numbers - code style, not security
|
|
146
|
+
{
|
|
147
|
+
name: 'timeout_magic_number',
|
|
148
|
+
check: (finding) => {
|
|
149
|
+
if (finding.category !== 'ai_pattern') return false
|
|
150
|
+
return /set(Timeout|Interval)\s*\([^,]+,\s*\d+\s*\)/.test(finding.lineContent)
|
|
151
|
+
},
|
|
152
|
+
reason: 'Timeout value (code style, not security)',
|
|
153
|
+
},
|
|
154
|
+
]
|
|
155
|
+
|
|
156
|
+
// ============================================================================
|
|
157
|
+
// Apply Auto-Dismiss Rules
|
|
158
|
+
// ============================================================================
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Rules that should NOT be applied to direct-surface findings.
|
|
162
|
+
* These rules are designed to reduce AI validation costs, not to suppress findings entirely.
|
|
163
|
+
*/
|
|
164
|
+
const VALIDATION_ONLY_RULES = new Set([
|
|
165
|
+
'info_severity_core_only', // Info findings should surface, just not go through expensive AI validation
|
|
166
|
+
])
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Apply smart auto-dismiss rules to filter obvious false positives
|
|
170
|
+
* Returns findings that should be sent to AI validation
|
|
171
|
+
*
|
|
172
|
+
* @param findings - Array of vulnerabilities to filter
|
|
173
|
+
* @param mode - 'validation' applies all rules (for AI validation candidates),
|
|
174
|
+
* 'surface' excludes cost-saving rules (for direct-surface findings)
|
|
175
|
+
*/
|
|
176
|
+
export function applyAutoDismissRules(
|
|
177
|
+
findings: Vulnerability[],
|
|
178
|
+
mode: 'validation' | 'surface' = 'validation'
|
|
179
|
+
): {
|
|
180
|
+
toValidate: Vulnerability[]
|
|
181
|
+
dismissed: Array<{ finding: Vulnerability; rule: string; reason: string }>
|
|
182
|
+
} {
|
|
183
|
+
const toValidate: Vulnerability[] = []
|
|
184
|
+
const dismissed: Array<{ finding: Vulnerability; rule: string; reason: string }> = []
|
|
185
|
+
|
|
186
|
+
// Filter rules based on mode
|
|
187
|
+
const applicableRules = mode === 'surface'
|
|
188
|
+
? AUTO_DISMISS_RULES.filter(rule => !VALIDATION_ONLY_RULES.has(rule.name))
|
|
189
|
+
: AUTO_DISMISS_RULES
|
|
190
|
+
|
|
191
|
+
for (const finding of findings) {
|
|
192
|
+
let wasDismissed = false
|
|
193
|
+
|
|
194
|
+
for (const rule of applicableRules) {
|
|
195
|
+
if (rule.check(finding)) {
|
|
196
|
+
dismissed.push({
|
|
197
|
+
finding,
|
|
198
|
+
rule: rule.name,
|
|
199
|
+
reason: rule.reason,
|
|
200
|
+
})
|
|
201
|
+
wasDismissed = true
|
|
202
|
+
break
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
if (!wasDismissed) {
|
|
207
|
+
toValidate.push(finding)
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
return { toValidate, dismissed }
|
|
212
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AI Provider Client Factories
|
|
3
|
+
*
|
|
4
|
+
* Provides lazy-initialized clients for OpenAI and Anthropic APIs.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import Anthropic from '@anthropic-ai/sdk'
|
|
8
|
+
import OpenAI from 'openai'
|
|
9
|
+
|
|
10
|
+
// ============================================================================
|
|
11
|
+
// Anthropic Client
|
|
12
|
+
// ============================================================================
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Initialize Anthropic client
|
|
16
|
+
*/
|
|
17
|
+
export function getAnthropicClient(): Anthropic {
|
|
18
|
+
const apiKey = process.env.ANTHROPIC_API_KEY
|
|
19
|
+
if (!apiKey) {
|
|
20
|
+
throw new Error('ANTHROPIC_API_KEY environment variable is not set')
|
|
21
|
+
}
|
|
22
|
+
return new Anthropic({ apiKey })
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// ============================================================================
|
|
26
|
+
// OpenAI Client
|
|
27
|
+
// ============================================================================
|
|
28
|
+
|
|
29
|
+
// Singleton instance for connection reuse
|
|
30
|
+
let openaiClient: OpenAI | null = null
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Initialize OpenAI client (singleton)
|
|
34
|
+
*/
|
|
35
|
+
export function getOpenAIClient(): OpenAI {
|
|
36
|
+
if (!openaiClient) {
|
|
37
|
+
const apiKey = process.env.OPENAI_API_KEY
|
|
38
|
+
if (!apiKey) {
|
|
39
|
+
throw new Error('OPENAI_API_KEY environment variable is not set')
|
|
40
|
+
}
|
|
41
|
+
openaiClient = new OpenAI({ apiKey })
|
|
42
|
+
}
|
|
43
|
+
return openaiClient
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// ============================================================================
|
|
47
|
+
// Pricing Constants
|
|
48
|
+
// ============================================================================
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* GPT-5-mini pricing constants (per 1M tokens)
|
|
52
|
+
*/
|
|
53
|
+
export const GPT5_MINI_PRICING = {
|
|
54
|
+
input: 0.25, // $0.25 per 1M tokens
|
|
55
|
+
cached: 0.025, // $0.025 per 1M tokens (10% of input)
|
|
56
|
+
output: 2.00, // $2.00 per 1M tokens
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Claude 3.5 Haiku pricing constants (per 1M tokens)
|
|
61
|
+
*/
|
|
62
|
+
export const HAIKU_PRICING = {
|
|
63
|
+
input: 0.80, // $0.80 per 1M tokens
|
|
64
|
+
cacheWrite: 1.00, // $1.00 per 1M tokens (5m cache)
|
|
65
|
+
cacheRead: 0.08, // $0.08 per 1M tokens
|
|
66
|
+
output: 4.00, // $4.00 per 1M tokens
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// ============================================================================
|
|
70
|
+
// Batching Configuration
|
|
71
|
+
// ============================================================================
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Number of files to include in each API call (Phase 2 optimization)
|
|
75
|
+
* Batching multiple files reduces API overhead and leverages prompt caching better
|
|
76
|
+
*/
|
|
77
|
+
export const FILES_PER_API_BATCH = 8
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Number of API batches to process in parallel (Phase 3 optimization)
|
|
81
|
+
* Higher values = faster scans but more API load
|
|
82
|
+
* OpenAI/GPT-5-mini handles this well
|
|
83
|
+
*/
|
|
84
|
+
export const PARALLEL_API_BATCHES = 6
|