@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,462 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Suppression Config Loader
|
|
3
|
+
* Loads and validates suppression configuration from various file formats
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { readFileSync, writeFileSync, existsSync } from 'fs'
|
|
7
|
+
import { join, dirname } from 'path'
|
|
8
|
+
import * as yaml from 'js-yaml'
|
|
9
|
+
import type {
|
|
10
|
+
SuppressionConfig,
|
|
11
|
+
RuleSuppression,
|
|
12
|
+
FindingSuppression,
|
|
13
|
+
} from './types'
|
|
14
|
+
import {
|
|
15
|
+
SUPPRESSION_CONFIG_FILES,
|
|
16
|
+
DEFAULT_SUPPRESSION_CONFIG,
|
|
17
|
+
} from './types'
|
|
18
|
+
import type { VulnerabilityCategory } from '../types'
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Result of loading suppression config
|
|
22
|
+
*/
|
|
23
|
+
export interface ConfigLoadResult {
|
|
24
|
+
/** The loaded config (or default if none found) */
|
|
25
|
+
config: SuppressionConfig
|
|
26
|
+
/** Path to the config file (if found) */
|
|
27
|
+
configPath?: string
|
|
28
|
+
/** Whether a config file was found */
|
|
29
|
+
found: boolean
|
|
30
|
+
/** Any errors encountered during loading */
|
|
31
|
+
errors: string[]
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Find the config file by walking up the directory tree
|
|
36
|
+
*/
|
|
37
|
+
export function findConfigFile(startPath: string): string | null {
|
|
38
|
+
let currentDir = startPath
|
|
39
|
+
|
|
40
|
+
// Walk up directory tree looking for config file
|
|
41
|
+
while (currentDir !== dirname(currentDir)) {
|
|
42
|
+
for (const filename of SUPPRESSION_CONFIG_FILES) {
|
|
43
|
+
const configPath = join(currentDir, filename)
|
|
44
|
+
if (existsSync(configPath)) {
|
|
45
|
+
return configPath
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
currentDir = dirname(currentDir)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Check root directory one more time
|
|
52
|
+
for (const filename of SUPPRESSION_CONFIG_FILES) {
|
|
53
|
+
const configPath = join(currentDir, filename)
|
|
54
|
+
if (existsSync(configPath)) {
|
|
55
|
+
return configPath
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return null
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Parse config file content based on extension
|
|
64
|
+
*/
|
|
65
|
+
function parseConfigContent(content: string, filePath: string): unknown {
|
|
66
|
+
const isYaml = filePath.endsWith('.yaml') || filePath.endsWith('.yml')
|
|
67
|
+
|
|
68
|
+
if (isYaml) {
|
|
69
|
+
return yaml.load(content)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// JSON or .oculumrc (treat as JSON)
|
|
73
|
+
return JSON.parse(content)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Validate suppression config structure
|
|
78
|
+
*/
|
|
79
|
+
function validateConfig(data: unknown): { valid: boolean; errors: string[] } {
|
|
80
|
+
const errors: string[] = []
|
|
81
|
+
|
|
82
|
+
if (!data || typeof data !== 'object') {
|
|
83
|
+
return { valid: false, errors: ['Config must be an object'] }
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const config = data as Record<string, unknown>
|
|
87
|
+
|
|
88
|
+
// Validate version
|
|
89
|
+
if (config.version !== undefined && config.version !== 1) {
|
|
90
|
+
errors.push(`Unsupported config version: ${config.version}. Only version 1 is supported.`)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Validate suppressions
|
|
94
|
+
if (config.suppressions !== undefined) {
|
|
95
|
+
if (typeof config.suppressions !== 'object' || config.suppressions === null) {
|
|
96
|
+
errors.push('suppressions must be an object')
|
|
97
|
+
} else {
|
|
98
|
+
const suppressions = config.suppressions as Record<string, unknown>
|
|
99
|
+
|
|
100
|
+
// Validate rules
|
|
101
|
+
if (suppressions.rules !== undefined) {
|
|
102
|
+
if (!Array.isArray(suppressions.rules)) {
|
|
103
|
+
errors.push('suppressions.rules must be an array')
|
|
104
|
+
} else {
|
|
105
|
+
suppressions.rules.forEach((rule, i) => {
|
|
106
|
+
if (!rule || typeof rule !== 'object') {
|
|
107
|
+
errors.push(`suppressions.rules[${i}] must be an object`)
|
|
108
|
+
} else {
|
|
109
|
+
const r = rule as Record<string, unknown>
|
|
110
|
+
if (!r.category || typeof r.category !== 'string') {
|
|
111
|
+
errors.push(`suppressions.rules[${i}].category is required`)
|
|
112
|
+
}
|
|
113
|
+
if (!r.reason || typeof r.reason !== 'string') {
|
|
114
|
+
errors.push(`suppressions.rules[${i}].reason is required`)
|
|
115
|
+
}
|
|
116
|
+
if (r.expires && typeof r.expires !== 'string') {
|
|
117
|
+
errors.push(`suppressions.rules[${i}].expires must be a string (ISO date)`)
|
|
118
|
+
}
|
|
119
|
+
if (r.paths && !Array.isArray(r.paths)) {
|
|
120
|
+
errors.push(`suppressions.rules[${i}].paths must be an array`)
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
})
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Validate findings
|
|
128
|
+
if (suppressions.findings !== undefined) {
|
|
129
|
+
if (!Array.isArray(suppressions.findings)) {
|
|
130
|
+
errors.push('suppressions.findings must be an array')
|
|
131
|
+
} else {
|
|
132
|
+
suppressions.findings.forEach((finding, i) => {
|
|
133
|
+
if (!finding || typeof finding !== 'object') {
|
|
134
|
+
errors.push(`suppressions.findings[${i}] must be an object`)
|
|
135
|
+
} else {
|
|
136
|
+
const f = finding as Record<string, unknown>
|
|
137
|
+
if (!f.hash || typeof f.hash !== 'string') {
|
|
138
|
+
errors.push(`suppressions.findings[${i}].hash is required`)
|
|
139
|
+
}
|
|
140
|
+
if (!f.file || typeof f.file !== 'string') {
|
|
141
|
+
errors.push(`suppressions.findings[${i}].file is required`)
|
|
142
|
+
}
|
|
143
|
+
if (!f.reason || typeof f.reason !== 'string') {
|
|
144
|
+
errors.push(`suppressions.findings[${i}].reason is required`)
|
|
145
|
+
}
|
|
146
|
+
if (f.expires && typeof f.expires !== 'string') {
|
|
147
|
+
errors.push(`suppressions.findings[${i}].expires must be a string (ISO date)`)
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
})
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Validate ignore patterns
|
|
157
|
+
if (config.ignore !== undefined) {
|
|
158
|
+
if (!Array.isArray(config.ignore)) {
|
|
159
|
+
errors.push('ignore must be an array of strings')
|
|
160
|
+
} else {
|
|
161
|
+
config.ignore.forEach((pattern, i) => {
|
|
162
|
+
if (typeof pattern !== 'string') {
|
|
163
|
+
errors.push(`ignore[${i}] must be a string`)
|
|
164
|
+
}
|
|
165
|
+
})
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return { valid: errors.length === 0, errors }
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Load suppression config from a specific path
|
|
174
|
+
*/
|
|
175
|
+
export function loadConfigFromPath(configPath: string): ConfigLoadResult {
|
|
176
|
+
const errors: string[] = []
|
|
177
|
+
|
|
178
|
+
try {
|
|
179
|
+
const content = readFileSync(configPath, 'utf-8')
|
|
180
|
+
const data = parseConfigContent(content, configPath)
|
|
181
|
+
|
|
182
|
+
const validation = validateConfig(data)
|
|
183
|
+
if (!validation.valid) {
|
|
184
|
+
return {
|
|
185
|
+
config: DEFAULT_SUPPRESSION_CONFIG,
|
|
186
|
+
configPath,
|
|
187
|
+
found: true,
|
|
188
|
+
errors: validation.errors,
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Cast to config type (after validation)
|
|
193
|
+
const config = data as SuppressionConfig
|
|
194
|
+
|
|
195
|
+
// Ensure defaults
|
|
196
|
+
return {
|
|
197
|
+
config: {
|
|
198
|
+
version: config.version || 1,
|
|
199
|
+
suppressions: {
|
|
200
|
+
rules: config.suppressions?.rules || [],
|
|
201
|
+
findings: config.suppressions?.findings || [],
|
|
202
|
+
},
|
|
203
|
+
ignore: config.ignore || [],
|
|
204
|
+
},
|
|
205
|
+
configPath,
|
|
206
|
+
found: true,
|
|
207
|
+
errors,
|
|
208
|
+
}
|
|
209
|
+
} catch (err) {
|
|
210
|
+
const message = err instanceof Error ? err.message : String(err)
|
|
211
|
+
return {
|
|
212
|
+
config: DEFAULT_SUPPRESSION_CONFIG,
|
|
213
|
+
configPath,
|
|
214
|
+
found: true,
|
|
215
|
+
errors: [`Failed to parse config: ${message}`],
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Load suppression config from a project directory
|
|
222
|
+
* Searches up the directory tree for a config file
|
|
223
|
+
*/
|
|
224
|
+
export function loadSuppressionConfig(projectPath: string): ConfigLoadResult {
|
|
225
|
+
const configPath = findConfigFile(projectPath)
|
|
226
|
+
|
|
227
|
+
if (!configPath) {
|
|
228
|
+
return {
|
|
229
|
+
config: DEFAULT_SUPPRESSION_CONFIG,
|
|
230
|
+
found: false,
|
|
231
|
+
errors: [],
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
return loadConfigFromPath(configPath)
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Write suppression config to a file
|
|
240
|
+
*/
|
|
241
|
+
export function writeSuppressionConfig(
|
|
242
|
+
configPath: string,
|
|
243
|
+
config: SuppressionConfig
|
|
244
|
+
): void {
|
|
245
|
+
const isYaml = configPath.endsWith('.yaml') || configPath.endsWith('.yml')
|
|
246
|
+
|
|
247
|
+
let content: string
|
|
248
|
+
if (isYaml) {
|
|
249
|
+
content = yaml.dump(config, {
|
|
250
|
+
indent: 2,
|
|
251
|
+
lineWidth: 100,
|
|
252
|
+
noRefs: true,
|
|
253
|
+
sortKeys: false,
|
|
254
|
+
})
|
|
255
|
+
} else {
|
|
256
|
+
content = JSON.stringify(config, null, 2) + '\n'
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
writeFileSync(configPath, content, 'utf-8')
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Add a finding suppression to the config file
|
|
264
|
+
*/
|
|
265
|
+
export function addFindingSuppression(
|
|
266
|
+
projectPath: string,
|
|
267
|
+
suppression: FindingSuppression
|
|
268
|
+
): { success: boolean; configPath: string; error?: string } {
|
|
269
|
+
// Find or create config file
|
|
270
|
+
let configPath = findConfigFile(projectPath)
|
|
271
|
+
let config: SuppressionConfig
|
|
272
|
+
|
|
273
|
+
if (configPath) {
|
|
274
|
+
const result = loadConfigFromPath(configPath)
|
|
275
|
+
if (result.errors.length > 0) {
|
|
276
|
+
return {
|
|
277
|
+
success: false,
|
|
278
|
+
configPath: configPath,
|
|
279
|
+
error: result.errors.join(', '),
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
config = result.config
|
|
283
|
+
} else {
|
|
284
|
+
// Create new config file in project root
|
|
285
|
+
configPath = join(projectPath, '.oculum.yaml')
|
|
286
|
+
config = { ...DEFAULT_SUPPRESSION_CONFIG }
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// Initialize suppressions if needed
|
|
290
|
+
if (!config.suppressions) {
|
|
291
|
+
config.suppressions = {}
|
|
292
|
+
}
|
|
293
|
+
if (!config.suppressions.findings) {
|
|
294
|
+
config.suppressions.findings = []
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// Check for duplicate
|
|
298
|
+
const existingIndex = config.suppressions.findings.findIndex(
|
|
299
|
+
f => f.hash === suppression.hash
|
|
300
|
+
)
|
|
301
|
+
|
|
302
|
+
if (existingIndex >= 0) {
|
|
303
|
+
// Update existing suppression
|
|
304
|
+
config.suppressions.findings[existingIndex] = suppression
|
|
305
|
+
} else {
|
|
306
|
+
// Add new suppression
|
|
307
|
+
config.suppressions.findings.push(suppression)
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// Write config
|
|
311
|
+
try {
|
|
312
|
+
writeSuppressionConfig(configPath, config)
|
|
313
|
+
return { success: true, configPath }
|
|
314
|
+
} catch (err) {
|
|
315
|
+
const message = err instanceof Error ? err.message : String(err)
|
|
316
|
+
return { success: false, configPath, error: message }
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* Remove a finding suppression from the config file
|
|
322
|
+
*/
|
|
323
|
+
export function removeFindingSuppression(
|
|
324
|
+
projectPath: string,
|
|
325
|
+
hash: string
|
|
326
|
+
): { success: boolean; configPath?: string; error?: string; removed: boolean } {
|
|
327
|
+
const configPath = findConfigFile(projectPath)
|
|
328
|
+
|
|
329
|
+
if (!configPath) {
|
|
330
|
+
return { success: true, removed: false }
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
const result = loadConfigFromPath(configPath)
|
|
334
|
+
if (result.errors.length > 0) {
|
|
335
|
+
return {
|
|
336
|
+
success: false,
|
|
337
|
+
configPath,
|
|
338
|
+
error: result.errors.join(', '),
|
|
339
|
+
removed: false,
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
const config = result.config
|
|
344
|
+
|
|
345
|
+
if (!config.suppressions?.findings) {
|
|
346
|
+
return { success: true, configPath, removed: false }
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
const initialLength = config.suppressions.findings.length
|
|
350
|
+
config.suppressions.findings = config.suppressions.findings.filter(
|
|
351
|
+
f => f.hash !== hash
|
|
352
|
+
)
|
|
353
|
+
|
|
354
|
+
const removed = config.suppressions.findings.length < initialLength
|
|
355
|
+
|
|
356
|
+
if (removed) {
|
|
357
|
+
try {
|
|
358
|
+
writeSuppressionConfig(configPath, config)
|
|
359
|
+
} catch (err) {
|
|
360
|
+
const message = err instanceof Error ? err.message : String(err)
|
|
361
|
+
return { success: false, configPath, error: message, removed: false }
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
return { success: true, configPath, removed }
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* Add a rule suppression to the config file
|
|
370
|
+
*/
|
|
371
|
+
export function addRuleSuppression(
|
|
372
|
+
projectPath: string,
|
|
373
|
+
suppression: RuleSuppression
|
|
374
|
+
): { success: boolean; configPath: string; error?: string } {
|
|
375
|
+
// Find or create config file
|
|
376
|
+
let configPath = findConfigFile(projectPath)
|
|
377
|
+
let config: SuppressionConfig
|
|
378
|
+
|
|
379
|
+
if (configPath) {
|
|
380
|
+
const result = loadConfigFromPath(configPath)
|
|
381
|
+
if (result.errors.length > 0) {
|
|
382
|
+
return {
|
|
383
|
+
success: false,
|
|
384
|
+
configPath: configPath,
|
|
385
|
+
error: result.errors.join(', '),
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
config = result.config
|
|
389
|
+
} else {
|
|
390
|
+
// Create new config file in project root
|
|
391
|
+
configPath = join(projectPath, '.oculum.yaml')
|
|
392
|
+
config = { ...DEFAULT_SUPPRESSION_CONFIG }
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
// Initialize suppressions if needed
|
|
396
|
+
if (!config.suppressions) {
|
|
397
|
+
config.suppressions = {}
|
|
398
|
+
}
|
|
399
|
+
if (!config.suppressions.rules) {
|
|
400
|
+
config.suppressions.rules = []
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
// Check for duplicate (same category and paths)
|
|
404
|
+
const existingIndex = config.suppressions.rules.findIndex(
|
|
405
|
+
r => r.category === suppression.category &&
|
|
406
|
+
JSON.stringify(r.paths || []) === JSON.stringify(suppression.paths || [])
|
|
407
|
+
)
|
|
408
|
+
|
|
409
|
+
if (existingIndex >= 0) {
|
|
410
|
+
// Update existing suppression
|
|
411
|
+
config.suppressions.rules[existingIndex] = suppression
|
|
412
|
+
} else {
|
|
413
|
+
// Add new suppression
|
|
414
|
+
config.suppressions.rules.push(suppression)
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
// Write config
|
|
418
|
+
try {
|
|
419
|
+
writeSuppressionConfig(configPath, config)
|
|
420
|
+
return { success: true, configPath }
|
|
421
|
+
} catch (err) {
|
|
422
|
+
const message = err instanceof Error ? err.message : String(err)
|
|
423
|
+
return { success: false, configPath, error: message }
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
/**
|
|
428
|
+
* List all suppressions in the config
|
|
429
|
+
*/
|
|
430
|
+
export function listSuppressions(projectPath: string): {
|
|
431
|
+
rules: RuleSuppression[]
|
|
432
|
+
findings: FindingSuppression[]
|
|
433
|
+
configPath?: string
|
|
434
|
+
errors: string[]
|
|
435
|
+
} {
|
|
436
|
+
const result = loadSuppressionConfig(projectPath)
|
|
437
|
+
|
|
438
|
+
return {
|
|
439
|
+
rules: result.config.suppressions?.rules || [],
|
|
440
|
+
findings: result.config.suppressions?.findings || [],
|
|
441
|
+
configPath: result.configPath,
|
|
442
|
+
errors: result.errors,
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
/**
|
|
447
|
+
* Check if a date string is expired
|
|
448
|
+
*/
|
|
449
|
+
export function isExpired(dateString: string | undefined): boolean {
|
|
450
|
+
if (!dateString) {
|
|
451
|
+
return false
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
try {
|
|
455
|
+
const expirationDate = new Date(dateString)
|
|
456
|
+
const now = new Date()
|
|
457
|
+
return expirationDate < now
|
|
458
|
+
} catch {
|
|
459
|
+
// Invalid date format - treat as not expired
|
|
460
|
+
return false
|
|
461
|
+
}
|
|
462
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Finding Hash Computation
|
|
3
|
+
* Creates stable, reproducible hashes for identifying specific findings
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { createHash } from 'crypto'
|
|
7
|
+
import type { Vulnerability } from '../types'
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Normalize a file path for hashing
|
|
11
|
+
* - Removes leading ./ or /
|
|
12
|
+
* - Converts backslashes to forward slashes
|
|
13
|
+
* - Lowercases (for case-insensitive matching)
|
|
14
|
+
*/
|
|
15
|
+
export function normalizePathForHash(filePath: string): string {
|
|
16
|
+
return filePath
|
|
17
|
+
.replace(/^\.\//, '') // Remove leading ./
|
|
18
|
+
.replace(/^\//, '') // Remove leading /
|
|
19
|
+
.replace(/\\/g, '/') // Normalize backslashes
|
|
20
|
+
.toLowerCase()
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Normalize line content for hashing
|
|
25
|
+
* - Trims whitespace
|
|
26
|
+
* - Collapses multiple whitespace to single space
|
|
27
|
+
* - Removes common noise (line numbers, etc.)
|
|
28
|
+
*/
|
|
29
|
+
export function normalizeContentForHash(content: string): string {
|
|
30
|
+
return content
|
|
31
|
+
.trim()
|
|
32
|
+
.replace(/\s+/g, ' ') // Collapse whitespace
|
|
33
|
+
.replace(/^\d+:\s*/, '') // Remove line number prefix if present
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Compute a stable hash for a vulnerability finding
|
|
38
|
+
*
|
|
39
|
+
* Hash = SHA256(normalizedPath:normalizedContent:category).slice(0,16)
|
|
40
|
+
*
|
|
41
|
+
* This creates a 16-character hex hash that:
|
|
42
|
+
* - Stays stable across whitespace changes
|
|
43
|
+
* - Changes when the actual code changes
|
|
44
|
+
* - Changes when the finding category changes
|
|
45
|
+
* - Is unique enough for practical use
|
|
46
|
+
*
|
|
47
|
+
* @param finding - The vulnerability to hash
|
|
48
|
+
* @returns A 16-character hex hash string
|
|
49
|
+
*/
|
|
50
|
+
export function computeFindingHash(finding: Vulnerability): string {
|
|
51
|
+
const normalizedPath = normalizePathForHash(finding.filePath)
|
|
52
|
+
const normalizedContent = normalizeContentForHash(finding.lineContent)
|
|
53
|
+
const category = finding.category
|
|
54
|
+
|
|
55
|
+
const input = `${normalizedPath}:${normalizedContent}:${category}`
|
|
56
|
+
const hash = createHash('sha256').update(input).digest('hex')
|
|
57
|
+
|
|
58
|
+
// Return first 16 characters (64 bits of entropy - sufficient for uniqueness)
|
|
59
|
+
return hash.slice(0, 16)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Compute hash from raw components (for testing or manual construction)
|
|
64
|
+
*/
|
|
65
|
+
export function computeHashFromComponents(
|
|
66
|
+
filePath: string,
|
|
67
|
+
lineContent: string,
|
|
68
|
+
category: string
|
|
69
|
+
): string {
|
|
70
|
+
const normalizedPath = normalizePathForHash(filePath)
|
|
71
|
+
const normalizedContent = normalizeContentForHash(lineContent)
|
|
72
|
+
|
|
73
|
+
const input = `${normalizedPath}:${normalizedContent}:${category}`
|
|
74
|
+
const hash = createHash('sha256').update(input).digest('hex')
|
|
75
|
+
|
|
76
|
+
return hash.slice(0, 16)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Validate a hash format
|
|
81
|
+
* Must be 16 hexadecimal characters
|
|
82
|
+
*/
|
|
83
|
+
export function isValidHash(hash: string): boolean {
|
|
84
|
+
return /^[0-9a-f]{16}$/i.test(hash)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Format a hash for display (with truncation indicator)
|
|
89
|
+
*/
|
|
90
|
+
export function formatHashForDisplay(hash: string): string {
|
|
91
|
+
if (hash.length <= 16) {
|
|
92
|
+
return hash
|
|
93
|
+
}
|
|
94
|
+
return `${hash.slice(0, 12)}...`
|
|
95
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Suppression System
|
|
3
|
+
* Allows users to suppress findings via config files and inline comments
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// Main exports
|
|
7
|
+
export { SuppressionManager, type SuppressionManagerOptions } from './manager'
|
|
8
|
+
|
|
9
|
+
// Types
|
|
10
|
+
export type {
|
|
11
|
+
SuppressionConfig,
|
|
12
|
+
RuleSuppression,
|
|
13
|
+
FindingSuppression,
|
|
14
|
+
InlineSuppression,
|
|
15
|
+
SuppressionMatch,
|
|
16
|
+
SuppressedVulnerability,
|
|
17
|
+
SuppressionResult,
|
|
18
|
+
} from './types'
|
|
19
|
+
export { SUPPRESSION_CONFIG_FILES, DEFAULT_SUPPRESSION_CONFIG } from './types'
|
|
20
|
+
|
|
21
|
+
// Hash utilities
|
|
22
|
+
export {
|
|
23
|
+
computeFindingHash,
|
|
24
|
+
computeHashFromComponents,
|
|
25
|
+
normalizePathForHash,
|
|
26
|
+
normalizeContentForHash,
|
|
27
|
+
isValidHash,
|
|
28
|
+
formatHashForDisplay,
|
|
29
|
+
} from './hash'
|
|
30
|
+
|
|
31
|
+
// Config utilities
|
|
32
|
+
export {
|
|
33
|
+
loadSuppressionConfig,
|
|
34
|
+
loadConfigFromPath,
|
|
35
|
+
findConfigFile,
|
|
36
|
+
writeSuppressionConfig,
|
|
37
|
+
addFindingSuppression,
|
|
38
|
+
removeFindingSuppression,
|
|
39
|
+
addRuleSuppression,
|
|
40
|
+
listSuppressions,
|
|
41
|
+
isExpired,
|
|
42
|
+
type ConfigLoadResult,
|
|
43
|
+
} from './config-loader'
|
|
44
|
+
|
|
45
|
+
// Inline comment utilities
|
|
46
|
+
export {
|
|
47
|
+
parseInlineSuppressions,
|
|
48
|
+
isLineSuppressed,
|
|
49
|
+
extractSuppressionReasons,
|
|
50
|
+
generateSuppressionComment,
|
|
51
|
+
} from './inline-parser'
|