@oculum/scanner 1.0.10 → 1.0.12
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/ai-context/index.d.ts +6 -0
- package/dist/ai-context/index.d.ts.map +1 -0
- package/dist/ai-context/index.js +13 -0
- package/dist/ai-context/index.js.map +1 -0
- package/dist/ai-context/manager.d.ts +67 -0
- package/dist/ai-context/manager.d.ts.map +1 -0
- package/dist/ai-context/manager.js +104 -0
- package/dist/ai-context/manager.js.map +1 -0
- 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/category-filter.d.ts +125 -0
- package/dist/category-filter.d.ts.map +1 -0
- package/dist/category-filter.js +360 -0
- package/dist/category-filter.js.map +1 -0
- package/dist/filtering/context-adjustments.d.ts +23 -0
- package/dist/filtering/context-adjustments.d.ts.map +1 -0
- package/dist/filtering/context-adjustments.js +100 -0
- package/dist/filtering/context-adjustments.js.map +1 -0
- package/dist/filtering/index.d.ts +3 -0
- package/dist/filtering/index.d.ts.map +1 -0
- package/dist/filtering/index.js +8 -0
- package/dist/filtering/index.js.map +1 -0
- package/dist/filtering/pipeline.d.ts +48 -0
- package/dist/filtering/pipeline.d.ts.map +1 -0
- package/dist/filtering/pipeline.js +76 -0
- package/dist/filtering/pipeline.js.map +1 -0
- package/dist/formatters/ai-context.d.ts +23 -0
- package/dist/formatters/ai-context.d.ts.map +1 -0
- package/dist/formatters/ai-context.js +238 -0
- package/dist/formatters/ai-context.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 +2 -2
- package/dist/formatters/github-comment.d.ts.map +1 -1
- package/dist/formatters/github-comment.js +77 -13
- package/dist/formatters/github-comment.js.map +1 -1
- package/dist/formatters/ide/claude-code.d.ts +17 -0
- package/dist/formatters/ide/claude-code.d.ts.map +1 -0
- package/dist/formatters/ide/claude-code.js +94 -0
- package/dist/formatters/ide/claude-code.js.map +1 -0
- package/dist/formatters/ide/cursor.d.ts +13 -0
- package/dist/formatters/ide/cursor.d.ts.map +1 -0
- package/dist/formatters/ide/cursor.js +125 -0
- package/dist/formatters/ide/cursor.js.map +1 -0
- package/dist/formatters/ide/index.d.ts +62 -0
- package/dist/formatters/ide/index.d.ts.map +1 -0
- package/dist/formatters/ide/index.js +184 -0
- package/dist/formatters/ide/index.js.map +1 -0
- package/dist/formatters/ide/windsurf.d.ts +13 -0
- package/dist/formatters/ide/windsurf.d.ts.map +1 -0
- package/dist/formatters/ide/windsurf.js +117 -0
- package/dist/formatters/ide/windsurf.js.map +1 -0
- package/dist/formatters/index.d.ts +3 -1
- package/dist/formatters/index.d.ts.map +1 -1
- package/dist/formatters/index.js +20 -1
- package/dist/formatters/index.js.map +1 -1
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +423 -56
- package/dist/index.js.map +1 -1
- package/dist/layer1/comments.d.ts +4 -1
- package/dist/layer1/comments.d.ts.map +1 -1
- package/dist/layer1/comments.js +1 -1
- package/dist/layer1/comments.js.map +1 -1
- package/dist/layer1/config-audit.d.ts +4 -1
- package/dist/layer1/config-audit.d.ts.map +1 -1
- package/dist/layer1/config-audit.js +65 -14
- package/dist/layer1/config-audit.js.map +1 -1
- package/dist/layer1/config-mcp-audit.d.ts +23 -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/entropy.d.ts +4 -1
- package/dist/layer1/entropy.d.ts.map +1 -1
- package/dist/layer1/entropy.js +212 -1
- package/dist/layer1/entropy.js.map +1 -1
- package/dist/layer1/file-flags.d.ts +4 -1
- package/dist/layer1/file-flags.d.ts.map +1 -1
- package/dist/layer1/file-flags.js +12 -5
- package/dist/layer1/file-flags.js.map +1 -1
- package/dist/layer1/index.d.ts +1 -0
- package/dist/layer1/index.d.ts.map +1 -1
- package/dist/layer1/index.js +22 -19
- package/dist/layer1/index.js.map +1 -1
- package/dist/layer1/patterns.d.ts +4 -1
- package/dist/layer1/patterns.d.ts.map +1 -1
- package/dist/layer1/patterns.js +34 -4
- package/dist/layer1/patterns.js.map +1 -1
- package/dist/layer1/urls.d.ts +4 -1
- package/dist/layer1/urls.d.ts.map +1 -1
- package/dist/layer1/urls.js +162 -14
- package/dist/layer1/urls.js.map +1 -1
- package/dist/layer1/weak-crypto.d.ts +4 -1
- package/dist/layer1/weak-crypto.d.ts.map +1 -1
- package/dist/layer1/weak-crypto.js +144 -7
- package/dist/layer1/weak-crypto.js.map +1 -1
- package/dist/layer2/ai-agent-tools.d.ts +4 -1
- package/dist/layer2/ai-agent-tools.d.ts.map +1 -1
- package/dist/layer2/ai-agent-tools.js +964 -2
- package/dist/layer2/ai-agent-tools.js.map +1 -1
- package/dist/layer2/ai-endpoint-protection.d.ts +2 -0
- package/dist/layer2/ai-endpoint-protection.d.ts.map +1 -1
- package/dist/layer2/ai-endpoint-protection.js +18 -4
- package/dist/layer2/ai-endpoint-protection.js.map +1 -1
- package/dist/layer2/ai-execution-sinks.d.ts +4 -1
- package/dist/layer2/ai-execution-sinks.d.ts.map +1 -1
- package/dist/layer2/ai-execution-sinks.js +688 -29
- package/dist/layer2/ai-execution-sinks.js.map +1 -1
- package/dist/layer2/ai-fingerprinting.d.ts +4 -1
- package/dist/layer2/ai-fingerprinting.d.ts.map +1 -1
- package/dist/layer2/ai-fingerprinting.js +28 -32
- package/dist/layer2/ai-fingerprinting.js.map +1 -1
- package/dist/layer2/ai-mcp-security.d.ts +20 -0
- package/dist/layer2/ai-mcp-security.d.ts.map +1 -0
- package/dist/layer2/ai-mcp-security.js +877 -0
- package/dist/layer2/ai-mcp-security.js.map +1 -0
- package/dist/layer2/ai-package-hallucination.d.ts +22 -0
- package/dist/layer2/ai-package-hallucination.d.ts.map +1 -0
- package/dist/layer2/ai-package-hallucination.js +828 -0
- package/dist/layer2/ai-package-hallucination.js.map +1 -0
- package/dist/layer2/ai-prompt-hygiene.d.ts +4 -1
- package/dist/layer2/ai-prompt-hygiene.d.ts.map +1 -1
- package/dist/layer2/ai-prompt-hygiene.js +817 -17
- package/dist/layer2/ai-prompt-hygiene.js.map +1 -1
- package/dist/layer2/ai-rag-safety.d.ts +4 -1
- package/dist/layer2/ai-rag-safety.d.ts.map +1 -1
- package/dist/layer2/ai-rag-safety.js +454 -3
- package/dist/layer2/ai-rag-safety.js.map +1 -1
- package/dist/layer2/ai-schema-validation.d.ts +4 -1
- package/dist/layer2/ai-schema-validation.d.ts.map +1 -1
- package/dist/layer2/ai-schema-validation.js +2 -2
- package/dist/layer2/ai-schema-validation.js.map +1 -1
- package/dist/layer2/auth-antipatterns.d.ts +2 -0
- package/dist/layer2/auth-antipatterns.d.ts.map +1 -1
- package/dist/layer2/auth-antipatterns.js +209 -20
- package/dist/layer2/auth-antipatterns.js.map +1 -1
- package/dist/layer2/byok-patterns.d.ts +4 -1
- package/dist/layer2/byok-patterns.d.ts.map +1 -1
- package/dist/layer2/byok-patterns.js +5 -2
- 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 +34 -0
- package/dist/layer2/dangerous-functions/dom-xss.d.ts.map +1 -0
- package/dist/layer2/dangerous-functions/dom-xss.js +230 -0
- package/dist/layer2/dangerous-functions/dom-xss.js.map +1 -0
- package/dist/layer2/dangerous-functions/index.d.ts +16 -0
- package/dist/layer2/dangerous-functions/index.d.ts.map +1 -0
- package/dist/layer2/dangerous-functions/index.js +1152 -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 +111 -0
- package/dist/layer2/dangerous-functions/math-random.d.ts.map +1 -0
- package/dist/layer2/dangerous-functions/math-random.js +684 -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 +163 -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 +24 -0
- package/dist/layer2/dangerous-functions/utils/control-flow.d.ts.map +1 -0
- package/dist/layer2/dangerous-functions/utils/control-flow.js +70 -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 +147 -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 +102 -0
- package/dist/layer2/dangerous-functions/utils/schema-validation.js.map +1 -0
- package/dist/layer2/data-exposure.d.ts +4 -1
- package/dist/layer2/data-exposure.d.ts.map +1 -1
- package/dist/layer2/data-exposure.js +14 -38
- package/dist/layer2/data-exposure.js.map +1 -1
- package/dist/layer2/framework-checks.d.ts +4 -1
- package/dist/layer2/framework-checks.d.ts.map +1 -1
- package/dist/layer2/framework-checks.js +5 -2
- package/dist/layer2/framework-checks.js.map +1 -1
- package/dist/layer2/index.d.ts +12 -1
- package/dist/layer2/index.d.ts.map +1 -1
- package/dist/layer2/index.js +110 -45
- package/dist/layer2/index.js.map +1 -1
- package/dist/layer2/logic-gates.d.ts +4 -1
- package/dist/layer2/logic-gates.d.ts.map +1 -1
- package/dist/layer2/logic-gates.js +58 -20
- package/dist/layer2/logic-gates.js.map +1 -1
- package/dist/layer2/model-supply-chain.d.ts +23 -0
- package/dist/layer2/model-supply-chain.d.ts.map +1 -0
- package/dist/layer2/model-supply-chain.js +444 -0
- package/dist/layer2/model-supply-chain.js.map +1 -0
- package/dist/layer2/risky-imports.d.ts +4 -1
- package/dist/layer2/risky-imports.d.ts.map +1 -1
- package/dist/layer2/risky-imports.js +6 -2
- package/dist/layer2/risky-imports.js.map +1 -1
- package/dist/layer2/variables.d.ts +4 -1
- package/dist/layer2/variables.d.ts.map +1 -1
- package/dist/layer2/variables.js +6 -2
- 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 +199 -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/modes/incremental.js +1 -1
- 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 +3 -3
- package/dist/tiers.d.ts.map +1 -1
- package/dist/tiers.js +34 -7
- package/dist/tiers.js.map +1 -1
- package/dist/types.d.ts +140 -9
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +34 -0
- package/dist/types.js.map +1 -1
- package/dist/utils/code-analysis.d.ts +39 -0
- package/dist/utils/code-analysis.d.ts.map +1 -0
- package/dist/utils/code-analysis.js +159 -0
- package/dist/utils/code-analysis.js.map +1 -0
- package/dist/utils/comment-analyzer.d.ts +38 -0
- package/dist/utils/comment-analyzer.d.ts.map +1 -0
- package/dist/utils/comment-analyzer.js +218 -0
- package/dist/utils/comment-analyzer.js.map +1 -0
- package/dist/utils/context-helpers.d.ts +112 -1
- package/dist/utils/context-helpers.d.ts.map +1 -1
- package/dist/utils/context-helpers.js +364 -11
- package/dist/utils/context-helpers.js.map +1 -1
- package/dist/utils/environment-context.d.ts +76 -0
- package/dist/utils/environment-context.d.ts.map +1 -0
- package/dist/utils/environment-context.js +271 -0
- package/dist/utils/environment-context.js.map +1 -0
- package/dist/utils/intent-detector.d.ts +66 -0
- package/dist/utils/intent-detector.d.ts.map +1 -0
- package/dist/utils/intent-detector.js +282 -0
- package/dist/utils/intent-detector.js.map +1 -0
- package/dist/utils/parsed-file.d.ts +51 -0
- package/dist/utils/parsed-file.d.ts.map +1 -0
- package/dist/utils/parsed-file.js +95 -0
- package/dist/utils/parsed-file.js.map +1 -0
- package/dist/utils/route-hierarchy.d.ts +50 -0
- package/dist/utils/route-hierarchy.d.ts.map +1 -0
- package/dist/utils/route-hierarchy.js +226 -0
- package/dist/utils/route-hierarchy.js.map +1 -0
- package/dist/utils/schema-semantics.d.ts +45 -0
- package/dist/utils/schema-semantics.d.ts.map +1 -0
- package/dist/utils/schema-semantics.js +193 -0
- package/dist/utils/schema-semantics.js.map +1 -0
- 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 +30 -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__/benchmark/fixtures/layer2/phase5-excessive-agency.ts +580 -0
- package/src/__tests__/benchmark/fixtures/layer2/sprint6-ai-enhancements.ts +515 -0
- package/src/__tests__/benchmark/run-depth-validation.ts +9 -9
- package/src/__tests__/category-filter.test.ts +478 -0
- package/src/__tests__/regression/known-false-positives.test.ts +490 -0
- package/src/__tests__/snapshots/__snapshots__/anthropic-validation-refactor.test.ts.snap +762 -0
- package/src/__tests__/snapshots/__snapshots__/dangerous-functions-refactor.test.ts.snap +503 -0
- package/src/__tests__/snapshots/__snapshots__/scan-depth.test.ts.snap +0 -9
- package/src/__tests__/snapshots/anthropic-validation-refactor.test.ts +321 -0
- package/src/__tests__/snapshots/dangerous-functions-refactor.test.ts +439 -0
- package/src/__tests__/validation/run-validation.ts +7 -7
- package/src/ai-context/__tests__/manager.test.ts +193 -0
- package/src/ai-context/index.ts +15 -0
- package/src/ai-context/manager.ts +145 -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/category-filter.ts +400 -0
- package/src/filtering/__tests__/pipeline.test.ts +134 -0
- package/src/filtering/context-adjustments.ts +111 -0
- package/src/filtering/index.ts +10 -0
- package/src/filtering/pipeline.ts +130 -0
- package/src/formatters/__tests__/ai-context.test.ts +254 -0
- package/src/formatters/ai-context.ts +302 -0
- package/src/formatters/cli-terminal.ts +444 -41
- package/src/formatters/github-comment.ts +82 -14
- package/src/formatters/ide/__tests__/ide.test.ts +319 -0
- package/src/formatters/ide/claude-code.ts +110 -0
- package/src/formatters/ide/cursor.ts +147 -0
- package/src/formatters/ide/index.ts +216 -0
- package/src/formatters/ide/windsurf.ts +135 -0
- package/src/formatters/index.ts +28 -0
- package/src/index.ts +506 -45
- package/src/layer1/comments.ts +3 -1
- package/src/layer1/config-audit.ts +74 -14
- package/src/layer1/config-mcp-audit.ts +278 -0
- package/src/layer1/entropy.ts +234 -1
- package/src/layer1/file-flags.ts +17 -6
- package/src/layer1/index.ts +29 -23
- package/src/layer1/patterns.ts +42 -4
- package/src/layer1/urls.ts +188 -14
- package/src/layer1/weak-crypto.ts +168 -16
- package/src/layer2/ai-agent-tools.ts +1043 -2
- package/src/layer2/ai-endpoint-protection.ts +19 -4
- package/src/layer2/ai-execution-sinks.ts +755 -29
- package/src/layer2/ai-fingerprinting.ts +33 -33
- package/src/layer2/ai-mcp-security.ts +933 -0
- package/src/layer2/ai-package-hallucination.ts +940 -0
- package/src/layer2/ai-prompt-hygiene.ts +898 -17
- package/src/layer2/ai-rag-safety.ts +467 -5
- package/src/layer2/ai-schema-validation.ts +4 -2
- package/src/layer2/auth-antipatterns.ts +235 -20
- package/src/layer2/byok-patterns.ts +9 -3
- package/src/layer2/dangerous-functions/child-process.ts +98 -0
- package/src/layer2/dangerous-functions/dom-xss.ts +292 -0
- package/src/layer2/dangerous-functions/index.ts +1533 -0
- package/src/layer2/dangerous-functions/json-parse.ts +385 -0
- package/src/layer2/dangerous-functions/math-random.ts +789 -0
- package/src/layer2/dangerous-functions/patterns.ts +176 -0
- package/src/layer2/dangerous-functions/request-validation.ts +145 -0
- package/src/layer2/dangerous-functions/utils/control-flow.ts +35 -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 +106 -0
- package/src/layer2/data-exposure.ts +18 -39
- package/src/layer2/framework-checks.ts +9 -2
- package/src/layer2/index.ts +124 -43
- package/src/layer2/logic-gates.ts +64 -22
- package/src/layer2/model-supply-chain.ts +531 -0
- package/src/layer2/risky-imports.ts +9 -2
- package/src/layer2/variables.ts +9 -2
- package/src/layer3/__tests__/osv-check.test.ts +384 -0
- package/src/layer3/anthropic/auto-dismiss.ts +223 -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/modes/incremental.ts +1 -1
- 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 +45 -9
- package/src/types.ts +212 -8
- package/src/utils/__tests__/code-analysis.test.ts +165 -0
- package/src/utils/__tests__/parsed-file.test.ts +124 -0
- package/src/utils/code-analysis.ts +179 -0
- package/src/utils/comment-analyzer.ts +249 -0
- package/src/utils/context-helpers.ts +421 -11
- package/src/utils/environment-context.ts +304 -0
- package/src/utils/intent-detector.ts +318 -0
- package/src/utils/parsed-file.ts +103 -0
- package/src/utils/route-hierarchy.ts +250 -0
- package/src/utils/schema-semantics.ts +233 -0
- 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,400 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Category-Based Filtering
|
|
3
|
+
*
|
|
4
|
+
* Enables CI to fail only on specific vulnerability categories,
|
|
5
|
+
* allowing gradual rollout (e.g., "only block prompt injection")
|
|
6
|
+
* and fine-grained control over which findings are blocking.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* // Fail only on AI-related and secret categories
|
|
10
|
+
* --fail-on-categories ai-*,secrets-*
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* // Combined with severity
|
|
14
|
+
* --fail-on high --fail-on-categories ai-*
|
|
15
|
+
* // Only fail on high+ AI findings
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import type { VulnerabilityCategory, Vulnerability, VulnerabilitySeverity } from './types'
|
|
19
|
+
import { severityRank } from './utils/parsed-file'
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Category group definitions for wildcard expansion
|
|
23
|
+
*
|
|
24
|
+
* These groups allow users to specify broad categories like "ai-*"
|
|
25
|
+
* which expand to all AI-related vulnerability categories.
|
|
26
|
+
*/
|
|
27
|
+
export const CATEGORY_GROUPS: Record<string, VulnerabilityCategory[]> = {
|
|
28
|
+
'ai-*': [
|
|
29
|
+
'ai_pattern',
|
|
30
|
+
'ai_prompt_injection',
|
|
31
|
+
'ai_unsafe_execution',
|
|
32
|
+
'ai_overpermissive_tool',
|
|
33
|
+
'ai_rag_exfiltration',
|
|
34
|
+
'ai_endpoint_unprotected',
|
|
35
|
+
'ai_schema_mismatch',
|
|
36
|
+
'ai_package_hallucination',
|
|
37
|
+
'ai_rag_corpus_poisoning',
|
|
38
|
+
'ai_rag_pii_leakage',
|
|
39
|
+
'ai_mcp_tool_poisoning',
|
|
40
|
+
'ai_mcp_credential_issue',
|
|
41
|
+
'ai_mcp_confused_deputy',
|
|
42
|
+
'ai_mcp_description_injection',
|
|
43
|
+
'ai_mcp_server_shadowing',
|
|
44
|
+
'ai_mcp_config_secrets',
|
|
45
|
+
'ai_mcp_config_permissions',
|
|
46
|
+
'ai_rag_query_injection',
|
|
47
|
+
'ai_rag_embedding_poisoning',
|
|
48
|
+
'ai_rag_chunk_injection',
|
|
49
|
+
'ai_package_typosquat',
|
|
50
|
+
'ai_package_malicious',
|
|
51
|
+
'ai_unsafe_model_load',
|
|
52
|
+
'ai_unverified_model',
|
|
53
|
+
'ai_unsafe_finetuning',
|
|
54
|
+
'ai_excessive_agency',
|
|
55
|
+
],
|
|
56
|
+
'secrets-*': [
|
|
57
|
+
'hardcoded_secret',
|
|
58
|
+
'high_entropy_string',
|
|
59
|
+
'sensitive_variable',
|
|
60
|
+
],
|
|
61
|
+
'owasp-*': [
|
|
62
|
+
'sql_injection',
|
|
63
|
+
'xss',
|
|
64
|
+
'command_injection',
|
|
65
|
+
'missing_auth',
|
|
66
|
+
'security_bypass',
|
|
67
|
+
'insecure_config',
|
|
68
|
+
'cors_misconfiguration',
|
|
69
|
+
'data_exposure',
|
|
70
|
+
'weak_crypto',
|
|
71
|
+
],
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* All known valid category names for validation
|
|
76
|
+
*/
|
|
77
|
+
export const ALL_CATEGORIES: VulnerabilityCategory[] = [
|
|
78
|
+
'hardcoded_secret',
|
|
79
|
+
'high_entropy_string',
|
|
80
|
+
'sensitive_variable',
|
|
81
|
+
'security_bypass',
|
|
82
|
+
'dangerous_function',
|
|
83
|
+
'sql_injection',
|
|
84
|
+
'xss',
|
|
85
|
+
'command_injection',
|
|
86
|
+
'insecure_config',
|
|
87
|
+
'missing_auth',
|
|
88
|
+
'suspicious_package',
|
|
89
|
+
'cors_misconfiguration',
|
|
90
|
+
'root_container',
|
|
91
|
+
'dangerous_file',
|
|
92
|
+
'ai_pattern',
|
|
93
|
+
'sensitive_url',
|
|
94
|
+
'weak_crypto',
|
|
95
|
+
'data_exposure',
|
|
96
|
+
'ai_prompt_injection',
|
|
97
|
+
'ai_unsafe_execution',
|
|
98
|
+
'ai_overpermissive_tool',
|
|
99
|
+
'ai_rag_exfiltration',
|
|
100
|
+
'ai_endpoint_unprotected',
|
|
101
|
+
'ai_schema_mismatch',
|
|
102
|
+
'ai_package_hallucination',
|
|
103
|
+
'ai_rag_corpus_poisoning',
|
|
104
|
+
'ai_rag_pii_leakage',
|
|
105
|
+
'ai_mcp_tool_poisoning',
|
|
106
|
+
'ai_mcp_credential_issue',
|
|
107
|
+
'ai_mcp_confused_deputy',
|
|
108
|
+
'ai_mcp_description_injection',
|
|
109
|
+
'ai_mcp_server_shadowing',
|
|
110
|
+
'ai_mcp_config_secrets',
|
|
111
|
+
'ai_mcp_config_permissions',
|
|
112
|
+
'ai_rag_query_injection',
|
|
113
|
+
'ai_rag_embedding_poisoning',
|
|
114
|
+
'ai_rag_chunk_injection',
|
|
115
|
+
'ai_package_typosquat',
|
|
116
|
+
'ai_package_malicious',
|
|
117
|
+
'ai_unsafe_model_load',
|
|
118
|
+
'ai_unverified_model',
|
|
119
|
+
'ai_unsafe_finetuning',
|
|
120
|
+
'ai_excessive_agency',
|
|
121
|
+
]
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Normalize category name for comparison
|
|
125
|
+
* - Converts to lowercase
|
|
126
|
+
* - Converts hyphens to underscores
|
|
127
|
+
* - Trims whitespace
|
|
128
|
+
*
|
|
129
|
+
* @example
|
|
130
|
+
* normalizeCategory('SQL-Injection') // 'sql_injection'
|
|
131
|
+
* normalizeCategory('high_entropy_string') // 'high_entropy_string'
|
|
132
|
+
*/
|
|
133
|
+
export function normalizeCategory(category: string): string {
|
|
134
|
+
return category
|
|
135
|
+
.toLowerCase()
|
|
136
|
+
.trim()
|
|
137
|
+
.replace(/-/g, '_')
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Check if a string is a valid wildcard pattern
|
|
142
|
+
* Valid wildcards end with '-*' or '_*'
|
|
143
|
+
*/
|
|
144
|
+
function isWildcardPattern(pattern: string): boolean {
|
|
145
|
+
return pattern.endsWith('-*') || pattern.endsWith('_*')
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Expand a wildcard pattern or single category to a list of categories
|
|
150
|
+
*
|
|
151
|
+
* @param pattern - Category name or wildcard (e.g., 'sql_injection', 'ai-*')
|
|
152
|
+
* @returns Array of matching categories
|
|
153
|
+
*
|
|
154
|
+
* @example
|
|
155
|
+
* expandCategoryPattern('ai-*') // Returns all ai_* categories
|
|
156
|
+
* expandCategoryPattern('sql_injection') // Returns ['sql_injection']
|
|
157
|
+
* expandCategoryPattern('unknown-*') // Returns []
|
|
158
|
+
*/
|
|
159
|
+
export function expandCategoryPattern(pattern: string): VulnerabilityCategory[] {
|
|
160
|
+
const normalized = normalizeCategory(pattern)
|
|
161
|
+
|
|
162
|
+
// Check for wildcard patterns
|
|
163
|
+
if (isWildcardPattern(normalized)) {
|
|
164
|
+
// Normalize the wildcard pattern (both ai-* and ai_* should work)
|
|
165
|
+
const normalizedWildcard = normalized.replace('_*', '-*')
|
|
166
|
+
|
|
167
|
+
// Look up in category groups
|
|
168
|
+
const expanded = CATEGORY_GROUPS[normalizedWildcard]
|
|
169
|
+
if (expanded) {
|
|
170
|
+
return [...expanded]
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Unknown wildcard - return empty
|
|
174
|
+
return []
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Single category - validate and return
|
|
178
|
+
// Handle both hyphenated and underscored versions
|
|
179
|
+
const normalizedCategory = normalized as VulnerabilityCategory
|
|
180
|
+
if (ALL_CATEGORIES.includes(normalizedCategory)) {
|
|
181
|
+
return [normalizedCategory]
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Try hyphenated version converted to underscore
|
|
185
|
+
const underscored = normalized.replace(/-/g, '_') as VulnerabilityCategory
|
|
186
|
+
if (ALL_CATEGORIES.includes(underscored)) {
|
|
187
|
+
return [underscored]
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
return []
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Check if a category matches any pattern in the filter list
|
|
195
|
+
*
|
|
196
|
+
* @param category - The vulnerability category to check
|
|
197
|
+
* @param patterns - Array of category patterns (names or wildcards)
|
|
198
|
+
* @returns true if the category matches any pattern
|
|
199
|
+
*
|
|
200
|
+
* @example
|
|
201
|
+
* matchesAnyCategory('ai_prompt_injection', ['ai-*']) // true
|
|
202
|
+
* matchesAnyCategory('sql_injection', ['ai-*']) // false
|
|
203
|
+
* matchesAnyCategory('sql_injection', ['sql_injection', 'xss']) // true
|
|
204
|
+
*/
|
|
205
|
+
export function matchesAnyCategory(
|
|
206
|
+
category: VulnerabilityCategory,
|
|
207
|
+
patterns: string[]
|
|
208
|
+
): boolean {
|
|
209
|
+
if (!patterns || patterns.length === 0) {
|
|
210
|
+
return false
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const normalizedCategory = normalizeCategory(category)
|
|
214
|
+
|
|
215
|
+
for (const pattern of patterns) {
|
|
216
|
+
const expanded = expandCategoryPattern(pattern)
|
|
217
|
+
|
|
218
|
+
// Check if category is in the expanded list
|
|
219
|
+
if (expanded.some(c => normalizeCategory(c) === normalizedCategory)) {
|
|
220
|
+
return true
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
return false
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// severityRank imported from utils/parsed-file
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Check if vulnerabilities should cause failure based on category filter
|
|
231
|
+
*
|
|
232
|
+
* When both category patterns and severity threshold are provided,
|
|
233
|
+
* BOTH conditions must match for a finding to cause failure.
|
|
234
|
+
*
|
|
235
|
+
* @param vulnerabilities - List of vulnerabilities to check
|
|
236
|
+
* @param categoryPatterns - Category patterns to filter on
|
|
237
|
+
* @param severityThreshold - Optional severity threshold (both must match)
|
|
238
|
+
* @returns true if any vulnerability matches and should cause failure
|
|
239
|
+
*
|
|
240
|
+
* @example
|
|
241
|
+
* // Only fail on AI findings
|
|
242
|
+
* shouldFailOnCategories(vulns, ['ai-*'])
|
|
243
|
+
*
|
|
244
|
+
* @example
|
|
245
|
+
* // Only fail on HIGH+ AI findings
|
|
246
|
+
* shouldFailOnCategories(vulns, ['ai-*'], 'high')
|
|
247
|
+
*/
|
|
248
|
+
export function shouldFailOnCategories(
|
|
249
|
+
vulnerabilities: Vulnerability[],
|
|
250
|
+
categoryPatterns: string[],
|
|
251
|
+
severityThreshold?: VulnerabilitySeverity
|
|
252
|
+
): boolean {
|
|
253
|
+
if (!vulnerabilities || vulnerabilities.length === 0) {
|
|
254
|
+
return false
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
if (!categoryPatterns || categoryPatterns.length === 0) {
|
|
258
|
+
return false
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
const thresholdRank = severityThreshold ? severityRank(severityThreshold) : 0
|
|
262
|
+
|
|
263
|
+
for (const vuln of vulnerabilities) {
|
|
264
|
+
// Check if category matches any pattern
|
|
265
|
+
if (!matchesAnyCategory(vuln.category, categoryPatterns)) {
|
|
266
|
+
continue
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// If no severity threshold specified, any matching category triggers failure
|
|
270
|
+
if (!severityThreshold) {
|
|
271
|
+
return true
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// Check if severity meets threshold
|
|
275
|
+
if (severityRank(vuln.severity) >= thresholdRank) {
|
|
276
|
+
return true
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
return false
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Get the categories that matched the filter from vulnerabilities
|
|
285
|
+
* Useful for error messages showing which categories caused failure
|
|
286
|
+
*/
|
|
287
|
+
export function getMatchingCategories(
|
|
288
|
+
vulnerabilities: Vulnerability[],
|
|
289
|
+
categoryPatterns: string[],
|
|
290
|
+
severityThreshold?: VulnerabilitySeverity
|
|
291
|
+
): VulnerabilityCategory[] {
|
|
292
|
+
if (!vulnerabilities || vulnerabilities.length === 0) {
|
|
293
|
+
return []
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
if (!categoryPatterns || categoryPatterns.length === 0) {
|
|
297
|
+
return []
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
const thresholdRank = severityThreshold ? severityRank(severityThreshold) : 0
|
|
301
|
+
const matched = new Set<VulnerabilityCategory>()
|
|
302
|
+
|
|
303
|
+
for (const vuln of vulnerabilities) {
|
|
304
|
+
// Check if category matches any pattern
|
|
305
|
+
if (!matchesAnyCategory(vuln.category, categoryPatterns)) {
|
|
306
|
+
continue
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// If no severity threshold, or severity meets threshold
|
|
310
|
+
if (!severityThreshold || severityRank(vuln.severity) >= thresholdRank) {
|
|
311
|
+
matched.add(vuln.category)
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
return Array.from(matched)
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* Parse comma-separated category string into array
|
|
320
|
+
*
|
|
321
|
+
* @param input - Comma-separated category string
|
|
322
|
+
* @returns Array of trimmed category patterns
|
|
323
|
+
*
|
|
324
|
+
* @example
|
|
325
|
+
* parseCategoryList('ai-*, secrets-*') // ['ai-*', 'secrets-*']
|
|
326
|
+
* parseCategoryList('sql_injection, xss') // ['sql_injection', 'xss']
|
|
327
|
+
*/
|
|
328
|
+
export function parseCategoryList(input: string): string[] {
|
|
329
|
+
if (!input || typeof input !== 'string') {
|
|
330
|
+
return []
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
return input
|
|
334
|
+
.split(',')
|
|
335
|
+
.map(s => s.trim())
|
|
336
|
+
.filter(s => s.length > 0)
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* Validate category names, separating valid from invalid
|
|
341
|
+
*
|
|
342
|
+
* @param categories - Array of category patterns to validate
|
|
343
|
+
* @returns Object with valid and invalid category arrays
|
|
344
|
+
*
|
|
345
|
+
* @example
|
|
346
|
+
* validateCategories(['ai-*', 'sql_injection', 'fake_category'])
|
|
347
|
+
* // { valid: ['ai-*', 'sql_injection'], invalid: ['fake_category'] }
|
|
348
|
+
*/
|
|
349
|
+
export function validateCategories(categories: string[]): {
|
|
350
|
+
valid: string[]
|
|
351
|
+
invalid: string[]
|
|
352
|
+
} {
|
|
353
|
+
const valid: string[] = []
|
|
354
|
+
const invalid: string[] = []
|
|
355
|
+
|
|
356
|
+
for (const category of categories) {
|
|
357
|
+
const normalized = normalizeCategory(category)
|
|
358
|
+
|
|
359
|
+
// Check if it's a valid wildcard
|
|
360
|
+
if (isWildcardPattern(normalized)) {
|
|
361
|
+
const normalizedWildcard = normalized.replace('_*', '-*')
|
|
362
|
+
if (CATEGORY_GROUPS[normalizedWildcard]) {
|
|
363
|
+
valid.push(category)
|
|
364
|
+
} else {
|
|
365
|
+
invalid.push(category)
|
|
366
|
+
}
|
|
367
|
+
continue
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// Check if it's a valid category name
|
|
371
|
+
const expanded = expandCategoryPattern(category)
|
|
372
|
+
if (expanded.length > 0) {
|
|
373
|
+
valid.push(category)
|
|
374
|
+
} else {
|
|
375
|
+
invalid.push(category)
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
return { valid, invalid }
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
/**
|
|
383
|
+
* Get a human-readable list of available category groups
|
|
384
|
+
* Useful for help text and error messages
|
|
385
|
+
*/
|
|
386
|
+
export function getAvailableCategoryGroups(): string[] {
|
|
387
|
+
return Object.keys(CATEGORY_GROUPS)
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* Get the count of categories in each group
|
|
392
|
+
* Useful for documentation and help text
|
|
393
|
+
*/
|
|
394
|
+
export function getCategoryGroupCounts(): Record<string, number> {
|
|
395
|
+
const counts: Record<string, number> = {}
|
|
396
|
+
for (const [group, categories] of Object.entries(CATEGORY_GROUPS)) {
|
|
397
|
+
counts[group] = categories.length
|
|
398
|
+
}
|
|
399
|
+
return counts
|
|
400
|
+
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { FilterPipeline } from '../pipeline'
|
|
2
|
+
import type { FilterDecision } from '../pipeline'
|
|
3
|
+
|
|
4
|
+
describe('FilterPipeline', () => {
|
|
5
|
+
describe('when disabled', () => {
|
|
6
|
+
it('does not record decisions', () => {
|
|
7
|
+
const pipeline = new FilterPipeline(false)
|
|
8
|
+
pipeline.record('finding-1', {
|
|
9
|
+
stage: 'auto_dismiss',
|
|
10
|
+
action: 'dismissed',
|
|
11
|
+
reason: 'test file',
|
|
12
|
+
})
|
|
13
|
+
expect(pipeline.getAuditTrail()).toEqual([])
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
it('reports isEnabled as false', () => {
|
|
17
|
+
expect(new FilterPipeline(false).isEnabled).toBe(false)
|
|
18
|
+
expect(new FilterPipeline().isEnabled).toBe(false)
|
|
19
|
+
})
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
describe('when enabled', () => {
|
|
23
|
+
let pipeline: FilterPipeline
|
|
24
|
+
|
|
25
|
+
beforeEach(() => {
|
|
26
|
+
pipeline = new FilterPipeline(true)
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
it('reports isEnabled as true', () => {
|
|
30
|
+
expect(pipeline.isEnabled).toBe(true)
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
it('records and retrieves decisions', () => {
|
|
34
|
+
const decision: FilterDecision = {
|
|
35
|
+
stage: 'auto_dismiss',
|
|
36
|
+
action: 'dismissed',
|
|
37
|
+
reason: 'test file detected',
|
|
38
|
+
ruleName: 'test_file_dismiss',
|
|
39
|
+
}
|
|
40
|
+
pipeline.record('finding-1', decision)
|
|
41
|
+
|
|
42
|
+
const trail = pipeline.getAuditTrail()
|
|
43
|
+
expect(trail).toHaveLength(1)
|
|
44
|
+
expect(trail[0].findingId).toBe('finding-1')
|
|
45
|
+
expect(trail[0].decisions).toEqual([decision])
|
|
46
|
+
expect(trail[0].finalStatus).toBe('dismissed')
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
it('accumulates multiple decisions for the same finding', () => {
|
|
50
|
+
pipeline.record('finding-1', {
|
|
51
|
+
stage: 'file_context_adjustment',
|
|
52
|
+
action: 'downgraded',
|
|
53
|
+
reason: 'tooling directory',
|
|
54
|
+
originalSeverity: 'medium',
|
|
55
|
+
newSeverity: 'info',
|
|
56
|
+
})
|
|
57
|
+
pipeline.record('finding-1', {
|
|
58
|
+
stage: 'ai_validation',
|
|
59
|
+
action: 'kept',
|
|
60
|
+
reason: 'AI confirmed the finding',
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
const decisions = pipeline.explainDismissal('finding-1')
|
|
64
|
+
expect(decisions).toHaveLength(2)
|
|
65
|
+
expect(decisions[0].stage).toBe('file_context_adjustment')
|
|
66
|
+
expect(decisions[1].stage).toBe('ai_validation')
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
it('marks final status as surfaced when no dismissal', () => {
|
|
70
|
+
pipeline.record('finding-1', {
|
|
71
|
+
stage: 'ai_validation',
|
|
72
|
+
action: 'kept',
|
|
73
|
+
reason: 'confirmed',
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
const trail = pipeline.getAuditTrail()
|
|
77
|
+
expect(trail[0].finalStatus).toBe('surfaced')
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
it('marks final status as dismissed for deduplicated findings', () => {
|
|
81
|
+
pipeline.record('finding-1', {
|
|
82
|
+
stage: 'deduplication',
|
|
83
|
+
action: 'deduplicated',
|
|
84
|
+
reason: 'duplicate of finding-2',
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
const trail = pipeline.getAuditTrail()
|
|
88
|
+
expect(trail[0].finalStatus).toBe('dismissed')
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
it('marks final status as dismissed for suppressed findings', () => {
|
|
92
|
+
pipeline.record('finding-1', {
|
|
93
|
+
stage: 'user_suppression',
|
|
94
|
+
action: 'suppressed',
|
|
95
|
+
reason: 'inline oculum-ignore comment',
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
const trail = pipeline.getAuditTrail()
|
|
99
|
+
expect(trail[0].finalStatus).toBe('dismissed')
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
it('returns empty array for unknown finding in explainDismissal', () => {
|
|
103
|
+
expect(pipeline.explainDismissal('nonexistent')).toEqual([])
|
|
104
|
+
})
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
describe('getSummaryStats', () => {
|
|
108
|
+
it('aggregates counts per stage and action', () => {
|
|
109
|
+
const pipeline = new FilterPipeline(true)
|
|
110
|
+
|
|
111
|
+
pipeline.record('f1', { stage: 'auto_dismiss', action: 'dismissed', reason: 'test' })
|
|
112
|
+
pipeline.record('f2', { stage: 'auto_dismiss', action: 'dismissed', reason: 'css' })
|
|
113
|
+
pipeline.record('f3', { stage: 'auto_dismiss', action: 'kept', reason: 'real finding' })
|
|
114
|
+
pipeline.record('f4', { stage: 'ai_validation', action: 'downgraded', reason: 'lower severity' })
|
|
115
|
+
pipeline.record('f5', { stage: 'deduplication', action: 'deduplicated', reason: 'dup' })
|
|
116
|
+
|
|
117
|
+
const stats = pipeline.getSummaryStats()
|
|
118
|
+
|
|
119
|
+
expect(stats.auto_dismiss.dismissed).toBe(2)
|
|
120
|
+
expect(stats.auto_dismiss.kept).toBe(1)
|
|
121
|
+
expect(stats.ai_validation.downgraded).toBe(1)
|
|
122
|
+
expect(stats.deduplication.deduplicated).toBe(1)
|
|
123
|
+
expect(stats.localhost_aggregation.dismissed).toBe(0)
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
it('returns zero counts when no decisions recorded', () => {
|
|
127
|
+
const pipeline = new FilterPipeline(true)
|
|
128
|
+
const stats = pipeline.getSummaryStats()
|
|
129
|
+
|
|
130
|
+
expect(stats.auto_dismiss.dismissed).toBe(0)
|
|
131
|
+
expect(stats.tier_filter.kept).toBe(0)
|
|
132
|
+
})
|
|
133
|
+
})
|
|
134
|
+
})
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Centralized Context-Based Severity Adjustments
|
|
3
|
+
*
|
|
4
|
+
* Consolidates the duplicate filter logic from:
|
|
5
|
+
* - layer2/index.ts applyFileContextAdjustments() — per-file with full FileContext
|
|
6
|
+
* - index.ts applyGlobalContextAdjustments() — post-layers with tooling-dir only
|
|
7
|
+
*
|
|
8
|
+
* Both functions are replaced by a single applyContextAdjustments() that handles
|
|
9
|
+
* all context-based severity downgrades in one place.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import type { Vulnerability } from '../types'
|
|
13
|
+
import type { FileContext } from '../utils/context-helpers'
|
|
14
|
+
|
|
15
|
+
/** Categories that are expected and not risky in tooling directories */
|
|
16
|
+
const TOOLING_DOWNGRADABLE_CATEGORIES = [
|
|
17
|
+
'dangerous_function', // exec, spawn, etc. are expected in scripts
|
|
18
|
+
'command_injection', // child_process usage is expected in build scripts
|
|
19
|
+
'weak_crypto', // Math.random() for seeding is fine in scripts
|
|
20
|
+
'data_exposure', // Logging is expected in tooling
|
|
21
|
+
'ai_pattern', // AI patterns in scripts are typically fine
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
/** Categories that are expected and not risky in test files */
|
|
25
|
+
const TEST_DOWNGRADABLE_CATEGORIES = [
|
|
26
|
+
'ai_pattern',
|
|
27
|
+
'dangerous_function',
|
|
28
|
+
'sensitive_variable',
|
|
29
|
+
'data_exposure',
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Apply context-based severity adjustments to findings.
|
|
34
|
+
*
|
|
35
|
+
* When called with a FileContext (from Layer 2 per-file processing), applies
|
|
36
|
+
* full context-aware rules: test files, tooling dirs, server-only files.
|
|
37
|
+
*
|
|
38
|
+
* When called without a FileContext (from the orchestrator for Layer 1 findings),
|
|
39
|
+
* applies only tooling-directory downgrades based on file path.
|
|
40
|
+
*/
|
|
41
|
+
export function applyContextAdjustments(
|
|
42
|
+
findings: Vulnerability[],
|
|
43
|
+
fileContext?: FileContext
|
|
44
|
+
): Vulnerability[] {
|
|
45
|
+
return findings.map(finding => {
|
|
46
|
+
// Never downgrade critical findings
|
|
47
|
+
if (finding.severity === 'critical') {
|
|
48
|
+
return finding
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (fileContext) {
|
|
52
|
+
// Full per-file context available (Layer 2 path)
|
|
53
|
+
|
|
54
|
+
// Test files: Downgrade specific categories
|
|
55
|
+
if (fileContext.isTestFile) {
|
|
56
|
+
if (TEST_DOWNGRADABLE_CATEGORIES.includes(finding.category)) {
|
|
57
|
+
return {
|
|
58
|
+
...finding,
|
|
59
|
+
severity: 'info' as const,
|
|
60
|
+
validationNotes: 'Downgraded: test/fixture file',
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Tooling directories: Downgrade expected patterns
|
|
66
|
+
if (fileContext.isToolingDir) {
|
|
67
|
+
if (TOOLING_DOWNGRADABLE_CATEGORIES.includes(finding.category)) {
|
|
68
|
+
return {
|
|
69
|
+
...finding,
|
|
70
|
+
severity: 'info' as const,
|
|
71
|
+
validationNotes: 'Downgraded: tooling/scripts directory',
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Server-only files: Client-exposure concerns don't apply
|
|
77
|
+
if (fileContext.isServerOnly) {
|
|
78
|
+
if (finding.category === 'hardcoded_secret' &&
|
|
79
|
+
finding.title.toLowerCase().includes('service role')) {
|
|
80
|
+
return {
|
|
81
|
+
...finding,
|
|
82
|
+
severity: 'info' as const,
|
|
83
|
+
validationNotes: 'Downgraded: server-only file (service role key expected)',
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
} else {
|
|
88
|
+
// No file context — apply path-based adjustments only (orchestrator path)
|
|
89
|
+
// This catches Layer 1 findings that didn't go through Layer 2's per-file adjustments
|
|
90
|
+
const inToolingDir = isToolingPath(finding.filePath)
|
|
91
|
+
|
|
92
|
+
if (inToolingDir && TOOLING_DOWNGRADABLE_CATEGORIES.includes(finding.category)) {
|
|
93
|
+
return {
|
|
94
|
+
...finding,
|
|
95
|
+
severity: 'info' as const,
|
|
96
|
+
validationNotes: `${finding.validationNotes || ''} Downgraded: tooling/scripts directory`.trim(),
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return finding
|
|
102
|
+
})
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Tooling-path check for findings without FileContext.
|
|
107
|
+
* Uses the same regex as utils/context-helpers.ts isToolingDirectory().
|
|
108
|
+
*/
|
|
109
|
+
function isToolingPath(filePath: string): boolean {
|
|
110
|
+
return /\/(scripts?|cli|tools?|bin|devtools|build|tasks)\//i.test(filePath)
|
|
111
|
+
}
|