@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,273 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Inline Suppression Comment Parser
|
|
3
|
+
* Parses oculum-ignore comments from source code
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { InlineSuppression } from './types'
|
|
7
|
+
import type { VulnerabilityCategory } from '../types'
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Valid suppression comment patterns:
|
|
11
|
+
*
|
|
12
|
+
* Single-line suppressions:
|
|
13
|
+
* // oculum-ignore-next-line: reason here
|
|
14
|
+
* // oculum-ignore-next-line [rule-id]: reason here
|
|
15
|
+
* code // oculum-ignore: reason here
|
|
16
|
+
* code // oculum-ignore [rule-id]: reason here
|
|
17
|
+
*
|
|
18
|
+
* Block suppressions:
|
|
19
|
+
* /* oculum-ignore-block: reason here * /
|
|
20
|
+
* ... code ...
|
|
21
|
+
* /* oculum-ignore-block-end * /
|
|
22
|
+
*
|
|
23
|
+
* Also supports:
|
|
24
|
+
* # oculum-ignore-next-line: reason (Python, Ruby, YAML, etc.)
|
|
25
|
+
* -- oculum-ignore-next-line: reason (SQL)
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
// Regex patterns for parsing comments
|
|
29
|
+
const PATTERNS = {
|
|
30
|
+
// // oculum-ignore-next-line: reason
|
|
31
|
+
// // oculum-ignore-next-line [rule-id]: reason
|
|
32
|
+
nextLine: /(?:\/\/|#|--)\s*oculum-ignore-next-line(?:\s*\[([^\]]+)\])?\s*:\s*(.+)/i,
|
|
33
|
+
|
|
34
|
+
// code // oculum-ignore: reason
|
|
35
|
+
// code // oculum-ignore [rule-id]: reason
|
|
36
|
+
sameLine: /(?:\/\/|#|--)\s*oculum-ignore(?:\s*\[([^\]]+)\])?\s*:\s*(.+)/i,
|
|
37
|
+
|
|
38
|
+
// /* oculum-ignore-block: reason */
|
|
39
|
+
// /* oculum-ignore-block [rule-id]: reason */
|
|
40
|
+
blockStart: /\/\*\s*oculum-ignore-block(?:\s*\[([^\]]+)\])?\s*:\s*([^*]+)\*\//i,
|
|
41
|
+
|
|
42
|
+
// /* oculum-ignore-block-end */
|
|
43
|
+
blockEnd: /\/\*\s*oculum-ignore-block-end\s*\*\//i,
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Parse a single line for suppression comments
|
|
48
|
+
*/
|
|
49
|
+
function parseLineForSuppression(
|
|
50
|
+
line: string,
|
|
51
|
+
lineNumber: number
|
|
52
|
+
): InlineSuppression | null {
|
|
53
|
+
// Check for next-line suppression (must be the only thing on the line, aside from whitespace)
|
|
54
|
+
const trimmed = line.trim()
|
|
55
|
+
|
|
56
|
+
// Next-line pattern (comment at start of line)
|
|
57
|
+
if (
|
|
58
|
+
trimmed.startsWith('//') ||
|
|
59
|
+
trimmed.startsWith('#') ||
|
|
60
|
+
trimmed.startsWith('--')
|
|
61
|
+
) {
|
|
62
|
+
const nextLineMatch = trimmed.match(PATTERNS.nextLine)
|
|
63
|
+
if (nextLineMatch) {
|
|
64
|
+
return {
|
|
65
|
+
lineNumber,
|
|
66
|
+
type: 'next-line',
|
|
67
|
+
ruleId: nextLineMatch[1] as VulnerabilityCategory | undefined,
|
|
68
|
+
reason: nextLineMatch[2].trim(),
|
|
69
|
+
commentText: trimmed,
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Same-line pattern (comment at end of line with code before)
|
|
75
|
+
// Only check if there's actual code before the comment
|
|
76
|
+
const sameLineMatch = line.match(PATTERNS.sameLine)
|
|
77
|
+
if (sameLineMatch) {
|
|
78
|
+
// Find where the comment starts
|
|
79
|
+
const commentIndex = line.search(/(?:\/\/|#|--)\s*oculum-ignore/i)
|
|
80
|
+
const beforeComment = line.substring(0, commentIndex).trim()
|
|
81
|
+
|
|
82
|
+
// Only treat as same-line if there's code before the comment
|
|
83
|
+
// (not just whitespace or if it's at the start)
|
|
84
|
+
if (beforeComment.length > 0) {
|
|
85
|
+
return {
|
|
86
|
+
lineNumber,
|
|
87
|
+
type: 'same-line',
|
|
88
|
+
ruleId: sameLineMatch[1] as VulnerabilityCategory | undefined,
|
|
89
|
+
reason: sameLineMatch[2].trim(),
|
|
90
|
+
commentText: line.substring(commentIndex).trim(),
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Block start pattern
|
|
96
|
+
const blockStartMatch = line.match(PATTERNS.blockStart)
|
|
97
|
+
if (blockStartMatch) {
|
|
98
|
+
return {
|
|
99
|
+
lineNumber,
|
|
100
|
+
type: 'block-start',
|
|
101
|
+
ruleId: blockStartMatch[1] as VulnerabilityCategory | undefined,
|
|
102
|
+
reason: blockStartMatch[2].trim(),
|
|
103
|
+
commentText: blockStartMatch[0],
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Block end pattern
|
|
108
|
+
const blockEndMatch = line.match(PATTERNS.blockEnd)
|
|
109
|
+
if (blockEndMatch) {
|
|
110
|
+
return {
|
|
111
|
+
lineNumber,
|
|
112
|
+
type: 'block-end',
|
|
113
|
+
reason: '', // Block end has no reason
|
|
114
|
+
commentText: blockEndMatch[0],
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return null
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Parse all inline suppressions from file content
|
|
123
|
+
*
|
|
124
|
+
* Returns a map of line numbers to their effective suppressions.
|
|
125
|
+
* A line can be suppressed by:
|
|
126
|
+
* 1. A same-line comment (oculum-ignore)
|
|
127
|
+
* 2. A next-line comment on the previous line (oculum-ignore-next-line)
|
|
128
|
+
* 3. Being within a block suppression (oculum-ignore-block ... oculum-ignore-block-end)
|
|
129
|
+
*/
|
|
130
|
+
export function parseInlineSuppressions(content: string): Map<number, InlineSuppression> {
|
|
131
|
+
const lines = content.split('\n')
|
|
132
|
+
const suppressions = new Map<number, InlineSuppression>()
|
|
133
|
+
|
|
134
|
+
// Track block suppression state
|
|
135
|
+
let currentBlock: { startLine: number; reason: string; ruleId?: VulnerabilityCategory } | null = null
|
|
136
|
+
|
|
137
|
+
for (let i = 0; i < lines.length; i++) {
|
|
138
|
+
const lineNumber = i + 1 // 1-indexed
|
|
139
|
+
const line = lines[i]
|
|
140
|
+
|
|
141
|
+
const suppression = parseLineForSuppression(line, lineNumber)
|
|
142
|
+
|
|
143
|
+
if (suppression) {
|
|
144
|
+
switch (suppression.type) {
|
|
145
|
+
case 'next-line':
|
|
146
|
+
// Apply to the next line
|
|
147
|
+
if (i + 1 < lines.length) {
|
|
148
|
+
suppressions.set(lineNumber + 1, {
|
|
149
|
+
...suppression,
|
|
150
|
+
lineNumber: lineNumber + 1,
|
|
151
|
+
})
|
|
152
|
+
}
|
|
153
|
+
break
|
|
154
|
+
|
|
155
|
+
case 'same-line':
|
|
156
|
+
// Apply to this line
|
|
157
|
+
suppressions.set(lineNumber, suppression)
|
|
158
|
+
break
|
|
159
|
+
|
|
160
|
+
case 'block-start':
|
|
161
|
+
// Start tracking block suppression
|
|
162
|
+
currentBlock = {
|
|
163
|
+
startLine: lineNumber,
|
|
164
|
+
reason: suppression.reason,
|
|
165
|
+
ruleId: suppression.ruleId,
|
|
166
|
+
}
|
|
167
|
+
break
|
|
168
|
+
|
|
169
|
+
case 'block-end':
|
|
170
|
+
// End block suppression
|
|
171
|
+
currentBlock = null
|
|
172
|
+
break
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// If we're in a block suppression, add suppression for this line
|
|
177
|
+
// (unless it's the block start/end line itself)
|
|
178
|
+
if (currentBlock && suppression?.type !== 'block-start' && suppression?.type !== 'block-end') {
|
|
179
|
+
// Don't override explicit same-line suppressions
|
|
180
|
+
if (!suppressions.has(lineNumber)) {
|
|
181
|
+
suppressions.set(lineNumber, {
|
|
182
|
+
lineNumber,
|
|
183
|
+
type: 'block-start', // Mark as from block
|
|
184
|
+
reason: currentBlock.reason,
|
|
185
|
+
ruleId: currentBlock.ruleId,
|
|
186
|
+
commentText: `Block suppression from line ${currentBlock.startLine}`,
|
|
187
|
+
})
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return suppressions
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Check if a specific line is suppressed
|
|
197
|
+
*/
|
|
198
|
+
export function isLineSuppressed(
|
|
199
|
+
suppressions: Map<number, InlineSuppression>,
|
|
200
|
+
lineNumber: number,
|
|
201
|
+
category?: VulnerabilityCategory
|
|
202
|
+
): InlineSuppression | null {
|
|
203
|
+
const suppression = suppressions.get(lineNumber)
|
|
204
|
+
|
|
205
|
+
if (!suppression) {
|
|
206
|
+
return null
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// If suppression specifies a rule and category doesn't match, not suppressed
|
|
210
|
+
if (suppression.ruleId && category && suppression.ruleId !== category) {
|
|
211
|
+
return null
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
return suppression
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Extract all unique suppression reasons from a file
|
|
219
|
+
* Useful for auditing suppressed findings
|
|
220
|
+
*/
|
|
221
|
+
export function extractSuppressionReasons(content: string): Array<{
|
|
222
|
+
reason: string
|
|
223
|
+
ruleId?: VulnerabilityCategory
|
|
224
|
+
lineNumber: number
|
|
225
|
+
type: InlineSuppression['type']
|
|
226
|
+
}> {
|
|
227
|
+
const suppressions = parseInlineSuppressions(content)
|
|
228
|
+
const seen = new Set<string>()
|
|
229
|
+
const results: Array<{
|
|
230
|
+
reason: string
|
|
231
|
+
ruleId?: VulnerabilityCategory
|
|
232
|
+
lineNumber: number
|
|
233
|
+
type: InlineSuppression['type']
|
|
234
|
+
}> = []
|
|
235
|
+
|
|
236
|
+
for (const [lineNumber, suppression] of suppressions) {
|
|
237
|
+
// Only include unique comment sources (not duplicates from block propagation)
|
|
238
|
+
const key = `${suppression.commentText}`
|
|
239
|
+
if (!seen.has(key) && suppression.type !== 'block-end') {
|
|
240
|
+
seen.add(key)
|
|
241
|
+
results.push({
|
|
242
|
+
reason: suppression.reason,
|
|
243
|
+
ruleId: suppression.ruleId,
|
|
244
|
+
lineNumber,
|
|
245
|
+
type: suppression.type,
|
|
246
|
+
})
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
return results
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Generate a suppression comment for a given line
|
|
255
|
+
*/
|
|
256
|
+
export function generateSuppressionComment(
|
|
257
|
+
reason: string,
|
|
258
|
+
options: {
|
|
259
|
+
type?: 'next-line' | 'same-line'
|
|
260
|
+
ruleId?: VulnerabilityCategory
|
|
261
|
+
commentStyle?: '//' | '#' | '--'
|
|
262
|
+
} = {}
|
|
263
|
+
): string {
|
|
264
|
+
const { type = 'next-line', ruleId, commentStyle = '//' } = options
|
|
265
|
+
|
|
266
|
+
const ruleSpec = ruleId ? ` [${ruleId}]` : ''
|
|
267
|
+
|
|
268
|
+
if (type === 'next-line') {
|
|
269
|
+
return `${commentStyle} oculum-ignore-next-line${ruleSpec}: ${reason}`
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
return `${commentStyle} oculum-ignore${ruleSpec}: ${reason}`
|
|
273
|
+
}
|
|
@@ -0,0 +1,379 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Suppression Manager
|
|
3
|
+
* Central class for managing all suppression logic
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { minimatch } from 'minimatch'
|
|
7
|
+
import type { Vulnerability, ScanFile } from '../types'
|
|
8
|
+
import type {
|
|
9
|
+
SuppressionConfig,
|
|
10
|
+
SuppressionMatch,
|
|
11
|
+
SuppressionResult,
|
|
12
|
+
SuppressedVulnerability,
|
|
13
|
+
InlineSuppression,
|
|
14
|
+
RuleSuppression,
|
|
15
|
+
FindingSuppression,
|
|
16
|
+
} from './types'
|
|
17
|
+
import { loadSuppressionConfig, isExpired } from './config-loader'
|
|
18
|
+
import { parseInlineSuppressions, isLineSuppressed } from './inline-parser'
|
|
19
|
+
import { computeFindingHash, normalizePathForHash } from './hash'
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Options for SuppressionManager
|
|
23
|
+
*/
|
|
24
|
+
export interface SuppressionManagerOptions {
|
|
25
|
+
/** Path to the project root (for finding config files) */
|
|
26
|
+
projectPath: string
|
|
27
|
+
/** Optional pre-loaded config (skips file loading) */
|
|
28
|
+
config?: SuppressionConfig
|
|
29
|
+
/** Whether to include expired suppressions (for auditing) */
|
|
30
|
+
includeExpired?: boolean
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* SuppressionManager handles all suppression logic
|
|
35
|
+
*
|
|
36
|
+
* Priority order for suppressions:
|
|
37
|
+
* 1. Inline comment suppressions (highest priority)
|
|
38
|
+
* 2. Config file finding suppressions (by hash)
|
|
39
|
+
* 3. Config file rule suppressions (by category)
|
|
40
|
+
*/
|
|
41
|
+
export class SuppressionManager {
|
|
42
|
+
private config: SuppressionConfig
|
|
43
|
+
private configPath: string | undefined
|
|
44
|
+
private configErrors: string[]
|
|
45
|
+
private includeExpired: boolean
|
|
46
|
+
private projectPath: string
|
|
47
|
+
|
|
48
|
+
// Cache for inline suppressions by file path
|
|
49
|
+
private inlineSuppressionCache: Map<string, Map<number, InlineSuppression>> = new Map()
|
|
50
|
+
|
|
51
|
+
constructor(options: SuppressionManagerOptions) {
|
|
52
|
+
this.projectPath = options.projectPath
|
|
53
|
+
this.includeExpired = options.includeExpired ?? false
|
|
54
|
+
|
|
55
|
+
if (options.config) {
|
|
56
|
+
this.config = options.config
|
|
57
|
+
this.configPath = undefined
|
|
58
|
+
this.configErrors = []
|
|
59
|
+
} else {
|
|
60
|
+
const result = loadSuppressionConfig(options.projectPath)
|
|
61
|
+
this.config = result.config
|
|
62
|
+
this.configPath = result.configPath
|
|
63
|
+
this.configErrors = result.errors
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Get configuration errors (if any)
|
|
69
|
+
*/
|
|
70
|
+
getConfigErrors(): string[] {
|
|
71
|
+
return this.configErrors
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Get the path to the config file (if found)
|
|
76
|
+
*/
|
|
77
|
+
getConfigPath(): string | undefined {
|
|
78
|
+
return this.configPath
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Check if a file path should be ignored entirely
|
|
83
|
+
*/
|
|
84
|
+
isPathIgnored(filePath: string): boolean {
|
|
85
|
+
if (!this.config.ignore || this.config.ignore.length === 0) {
|
|
86
|
+
return false
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const normalizedPath = normalizePathForHash(filePath)
|
|
90
|
+
|
|
91
|
+
return this.config.ignore.some(pattern =>
|
|
92
|
+
minimatch(normalizedPath, pattern, { dot: true })
|
|
93
|
+
)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Parse inline suppressions for a file
|
|
98
|
+
*/
|
|
99
|
+
private getInlineSuppressions(
|
|
100
|
+
filePath: string,
|
|
101
|
+
content: string
|
|
102
|
+
): Map<number, InlineSuppression> {
|
|
103
|
+
// Check cache first
|
|
104
|
+
const cached = this.inlineSuppressionCache.get(filePath)
|
|
105
|
+
if (cached) {
|
|
106
|
+
return cached
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Parse and cache
|
|
110
|
+
const suppressions = parseInlineSuppressions(content)
|
|
111
|
+
this.inlineSuppressionCache.set(filePath, suppressions)
|
|
112
|
+
return suppressions
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Check if a finding is suppressed
|
|
117
|
+
*
|
|
118
|
+
* Priority:
|
|
119
|
+
* 1. Inline comment (highest)
|
|
120
|
+
* 2. Config finding suppression (by hash)
|
|
121
|
+
* 3. Config rule suppression (by category)
|
|
122
|
+
*/
|
|
123
|
+
isFindingSuppressed(
|
|
124
|
+
finding: Vulnerability,
|
|
125
|
+
fileContent?: string
|
|
126
|
+
): SuppressionMatch {
|
|
127
|
+
const hash = computeFindingHash(finding)
|
|
128
|
+
const normalizedPath = normalizePathForHash(finding.filePath)
|
|
129
|
+
|
|
130
|
+
// 1. Check inline suppressions (if file content provided)
|
|
131
|
+
if (fileContent) {
|
|
132
|
+
const inlineSuppressions = this.getInlineSuppressions(finding.filePath, fileContent)
|
|
133
|
+
const inlineSuppression = isLineSuppressed(
|
|
134
|
+
inlineSuppressions,
|
|
135
|
+
finding.lineNumber,
|
|
136
|
+
finding.category
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
if (inlineSuppression) {
|
|
140
|
+
return {
|
|
141
|
+
suppressed: true,
|
|
142
|
+
match: {
|
|
143
|
+
type: 'inline',
|
|
144
|
+
reason: inlineSuppression.reason,
|
|
145
|
+
},
|
|
146
|
+
hash,
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// 2. Check config finding suppressions (by hash)
|
|
152
|
+
const findingSuppressions = this.config.suppressions?.findings || []
|
|
153
|
+
const findingSuppression = findingSuppressions.find(s => s.hash === hash)
|
|
154
|
+
|
|
155
|
+
if (findingSuppression) {
|
|
156
|
+
const expired = isExpired(findingSuppression.expires)
|
|
157
|
+
|
|
158
|
+
if (expired && !this.includeExpired) {
|
|
159
|
+
// Expired - not suppressed
|
|
160
|
+
return {
|
|
161
|
+
suppressed: false,
|
|
162
|
+
match: {
|
|
163
|
+
type: 'config-finding',
|
|
164
|
+
reason: findingSuppression.reason,
|
|
165
|
+
expires: findingSuppression.expires,
|
|
166
|
+
expired: true,
|
|
167
|
+
},
|
|
168
|
+
hash,
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
return {
|
|
173
|
+
suppressed: true,
|
|
174
|
+
match: {
|
|
175
|
+
type: 'config-finding',
|
|
176
|
+
reason: findingSuppression.reason,
|
|
177
|
+
expires: findingSuppression.expires,
|
|
178
|
+
expired,
|
|
179
|
+
},
|
|
180
|
+
hash,
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// 3. Check config rule suppressions (by category)
|
|
185
|
+
const ruleSuppressions = this.config.suppressions?.rules || []
|
|
186
|
+
const ruleSuppression = ruleSuppressions.find(s => {
|
|
187
|
+
// Must match category
|
|
188
|
+
if (s.category !== finding.category) {
|
|
189
|
+
return false
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// If paths specified, must match one of them
|
|
193
|
+
if (s.paths && s.paths.length > 0) {
|
|
194
|
+
const matchesPath = s.paths.some(pattern =>
|
|
195
|
+
minimatch(normalizedPath, pattern, { dot: true })
|
|
196
|
+
)
|
|
197
|
+
if (!matchesPath) {
|
|
198
|
+
return false
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return true
|
|
203
|
+
})
|
|
204
|
+
|
|
205
|
+
if (ruleSuppression) {
|
|
206
|
+
const expired = isExpired(ruleSuppression.expires)
|
|
207
|
+
|
|
208
|
+
if (expired && !this.includeExpired) {
|
|
209
|
+
// Expired - not suppressed
|
|
210
|
+
return {
|
|
211
|
+
suppressed: false,
|
|
212
|
+
match: {
|
|
213
|
+
type: 'config-rule',
|
|
214
|
+
reason: ruleSuppression.reason,
|
|
215
|
+
expires: ruleSuppression.expires,
|
|
216
|
+
expired: true,
|
|
217
|
+
},
|
|
218
|
+
hash,
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
return {
|
|
223
|
+
suppressed: true,
|
|
224
|
+
match: {
|
|
225
|
+
type: 'config-rule',
|
|
226
|
+
reason: ruleSuppression.reason,
|
|
227
|
+
expires: ruleSuppression.expires,
|
|
228
|
+
expired,
|
|
229
|
+
},
|
|
230
|
+
hash,
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// Not suppressed
|
|
235
|
+
return {
|
|
236
|
+
suppressed: false,
|
|
237
|
+
hash,
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Apply suppressions to a list of findings
|
|
243
|
+
*/
|
|
244
|
+
applySuppressions(
|
|
245
|
+
findings: Vulnerability[],
|
|
246
|
+
files: ScanFile[]
|
|
247
|
+
): SuppressionResult {
|
|
248
|
+
// Build file content lookup
|
|
249
|
+
const fileContentMap = new Map<string, string>()
|
|
250
|
+
for (const file of files) {
|
|
251
|
+
fileContentMap.set(normalizePathForHash(file.path), file.content)
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
const passed: Vulnerability[] = []
|
|
255
|
+
const suppressed: SuppressedVulnerability[] = []
|
|
256
|
+
let expiredCount = 0
|
|
257
|
+
|
|
258
|
+
const stats = {
|
|
259
|
+
total: findings.length,
|
|
260
|
+
inlineSuppressed: 0,
|
|
261
|
+
configFindingSuppressed: 0,
|
|
262
|
+
configRuleSuppressed: 0,
|
|
263
|
+
expired: 0,
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
for (const finding of findings) {
|
|
267
|
+
// Get file content for inline suppression checking
|
|
268
|
+
const normalizedPath = normalizePathForHash(finding.filePath)
|
|
269
|
+
const fileContent = fileContentMap.get(normalizedPath)
|
|
270
|
+
|
|
271
|
+
const result = this.isFindingSuppressed(finding, fileContent)
|
|
272
|
+
|
|
273
|
+
if (result.suppressed) {
|
|
274
|
+
suppressed.push({
|
|
275
|
+
vulnerability: {
|
|
276
|
+
id: finding.id,
|
|
277
|
+
filePath: finding.filePath,
|
|
278
|
+
lineNumber: finding.lineNumber,
|
|
279
|
+
category: finding.category,
|
|
280
|
+
severity: finding.severity,
|
|
281
|
+
title: finding.title,
|
|
282
|
+
},
|
|
283
|
+
suppression: {
|
|
284
|
+
type: result.match!.type,
|
|
285
|
+
reason: result.match!.reason,
|
|
286
|
+
expires: result.match!.expires,
|
|
287
|
+
hash: result.hash,
|
|
288
|
+
},
|
|
289
|
+
})
|
|
290
|
+
|
|
291
|
+
// Update stats
|
|
292
|
+
switch (result.match!.type) {
|
|
293
|
+
case 'inline':
|
|
294
|
+
stats.inlineSuppressed++
|
|
295
|
+
break
|
|
296
|
+
case 'config-finding':
|
|
297
|
+
stats.configFindingSuppressed++
|
|
298
|
+
break
|
|
299
|
+
case 'config-rule':
|
|
300
|
+
stats.configRuleSuppressed++
|
|
301
|
+
break
|
|
302
|
+
}
|
|
303
|
+
} else {
|
|
304
|
+
passed.push(finding)
|
|
305
|
+
|
|
306
|
+
// Track expired suppressions
|
|
307
|
+
if (result.match?.expired) {
|
|
308
|
+
expiredCount++
|
|
309
|
+
stats.expired++
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
return {
|
|
315
|
+
findings: passed,
|
|
316
|
+
suppressed,
|
|
317
|
+
expiredSuppressions: expiredCount,
|
|
318
|
+
stats,
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* Get all suppressions from config
|
|
324
|
+
*/
|
|
325
|
+
getAllSuppressions(): {
|
|
326
|
+
rules: RuleSuppression[]
|
|
327
|
+
findings: FindingSuppression[]
|
|
328
|
+
} {
|
|
329
|
+
return {
|
|
330
|
+
rules: this.config.suppressions?.rules || [],
|
|
331
|
+
findings: this.config.suppressions?.findings || [],
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* Get ignore patterns from config
|
|
337
|
+
*/
|
|
338
|
+
getIgnorePatterns(): string[] {
|
|
339
|
+
return this.config.ignore || []
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* Check if suppression system is active (has any suppressions)
|
|
344
|
+
*/
|
|
345
|
+
hasSuppressions(): boolean {
|
|
346
|
+
const rules = this.config.suppressions?.rules || []
|
|
347
|
+
const findings = this.config.suppressions?.findings || []
|
|
348
|
+
const ignore = this.config.ignore || []
|
|
349
|
+
|
|
350
|
+
return rules.length > 0 || findings.length > 0 || ignore.length > 0
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
/**
|
|
354
|
+
* Get summary of suppression configuration
|
|
355
|
+
*/
|
|
356
|
+
getSummary(): {
|
|
357
|
+
configPath: string | undefined
|
|
358
|
+
ruleCount: number
|
|
359
|
+
findingCount: number
|
|
360
|
+
ignorePatternCount: number
|
|
361
|
+
hasErrors: boolean
|
|
362
|
+
} {
|
|
363
|
+
return {
|
|
364
|
+
configPath: this.configPath,
|
|
365
|
+
ruleCount: this.config.suppressions?.rules?.length || 0,
|
|
366
|
+
findingCount: this.config.suppressions?.findings?.length || 0,
|
|
367
|
+
ignorePatternCount: this.config.ignore?.length || 0,
|
|
368
|
+
hasErrors: this.configErrors.length > 0,
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
/**
|
|
373
|
+
* Clear the inline suppression cache
|
|
374
|
+
* (useful if files have been modified)
|
|
375
|
+
*/
|
|
376
|
+
clearCache(): void {
|
|
377
|
+
this.inlineSuppressionCache.clear()
|
|
378
|
+
}
|
|
379
|
+
}
|