@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
|
@@ -35,7 +35,29 @@ const CATEGORY_DOCS: Partial<Record<VulnerabilityCategory, string>> = {
|
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
/**
|
|
38
|
-
*
|
|
38
|
+
* Helper to determine language from file path
|
|
39
|
+
*/
|
|
40
|
+
function getLanguageFromPath(filePath: string): string {
|
|
41
|
+
const ext = filePath.split('.').pop()?.toLowerCase() || ''
|
|
42
|
+
const langMap: Record<string, string> = {
|
|
43
|
+
ts: 'typescript',
|
|
44
|
+
tsx: 'typescript',
|
|
45
|
+
js: 'javascript',
|
|
46
|
+
jsx: 'javascript',
|
|
47
|
+
py: 'python',
|
|
48
|
+
go: 'go',
|
|
49
|
+
java: 'java',
|
|
50
|
+
rb: 'ruby',
|
|
51
|
+
php: 'php',
|
|
52
|
+
yaml: 'yaml',
|
|
53
|
+
yml: 'yaml',
|
|
54
|
+
json: 'json',
|
|
55
|
+
}
|
|
56
|
+
return langMap[ext] || ''
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Format a single finding as a markdown section with actionable info (PRO-82)
|
|
39
61
|
*/
|
|
40
62
|
function formatFinding(finding: Vulnerability, options: { showFile?: boolean; showDocs?: boolean } = {}): string {
|
|
41
63
|
const { showFile = true, showDocs = true } = options
|
|
@@ -44,18 +66,43 @@ function formatFinding(finding: Vulnerability, options: { showFile?: boolean; sh
|
|
|
44
66
|
? `\`${finding.filePath}:${finding.lineNumber}\``
|
|
45
67
|
: `Line ${finding.lineNumber}`
|
|
46
68
|
|
|
47
|
-
let md =
|
|
48
|
-
md +=
|
|
49
|
-
|
|
69
|
+
let md = `#### ${badge} ${finding.title}\n\n`
|
|
70
|
+
md += `📍 ${location}\n\n`
|
|
71
|
+
|
|
72
|
+
// Impact (why this matters) - shown if available
|
|
73
|
+
if (finding.impact) {
|
|
74
|
+
md += `**Impact:** ${finding.impact}\n\n`
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Code snippet in collapsible
|
|
78
|
+
if (finding.lineContent && finding.lineContent.trim()) {
|
|
79
|
+
const language = getLanguageFromPath(finding.filePath)
|
|
80
|
+
md += `<details>\n<summary>View code</summary>\n\n`
|
|
81
|
+
md += `\`\`\`${language}\n${finding.lineContent.trim()}\n\`\`\`\n\n`
|
|
82
|
+
md += `</details>\n\n`
|
|
83
|
+
}
|
|
50
84
|
|
|
51
|
-
|
|
52
|
-
|
|
85
|
+
// Fix steps - shown as numbered list (PRO-82)
|
|
86
|
+
if (finding.fixSteps && finding.fixSteps.length > 0) {
|
|
87
|
+
md += `**Fix:**\n`
|
|
88
|
+
finding.fixSteps.forEach((step, i) => {
|
|
89
|
+
md += `${i + 1}. ${step}\n`
|
|
90
|
+
})
|
|
91
|
+
md += '\n'
|
|
92
|
+
} else if (finding.suggestedFix) {
|
|
93
|
+
// Fallback to legacy field
|
|
94
|
+
md += `💡 **Fix:** ${finding.suggestedFix}\n\n`
|
|
53
95
|
}
|
|
54
96
|
|
|
55
|
-
//
|
|
97
|
+
// Documentation links
|
|
56
98
|
const docsUrl = CATEGORY_DOCS[finding.category]
|
|
57
|
-
|
|
58
|
-
|
|
99
|
+
const referenceUrl = finding.references && finding.references.length > 0 ? finding.references[0] : null
|
|
100
|
+
|
|
101
|
+
if (showDocs && (docsUrl || referenceUrl)) {
|
|
102
|
+
const links: string[] = []
|
|
103
|
+
if (docsUrl) links.push(`[Learn more](${docsUrl})`)
|
|
104
|
+
if (referenceUrl && referenceUrl !== docsUrl) links.push(`[OWASP/CWE](${referenceUrl})`)
|
|
105
|
+
md += links.join(' · ') + '\n\n'
|
|
59
106
|
}
|
|
60
107
|
|
|
61
108
|
return md
|
|
@@ -357,7 +404,7 @@ export function formatShortStatus(result: ScanResult): string {
|
|
|
357
404
|
}
|
|
358
405
|
|
|
359
406
|
/**
|
|
360
|
-
* Format as inline annotation for GitHub check run
|
|
407
|
+
* Format as inline annotation for GitHub check run (PRO-82: actionable output)
|
|
361
408
|
*/
|
|
362
409
|
export function formatAnnotation(finding: Vulnerability): {
|
|
363
410
|
path: string
|
|
@@ -371,12 +418,33 @@ export function formatAnnotation(finding: Vulnerability): {
|
|
|
371
418
|
finding.severity === 'critical' || finding.severity === 'high' ? 'failure' :
|
|
372
419
|
finding.severity === 'medium' ? 'warning' : 'notice'
|
|
373
420
|
|
|
421
|
+
// Build actionable message
|
|
422
|
+
let message = ''
|
|
423
|
+
|
|
424
|
+
// Impact first (why this matters)
|
|
425
|
+
if (finding.impact) {
|
|
426
|
+
message += `Impact: ${finding.impact}\n\n`
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
// Description
|
|
430
|
+
message += finding.description
|
|
431
|
+
|
|
432
|
+
// Fix steps or legacy suggestedFix
|
|
433
|
+
if (finding.fixSteps && finding.fixSteps.length > 0) {
|
|
434
|
+
message += '\n\n💡 Fix:\n'
|
|
435
|
+
finding.fixSteps.forEach((step, i) => {
|
|
436
|
+
message += `${i + 1}. ${step}\n`
|
|
437
|
+
})
|
|
438
|
+
} else if (finding.suggestedFix) {
|
|
439
|
+
message += `\n\n💡 Fix: ${finding.suggestedFix}`
|
|
440
|
+
}
|
|
441
|
+
|
|
374
442
|
return {
|
|
375
443
|
path: finding.filePath,
|
|
376
444
|
start_line: finding.lineNumber,
|
|
377
445
|
end_line: finding.lineNumber,
|
|
378
446
|
annotation_level: level,
|
|
379
447
|
title: `${SEVERITY_BADGE[finding.severity]} ${finding.title}`,
|
|
380
|
-
message
|
|
448
|
+
message,
|
|
381
449
|
}
|
|
382
450
|
}
|
package/src/formatters/index.ts
CHANGED
package/src/index.ts
CHANGED
|
@@ -14,6 +14,7 @@ import type {
|
|
|
14
14
|
ScanModeConfig,
|
|
15
15
|
ScanDepth,
|
|
16
16
|
CancellationToken,
|
|
17
|
+
SuppressedVulnerabilitySummary,
|
|
17
18
|
} from './types'
|
|
18
19
|
import { SCANNABLE_EXTENSIONS, SPECIAL_FILES, MAX_FILE_SIZE, SCAN_MODE_DEFAULTS } from './types'
|
|
19
20
|
import { runLayer1Scan } from './layer1'
|
|
@@ -34,6 +35,14 @@ import {
|
|
|
34
35
|
computeTierStats,
|
|
35
36
|
formatTierStats,
|
|
36
37
|
} from './tiers'
|
|
38
|
+
// Suppression system
|
|
39
|
+
import { SuppressionManager } from './suppression'
|
|
40
|
+
// Rule metadata for actionable output (PRO-82)
|
|
41
|
+
import { getRuleMetadata } from './rules'
|
|
42
|
+
// Framework-aware fix suggestions (PRO-83)
|
|
43
|
+
import { getFrameworkFix } from './rules/framework-fixes'
|
|
44
|
+
// Project context for framework detection
|
|
45
|
+
import { buildProjectContext, type ProjectContext } from './utils/project-context-builder'
|
|
37
46
|
|
|
38
47
|
// Maximum candidates per file to send to AI validation (cost control)
|
|
39
48
|
const MAX_VALIDATION_CANDIDATES_PER_FILE = 10
|
|
@@ -143,6 +152,10 @@ export interface ScanOptions {
|
|
|
143
152
|
quiet?: boolean
|
|
144
153
|
/** Cancellation token for aborting scans gracefully */
|
|
145
154
|
cancellationToken?: CancellationToken
|
|
155
|
+
/** Project path for loading suppression config (defaults to cwd) */
|
|
156
|
+
projectPath?: string
|
|
157
|
+
/** Include suppressed findings in output (for --show-suppressed) */
|
|
158
|
+
showSuppressed?: boolean
|
|
146
159
|
}
|
|
147
160
|
|
|
148
161
|
export interface ScanProgress {
|
|
@@ -286,7 +299,11 @@ export async function runScan(
|
|
|
286
299
|
|
|
287
300
|
checkCancelled()
|
|
288
301
|
|
|
302
|
+
// Phase timing tracking
|
|
303
|
+
const phaseTiming: { layer1?: number; layer2?: number; aiValidation?: number; layer3?: number } = {}
|
|
304
|
+
|
|
289
305
|
// Layer 1: Surface Scan
|
|
306
|
+
const layer1Start = Date.now()
|
|
290
307
|
reportProgress('layer1', 'Running surface scan (patterns, entropy, config)...')
|
|
291
308
|
let layer1Result = await runLayer1Scan(files, onProgress, cancellationToken)
|
|
292
309
|
|
|
@@ -296,11 +313,13 @@ export async function runScan(
|
|
|
296
313
|
...layer1Result,
|
|
297
314
|
vulnerabilities: aggregateLocalhostFindings(layer1Result.vulnerabilities)
|
|
298
315
|
}
|
|
299
|
-
|
|
316
|
+
phaseTiming.layer1 = Date.now() - layer1Start
|
|
317
|
+
log(`[Layer1] repo=${repoInfo.name} findings_raw=${layer1RawCount} findings_deduped=${layer1Result.vulnerabilities.length} duration=${phaseTiming.layer1}ms`)
|
|
300
318
|
|
|
301
319
|
checkCancelled()
|
|
302
320
|
|
|
303
321
|
// Layer 2: Structural Scan
|
|
322
|
+
const layer2Start = Date.now()
|
|
304
323
|
reportProgress('layer2', 'Running structural scan (variables, logic gates)...', layer1Result.vulnerabilities.length)
|
|
305
324
|
const layer2Result = await runLayer2Scan(files, { middlewareConfig, fileAuthImports }, onProgress, cancellationToken)
|
|
306
325
|
|
|
@@ -309,7 +328,8 @@ export async function runScan(
|
|
|
309
328
|
.filter(([, count]) => count > 0)
|
|
310
329
|
.map(([name, count]) => `${name}:${count}`)
|
|
311
330
|
.join(',')
|
|
312
|
-
|
|
331
|
+
phaseTiming.layer2 = Date.now() - layer2Start
|
|
332
|
+
log(`[Layer2] repo=${repoInfo.name} findings_raw=${Object.values(layer2Result.stats.raw).reduce((a, b) => a + b, 0)} findings_deduped=${layer2Result.vulnerabilities.length} duration=${phaseTiming.layer2}ms heuristic_breakdown={${heuristicBreakdown}}`)
|
|
313
333
|
|
|
314
334
|
// Combine Layer 1 and Layer 2 findings
|
|
315
335
|
const layer12Findings = [...layer1Result.vulnerabilities, ...layer2Result.vulnerabilities]
|
|
@@ -319,9 +339,19 @@ export async function runScan(
|
|
|
319
339
|
const aggregatedFindings = aggregateNoisyFindings(layer12Findings)
|
|
320
340
|
const aggregatedCount = beforeAggregationCount - aggregatedFindings.length
|
|
321
341
|
|
|
342
|
+
// Build project context for framework-aware fixes (PRO-83)
|
|
343
|
+
// This detects frameworks (Next.js, Express), ORMs (Prisma, Drizzle), and frontend libs (React, Vue)
|
|
344
|
+
const projectContext = buildProjectContext(files)
|
|
345
|
+
|
|
346
|
+
// Enrich findings with metadata from rule registry (PRO-82)
|
|
347
|
+
// PRO-83: Uses projectContext for framework-specific fix suggestions
|
|
348
|
+
// This provides default impact, evidence, fixSteps, references for all findings
|
|
349
|
+
// AI validation can override these later with context-aware content
|
|
350
|
+
const enrichedFindings = enrichWithMetadata(aggregatedFindings, projectContext)
|
|
351
|
+
|
|
322
352
|
// Apply tier-based filtering based on scan depth
|
|
323
353
|
// This is the key integration point for the detector tier system
|
|
324
|
-
const tierFiltered = filterByTierAndDepth(
|
|
354
|
+
const tierFiltered = filterByTierAndDepth(enrichedFindings, depth)
|
|
325
355
|
|
|
326
356
|
// Log tier breakdown
|
|
327
357
|
log(`[Scanner] repo=${repoInfo.name} tier_breakdown=${formatTierStats(tierFiltered.tierStats)}`)
|
|
@@ -339,7 +369,12 @@ export async function runScan(
|
|
|
339
369
|
)
|
|
340
370
|
|
|
341
371
|
// Surface findings that don't need validation (excluding those that do)
|
|
342
|
-
const
|
|
372
|
+
const noValidationNeededRaw = tierFiltered.toSurface.filter(v => !additionalValidation.includes(v))
|
|
373
|
+
|
|
374
|
+
// Apply auto-dismiss rules to direct-surface findings (mode='surface')
|
|
375
|
+
// Uses 'surface' mode to exclude cost-saving rules like 'info_severity_core_only'
|
|
376
|
+
// This ensures test/scanner/example files are dismissed, but info-severity findings still surface
|
|
377
|
+
const { toValidate: noValidationNeeded, dismissed: surfaceDismissed } = applyAutoDismissRules(noValidationNeededRaw, 'surface')
|
|
343
378
|
|
|
344
379
|
// Combine tier-filtered validation candidates with additional ones
|
|
345
380
|
const requiresValidation = [...tierFiltered.toValidate, ...additionalValidation]
|
|
@@ -347,13 +382,22 @@ export async function runScan(
|
|
|
347
382
|
// Apply smart auto-dismiss rules BEFORE AI validation (saves API costs)
|
|
348
383
|
const { toValidate: afterAutoDismiss, dismissed: autoDismissed } = applyAutoDismissRules(requiresValidation)
|
|
349
384
|
|
|
350
|
-
//
|
|
385
|
+
// Combine all dismissed findings for logging
|
|
386
|
+
const allDismissed = [...surfaceDismissed, ...autoDismissed]
|
|
387
|
+
|
|
388
|
+
// Track auto-dismiss by severity and category for logging
|
|
351
389
|
const autoDismissBySeverity: Record<string, number> = { info: 0, low: 0, medium: 0, high: 0, critical: 0 }
|
|
352
|
-
|
|
390
|
+
const autoDismissByCategory: Record<string, number> = {}
|
|
391
|
+
for (const d of allDismissed) {
|
|
353
392
|
autoDismissBySeverity[d.finding.severity] = (autoDismissBySeverity[d.finding.severity] || 0) + 1
|
|
393
|
+
autoDismissByCategory[d.finding.category] = (autoDismissByCategory[d.finding.category] || 0) + 1
|
|
354
394
|
}
|
|
355
|
-
if (
|
|
356
|
-
|
|
395
|
+
if (allDismissed.length > 0) {
|
|
396
|
+
const categoryBreakdown = Object.entries(autoDismissByCategory)
|
|
397
|
+
.sort(([, a], [, b]) => b - a)
|
|
398
|
+
.map(([cat, count]) => `${cat}:${count}`)
|
|
399
|
+
.join(',')
|
|
400
|
+
log(`[AutoDismiss] repo=${repoInfo.name} total=${allDismissed.length} (surface=${surfaceDismissed.length} validation=${autoDismissed.length}) by_severity={info:${autoDismissBySeverity.info},low:${autoDismissBySeverity.low},medium:${autoDismissBySeverity.medium},high:${autoDismissBySeverity.high}} by_category={${categoryBreakdown}}`)
|
|
357
401
|
}
|
|
358
402
|
|
|
359
403
|
// Apply per-file cap to validation candidates (cost control)
|
|
@@ -370,6 +414,7 @@ export async function runScan(
|
|
|
370
414
|
|
|
371
415
|
if (shouldValidate) {
|
|
372
416
|
checkCancelled()
|
|
417
|
+
const aiValidationStart = Date.now()
|
|
373
418
|
reportProgress('validating', 'AI validating findings (entropy, secrets, AI patterns)...', cappedValidation.length)
|
|
374
419
|
|
|
375
420
|
// For incremental scans, only validate findings in changed files
|
|
@@ -396,8 +441,9 @@ export async function runScan(
|
|
|
396
441
|
validatedFindings = validationResult.vulnerabilities
|
|
397
442
|
const { stats: validationStats } = validationResult
|
|
398
443
|
capturedValidationStats = validationStats // Capture for return
|
|
444
|
+
phaseTiming.aiValidation = Date.now() - aiValidationStart
|
|
399
445
|
|
|
400
|
-
log(`[AI Validation] repo=${repoInfo.name} depth=${depth} candidates=${findingsToValidate.length} capped_from=${requiresValidation.length} auto_dismissed=${autoDismissed.length} kept=${validationStats.confirmedFindings} rejected=${validationStats.dismissedFindings} downgraded=${validationStats.downgradedFindings}`)
|
|
446
|
+
log(`[AI Validation] repo=${repoInfo.name} depth=${depth} duration=${phaseTiming.aiValidation}ms candidates=${findingsToValidate.length} capped_from=${requiresValidation.length} auto_dismissed=${autoDismissed.length} kept=${validationStats.confirmedFindings} rejected=${validationStats.dismissedFindings} downgraded=${validationStats.downgradedFindings}`)
|
|
401
447
|
log(`[AI Validation] cost_estimate: input_tokens=${validationStats.estimatedInputTokens} output_tokens=${validationStats.estimatedOutputTokens} cost=$${validationStats.estimatedCost.toFixed(4)} api_calls=${validationStats.apiCalls}`)
|
|
402
448
|
|
|
403
449
|
// Add back findings that weren't validated (not in changed files)
|
|
@@ -411,7 +457,11 @@ export async function runScan(
|
|
|
411
457
|
validatedFindings.push(...notValidated)
|
|
412
458
|
}
|
|
413
459
|
} else if (scanModeConfig.skipAIValidation) {
|
|
414
|
-
log(`[AI Validation] repo=${repoInfo.name} depth=${depth} skipped=true reason=scan_mode_config`)
|
|
460
|
+
log(`[AI Validation] repo=${repoInfo.name} depth=${depth} skipped=true reason=scan_mode_config findings_requiring_validation=${cappedValidation.length}`)
|
|
461
|
+
// In cheap mode, don't surface findings that require AI validation
|
|
462
|
+
// These are low-confidence without validation and would be noise
|
|
463
|
+
// Only surface high-confidence findings that don't need validation
|
|
464
|
+
validatedFindings = []
|
|
415
465
|
}
|
|
416
466
|
|
|
417
467
|
// Combine validated and non-validated findings
|
|
@@ -423,6 +473,7 @@ export async function runScan(
|
|
|
423
473
|
|
|
424
474
|
if (shouldRunLayer3) {
|
|
425
475
|
checkCancelled()
|
|
476
|
+
const layer3Start = Date.now()
|
|
426
477
|
reportProgress('layer3', 'Running AI semantic analysis...', allVulnerabilities.length)
|
|
427
478
|
|
|
428
479
|
// For incremental scans, only analyze changed files
|
|
@@ -448,26 +499,64 @@ export async function runScan(
|
|
|
448
499
|
},
|
|
449
500
|
cancellationToken,
|
|
450
501
|
})
|
|
502
|
+
phaseTiming.layer3 = Date.now() - layer3Start
|
|
451
503
|
allVulnerabilities.push(...layer3Result.vulnerabilities)
|
|
452
|
-
log(`[Layer3] repo=${repoInfo.name} depth=${depth} files_analyzed=${layer3Result.aiAnalyzed} findings=${layer3Result.vulnerabilities.length}`)
|
|
504
|
+
log(`[Layer3] repo=${repoInfo.name} depth=${depth} duration=${phaseTiming.layer3}ms files_analyzed=${layer3Result.aiAnalyzed} findings=${layer3Result.vulnerabilities.length}`)
|
|
453
505
|
} else if (scanModeConfig.skipLayer3) {
|
|
454
506
|
log(`[Layer3] repo=${repoInfo.name} depth=${depth} skipped=true reason=scan_mode_config`)
|
|
455
507
|
}
|
|
456
508
|
|
|
509
|
+
// Log phase timing summary
|
|
510
|
+
const phaseTimingStr = Object.entries(phaseTiming)
|
|
511
|
+
.filter(([, ms]) => ms !== undefined)
|
|
512
|
+
.map(([phase, ms]) => `${phase}=${ms}ms`)
|
|
513
|
+
.join(' ')
|
|
514
|
+
if (phaseTimingStr) {
|
|
515
|
+
log(`[Scanner] repo=${repoInfo.name} phase_timing: ${phaseTimingStr}`)
|
|
516
|
+
}
|
|
517
|
+
|
|
457
518
|
// Deduplicate vulnerabilities
|
|
458
519
|
const uniqueVulnerabilities = deduplicateVulnerabilities(allVulnerabilities)
|
|
459
520
|
|
|
460
521
|
// Resolve contradictions (e.g., middleware-protected INFO vs missing-auth CRITICAL on same route)
|
|
461
522
|
const resolvedVulnerabilities = resolveContradictions(uniqueVulnerabilities, middlewareConfig)
|
|
462
|
-
|
|
523
|
+
|
|
524
|
+
// Apply suppressions (inline comments + config file)
|
|
525
|
+
const projectPath = options.projectPath || process.cwd()
|
|
526
|
+
const suppressionManager = new SuppressionManager({ projectPath })
|
|
527
|
+
const suppressionResult = suppressionManager.applySuppressions(resolvedVulnerabilities, files)
|
|
528
|
+
|
|
529
|
+
// Log suppression stats if any were suppressed
|
|
530
|
+
if (suppressionResult.suppressed.length > 0 || suppressionResult.expiredSuppressions > 0) {
|
|
531
|
+
log(`[Suppression] repo=${repoInfo.name} suppressed=${suppressionResult.suppressed.length} (inline=${suppressionResult.stats.inlineSuppressed} config_finding=${suppressionResult.stats.configFindingSuppressed} config_rule=${suppressionResult.stats.configRuleSuppressed}) expired=${suppressionResult.expiredSuppressions}`)
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
// Use the filtered findings (after suppression)
|
|
535
|
+
const afterSuppression = suppressionResult.findings
|
|
536
|
+
|
|
463
537
|
// Sort by severity
|
|
464
|
-
const sortedVulnerabilities = sortBySeverity(
|
|
538
|
+
const sortedVulnerabilities = sortBySeverity(afterSuppression)
|
|
465
539
|
|
|
466
|
-
// Compute issue-mix counts
|
|
540
|
+
// Compute issue-mix counts (based on unsuppressed findings)
|
|
467
541
|
const severityCounts = computeSeverityCounts(sortedVulnerabilities)
|
|
468
542
|
const categoryCounts = computeCategoryCounts(sortedVulnerabilities)
|
|
469
543
|
const hasBlockingIssues = severityCounts.critical > 0 || severityCounts.high > 0
|
|
470
544
|
|
|
545
|
+
// Build suppressed vulnerabilities summary (for --show-suppressed)
|
|
546
|
+
const suppressedVulnerabilities: SuppressedVulnerabilitySummary[] | undefined = options.showSuppressed
|
|
547
|
+
? suppressionResult.suppressed.map(s => ({
|
|
548
|
+
hash: s.suppression.hash,
|
|
549
|
+
filePath: s.vulnerability.filePath,
|
|
550
|
+
lineNumber: s.vulnerability.lineNumber,
|
|
551
|
+
category: s.vulnerability.category,
|
|
552
|
+
severity: s.vulnerability.severity,
|
|
553
|
+
title: s.vulnerability.title,
|
|
554
|
+
suppressionType: s.suppression.type,
|
|
555
|
+
suppressionReason: s.suppression.reason,
|
|
556
|
+
expires: s.suppression.expires,
|
|
557
|
+
}))
|
|
558
|
+
: undefined
|
|
559
|
+
|
|
471
560
|
reportProgress('complete', 'Scan complete!', sortedVulnerabilities.length)
|
|
472
561
|
|
|
473
562
|
return {
|
|
@@ -483,6 +572,10 @@ export async function runScan(
|
|
|
483
572
|
scanDuration: Date.now() - startTime,
|
|
484
573
|
timestamp: new Date().toISOString(),
|
|
485
574
|
validationStats: capturedValidationStats,
|
|
575
|
+
suppressionStats: suppressionResult.suppressed.length > 0 || suppressionResult.expiredSuppressions > 0
|
|
576
|
+
? suppressionResult.stats
|
|
577
|
+
: undefined,
|
|
578
|
+
suppressedVulnerabilities,
|
|
486
579
|
}
|
|
487
580
|
} catch (error) {
|
|
488
581
|
if (cancellationToken?.cancelled) {
|
|
@@ -519,6 +612,50 @@ export async function runScan(
|
|
|
519
612
|
}
|
|
520
613
|
}
|
|
521
614
|
|
|
615
|
+
/**
|
|
616
|
+
* Enrich findings with metadata from the rule registry (PRO-82)
|
|
617
|
+
* Sets default impact, evidence, fixSteps, and references from registry
|
|
618
|
+
*
|
|
619
|
+
* PRO-83: When projectContext is provided, uses framework-aware fix suggestions
|
|
620
|
+
* that are tailored to the user's detected tech stack (e.g., Prisma-specific
|
|
621
|
+
* SQL injection fixes instead of generic advice).
|
|
622
|
+
*
|
|
623
|
+
* These can be overridden later by AI-generated content
|
|
624
|
+
*/
|
|
625
|
+
function enrichWithMetadata(
|
|
626
|
+
findings: Vulnerability[],
|
|
627
|
+
projectContext?: ProjectContext
|
|
628
|
+
): Vulnerability[] {
|
|
629
|
+
return findings.map(f => {
|
|
630
|
+
const metadata = getRuleMetadata(f.category)
|
|
631
|
+
if (!metadata) return f
|
|
632
|
+
|
|
633
|
+
// PRO-83: Check for framework-specific fix suggestions
|
|
634
|
+
let fixSteps = metadata.fixSteps
|
|
635
|
+
if (projectContext) {
|
|
636
|
+
const frameworkFix = getFrameworkFix(
|
|
637
|
+
f.category,
|
|
638
|
+
projectContext.frameworks,
|
|
639
|
+
projectContext.dataAccess
|
|
640
|
+
)
|
|
641
|
+
if (frameworkFix) {
|
|
642
|
+
fixSteps = frameworkFix.fixSteps
|
|
643
|
+
// Optionally append code example to description if available
|
|
644
|
+
// This makes the fix more actionable
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
return {
|
|
649
|
+
...f,
|
|
650
|
+
// Set defaults from registry (AI can override later)
|
|
651
|
+
impact: f.impact || metadata.whyItMatters,
|
|
652
|
+
evidence: f.evidence || metadata.evidence,
|
|
653
|
+
fixSteps: f.fixSteps || fixSteps,
|
|
654
|
+
references: f.references || metadata.references,
|
|
655
|
+
}
|
|
656
|
+
})
|
|
657
|
+
}
|
|
658
|
+
|
|
522
659
|
/**
|
|
523
660
|
* Aggregate noisy findings in the same file to reduce clutter
|
|
524
661
|
* Groups repeated findings with same filePath + category + title
|
|
@@ -905,3 +1042,49 @@ export { runLayer3Scan } from './layer3'
|
|
|
905
1042
|
export { buildProjectContext, type ProjectContext } from './utils/project-context-builder'
|
|
906
1043
|
export { validateFindingsWithAI, type ValidationStats, type AIValidationResult } from './layer3/anthropic'
|
|
907
1044
|
export { createCancellationToken } from './types'
|
|
1045
|
+
|
|
1046
|
+
// Suppression system exports
|
|
1047
|
+
export {
|
|
1048
|
+
SuppressionManager,
|
|
1049
|
+
computeFindingHash,
|
|
1050
|
+
loadSuppressionConfig,
|
|
1051
|
+
addFindingSuppression,
|
|
1052
|
+
removeFindingSuppression,
|
|
1053
|
+
addRuleSuppression,
|
|
1054
|
+
listSuppressions,
|
|
1055
|
+
parseInlineSuppressions,
|
|
1056
|
+
generateSuppressionComment,
|
|
1057
|
+
isValidHash,
|
|
1058
|
+
type SuppressionConfig,
|
|
1059
|
+
type FindingSuppression,
|
|
1060
|
+
type RuleSuppression,
|
|
1061
|
+
type SuppressionResult,
|
|
1062
|
+
type SuppressedVulnerability,
|
|
1063
|
+
} from './suppression'
|
|
1064
|
+
|
|
1065
|
+
// Baseline system exports
|
|
1066
|
+
export {
|
|
1067
|
+
BaselineManager,
|
|
1068
|
+
computeDiff,
|
|
1069
|
+
hasNewBlockingIssues,
|
|
1070
|
+
formatDiffSummary,
|
|
1071
|
+
BASELINE_FILE_PATH,
|
|
1072
|
+
OCULUM_DIR,
|
|
1073
|
+
type BaselineData,
|
|
1074
|
+
type BaselineFinding,
|
|
1075
|
+
type DiffResult,
|
|
1076
|
+
type BaselineDiff,
|
|
1077
|
+
type BaselineManagerOptions,
|
|
1078
|
+
type LoadBaselineResult,
|
|
1079
|
+
type SaveBaselineResult,
|
|
1080
|
+
type ClearBaselineResult,
|
|
1081
|
+
} from './baseline'
|
|
1082
|
+
|
|
1083
|
+
// Rule metadata exports (PRO-82)
|
|
1084
|
+
export {
|
|
1085
|
+
RULE_REGISTRY,
|
|
1086
|
+
getRuleMetadata,
|
|
1087
|
+
getAllCategories,
|
|
1088
|
+
hasMetadata,
|
|
1089
|
+
type RuleMetadata,
|
|
1090
|
+
} from './rules'
|
|
@@ -211,12 +211,33 @@ export const CONFIG_RULES: ConfigRule[] = [
|
|
|
211
211
|
// Get the package's own npm scope to detect internal monorepo deps
|
|
212
212
|
const ownScope = pkg.name?.startsWith('@') ? pkg.name.split('/')[0] : null
|
|
213
213
|
|
|
214
|
+
// Also detect common org scope from other dependencies (for packages without scoped names)
|
|
215
|
+
// If we see @org/foo and @org/bar in deps, @org is likely the monorepo's scope
|
|
216
|
+
const scopeCounts: Record<string, number> = {}
|
|
217
|
+
for (const depName of Object.keys(allDeps)) {
|
|
218
|
+
if (depName.startsWith('@')) {
|
|
219
|
+
const scope = depName.split('/')[0]
|
|
220
|
+
scopeCounts[scope] = (scopeCounts[scope] || 0) + 1
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
// Find the most common scope (likely the monorepo's org scope)
|
|
224
|
+
const inferredScope = Object.entries(scopeCounts)
|
|
225
|
+
.filter(([, count]) => count >= 2) // At least 2 deps from same scope
|
|
226
|
+
.sort((a, b) => b[1] - a[1])[0]?.[0] || null
|
|
227
|
+
|
|
214
228
|
for (const [name, version] of Object.entries(allDeps)) {
|
|
215
229
|
if (version === '*' || version === 'latest') {
|
|
216
|
-
// Skip internal monorepo packages
|
|
217
|
-
//
|
|
230
|
+
// Skip internal monorepo packages:
|
|
231
|
+
// 1. Same npm scope as the package itself
|
|
232
|
+
// 2. Or same scope as other workspace packages (inferred from multiple deps)
|
|
218
233
|
const depScope = name.startsWith('@') ? name.split('/')[0] : null
|
|
219
|
-
|
|
234
|
+
|
|
235
|
+
// Check if this is a workspace package
|
|
236
|
+
const isWorkspacePackage =
|
|
237
|
+
(ownScope && depScope === ownScope) ||
|
|
238
|
+
(inferredScope && depScope === inferredScope && version === '*')
|
|
239
|
+
|
|
240
|
+
if (isWorkspacePackage) {
|
|
220
241
|
continue
|
|
221
242
|
}
|
|
222
243
|
|