@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
|
@@ -464,4 +464,494 @@ describe('API tests', () => {
|
|
|
464
464
|
}
|
|
465
465
|
})
|
|
466
466
|
})
|
|
467
|
+
|
|
468
|
+
describe('8. Sprint 4 FP Fixes', () => {
|
|
469
|
+
it('should skip Math.random in template placeholders', async () => {
|
|
470
|
+
// Template placeholder generators use Math.random for non-security purposes
|
|
471
|
+
const file: ScanFile = {
|
|
472
|
+
path: 'src/utils/templates.ts',
|
|
473
|
+
content: `
|
|
474
|
+
const templates = {
|
|
475
|
+
random: () => Math.random().toString(),
|
|
476
|
+
random_hex: () => Math.random().toString(16).slice(2),
|
|
477
|
+
timestamp: () => Date.now().toString(),
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
export function generatePlaceholder(type: string) {
|
|
481
|
+
const generator = templates[type]
|
|
482
|
+
return generator ? generator() : '{{' + type + '}}'
|
|
483
|
+
}
|
|
484
|
+
`,
|
|
485
|
+
language: 'typescript',
|
|
486
|
+
size: 300,
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
const findings = await scanFile(file)
|
|
490
|
+
const mathRandomFindings = findByCategory(findings, 'dangerous_function').filter(f =>
|
|
491
|
+
f.title?.toLowerCase().includes('math.random')
|
|
492
|
+
)
|
|
493
|
+
|
|
494
|
+
// Template placeholder Math.random should be skipped entirely or INFO at most
|
|
495
|
+
expect(mathRandomFindings.length).toBe(0)
|
|
496
|
+
})
|
|
497
|
+
|
|
498
|
+
it('should skip Math.random in Three.js animation', async () => {
|
|
499
|
+
// Three.js uses Math.random for visual effects, not security
|
|
500
|
+
const file: ScanFile = {
|
|
501
|
+
path: 'src/components/ParticleField.tsx',
|
|
502
|
+
content: `
|
|
503
|
+
import { useFrame } from '@react-three/fiber'
|
|
504
|
+
import { useMemo } from 'react'
|
|
505
|
+
|
|
506
|
+
export function ParticleField() {
|
|
507
|
+
const offset = useMemo(() => Math.random() * Math.PI * 2, [])
|
|
508
|
+
const speed = useMemo(() => 0.5 + Math.random() * 0.5, [])
|
|
509
|
+
|
|
510
|
+
useFrame((state) => {
|
|
511
|
+
// Animation logic using random offset
|
|
512
|
+
meshRef.current.rotation.y = state.clock.elapsedTime * speed + offset
|
|
513
|
+
})
|
|
514
|
+
|
|
515
|
+
return <mesh ref={meshRef}><sphereGeometry /></mesh>
|
|
516
|
+
}
|
|
517
|
+
`,
|
|
518
|
+
language: 'typescript',
|
|
519
|
+
size: 400,
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
const findings = await scanFile(file)
|
|
523
|
+
const mathRandomFindings = findByCategory(findings, 'dangerous_function').filter(f =>
|
|
524
|
+
f.title?.toLowerCase().includes('math.random')
|
|
525
|
+
)
|
|
526
|
+
|
|
527
|
+
// Three.js animation Math.random should be skipped entirely
|
|
528
|
+
expect(mathRandomFindings.length).toBe(0)
|
|
529
|
+
})
|
|
530
|
+
|
|
531
|
+
it('should skip regex with same-line escaping', async () => {
|
|
532
|
+
// Escaping on the same line as RegExp is safe
|
|
533
|
+
const file: ScanFile = {
|
|
534
|
+
path: 'src/utils/search.ts',
|
|
535
|
+
content: `
|
|
536
|
+
export function createSearchRegex(input: string) {
|
|
537
|
+
// Same-line escaping pattern - escapeRegExp function
|
|
538
|
+
return new RegExp(escapeRegExp(input), 'gi')
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
export function escapeAndCreate(pattern: string) {
|
|
542
|
+
const escaped = escapeRegExp(pattern)
|
|
543
|
+
return new RegExp(escaped)
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
function escapeRegExp(str: string) {
|
|
547
|
+
return str.replace(/[.*+?^\${}()|[\\]\\\\]/g, '\\\\$&')
|
|
548
|
+
}
|
|
549
|
+
`,
|
|
550
|
+
language: 'typescript',
|
|
551
|
+
size: 300,
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
const findings = await scanFile(file)
|
|
555
|
+
const regexFindings = findByCategory(findings, 'dangerous_function').filter(f =>
|
|
556
|
+
f.title?.toLowerCase().includes('regex')
|
|
557
|
+
)
|
|
558
|
+
|
|
559
|
+
// Escaped regex should be skipped
|
|
560
|
+
expect(regexFindings.length).toBe(0)
|
|
561
|
+
})
|
|
562
|
+
|
|
563
|
+
it('should downgrade file path in desktop app to INFO', async () => {
|
|
564
|
+
// Desktop apps legitimately access filesystem
|
|
565
|
+
const file: ScanFile = {
|
|
566
|
+
path: 'apps/desktop/src/main/fileService.ts',
|
|
567
|
+
content: `
|
|
568
|
+
import fs from 'fs'
|
|
569
|
+
import path from 'path'
|
|
570
|
+
|
|
571
|
+
export function readUserFile(filename: string) {
|
|
572
|
+
const userPath = path.join(app.getPath('userData'), filename)
|
|
573
|
+
return fs.readFileSync(userPath, 'utf-8')
|
|
574
|
+
}
|
|
575
|
+
`,
|
|
576
|
+
language: 'typescript',
|
|
577
|
+
size: 200,
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
const findings = await scanFile(file)
|
|
581
|
+
const filePathFindings = findByCategory(findings, 'dangerous_function').filter(f =>
|
|
582
|
+
f.title?.toLowerCase().includes('file path') || f.title?.toLowerCase().includes('path traversal')
|
|
583
|
+
)
|
|
584
|
+
|
|
585
|
+
// Desktop app file paths should be INFO (not MEDIUM/HIGH)
|
|
586
|
+
for (const f of filePathFindings) {
|
|
587
|
+
expect(['info', 'low']).toContain(f.severity)
|
|
588
|
+
}
|
|
589
|
+
})
|
|
590
|
+
|
|
591
|
+
it('should downgrade child_process in desktop app to MEDIUM', async () => {
|
|
592
|
+
// Desktop apps legitimately spawn processes
|
|
593
|
+
const file: ScanFile = {
|
|
594
|
+
path: 'packages/electron-app/src/ipcHandlers.ts',
|
|
595
|
+
content: `
|
|
596
|
+
import { ipcMain } from 'electron'
|
|
597
|
+
import { spawn } from 'child_process'
|
|
598
|
+
|
|
599
|
+
ipcMain.handle('run-command', async (event, command, args) => {
|
|
600
|
+
// Spawning processes in Electron is expected
|
|
601
|
+
const process = spawn(command, args)
|
|
602
|
+
return new Promise((resolve, reject) => {
|
|
603
|
+
let output = ''
|
|
604
|
+
process.stdout.on('data', (data) => output += data)
|
|
605
|
+
process.on('close', (code) => resolve({ code, output }))
|
|
606
|
+
process.on('error', reject)
|
|
607
|
+
})
|
|
608
|
+
})
|
|
609
|
+
`,
|
|
610
|
+
language: 'typescript',
|
|
611
|
+
size: 400,
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
const findings = await scanFile(file)
|
|
615
|
+
const childProcessFindings = findByCategory(findings, 'dangerous_function').filter(f =>
|
|
616
|
+
f.title?.toLowerCase().includes('child_process')
|
|
617
|
+
)
|
|
618
|
+
|
|
619
|
+
// Desktop app child_process should be MEDIUM (not HIGH/CRITICAL)
|
|
620
|
+
for (const f of childProcessFindings) {
|
|
621
|
+
expect(['info', 'low', 'medium']).toContain(f.severity)
|
|
622
|
+
}
|
|
623
|
+
})
|
|
624
|
+
|
|
625
|
+
it('should skip Math.random in load balancing context', async () => {
|
|
626
|
+
// Load balancing/selection uses Math.random for distribution, not security
|
|
627
|
+
const file: ScanFile = {
|
|
628
|
+
path: 'src/utils/loadBalancer.ts',
|
|
629
|
+
content: `
|
|
630
|
+
export function selectEndpoint(endpoints: string[], mode: 'random' | 'round-robin') {
|
|
631
|
+
if (mode === 'random') {
|
|
632
|
+
const index = Math.floor(Math.random() * endpoints.length)
|
|
633
|
+
return endpoints[index]
|
|
634
|
+
}
|
|
635
|
+
return endpoints[currentIndex++ % endpoints.length]
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
export function pickRandomServer(servers: Server[]) {
|
|
639
|
+
const keys = Object.keys(servers)
|
|
640
|
+
return servers[keys[Math.floor(Math.random() * keys.length)]]
|
|
641
|
+
}
|
|
642
|
+
`,
|
|
643
|
+
language: 'typescript',
|
|
644
|
+
size: 400,
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
const findings = await scanFile(file)
|
|
648
|
+
const mathRandomFindings = findByCategory(findings, 'dangerous_function').filter(f =>
|
|
649
|
+
f.title?.toLowerCase().includes('math.random')
|
|
650
|
+
)
|
|
651
|
+
|
|
652
|
+
// Load balancing Math.random should have info/low severity or be skipped
|
|
653
|
+
for (const f of mathRandomFindings) {
|
|
654
|
+
expect(['info', 'low']).toContain(f.severity)
|
|
655
|
+
}
|
|
656
|
+
})
|
|
657
|
+
})
|
|
658
|
+
|
|
659
|
+
describe('9. Sprint 5 FP Fixes', () => {
|
|
660
|
+
it('should skip regex with multi-line .replaceAll escaping', async () => {
|
|
661
|
+
// Multi-line escaping pattern - escaping happens on previous lines before RegExp
|
|
662
|
+
const file: ScanFile = {
|
|
663
|
+
path: 'src/utils/matcher.ts',
|
|
664
|
+
content: `
|
|
665
|
+
const regexStr = pattern
|
|
666
|
+
.replaceAll(/[$()*+.?[\\]^{|}]/g, '\\\\$&')
|
|
667
|
+
.replaceAll('\\\\(.*\\\\)', '.*');
|
|
668
|
+
return new RegExp(\`^\${regexStr}$\`);
|
|
669
|
+
`,
|
|
670
|
+
language: 'typescript',
|
|
671
|
+
size: 200,
|
|
672
|
+
}
|
|
673
|
+
const findings = await scanFile(file)
|
|
674
|
+
const regexFindings = findByCategory(findings, 'dangerous_function')
|
|
675
|
+
.filter(f => f.title?.toLowerCase().includes('regex'))
|
|
676
|
+
expect(regexFindings.length).toBe(0)
|
|
677
|
+
})
|
|
678
|
+
|
|
679
|
+
it('should skip new RegExp(existingRegex.source)', async () => {
|
|
680
|
+
// Using .source from an existing RegExp is safe - it's already validated
|
|
681
|
+
const file: ScanFile = {
|
|
682
|
+
path: 'src/utils/regex.ts',
|
|
683
|
+
content: `
|
|
684
|
+
const globalPattern = pattern.global
|
|
685
|
+
? pattern
|
|
686
|
+
: new RegExp(pattern.source, pattern.flags + 'g');
|
|
687
|
+
`,
|
|
688
|
+
language: 'typescript',
|
|
689
|
+
size: 150,
|
|
690
|
+
}
|
|
691
|
+
const findings = await scanFile(file)
|
|
692
|
+
const regexFindings = findByCategory(findings, 'dangerous_function')
|
|
693
|
+
.filter(f => f.title?.toLowerCase().includes('regex'))
|
|
694
|
+
expect(regexFindings.length).toBe(0)
|
|
695
|
+
})
|
|
696
|
+
|
|
697
|
+
it('should skip readdir + pMap iteration', async () => {
|
|
698
|
+
// Paths from fs.readdir iterated via pMap are filesystem-controlled
|
|
699
|
+
const file: ScanFile = {
|
|
700
|
+
path: 'src/cache/reader.ts',
|
|
701
|
+
content: `
|
|
702
|
+
import fs from 'fs/promises'
|
|
703
|
+
import pMap from 'p-map'
|
|
704
|
+
|
|
705
|
+
const files = await fs.readdir(cachePath);
|
|
706
|
+
const results = await pMap(files, async (file) => {
|
|
707
|
+
return fs.readFile(\`\${cachePath}/\${file}\`);
|
|
708
|
+
});
|
|
709
|
+
`,
|
|
710
|
+
language: 'typescript',
|
|
711
|
+
size: 200,
|
|
712
|
+
}
|
|
713
|
+
const findings = await scanFile(file)
|
|
714
|
+
const pathFindings = findByCategory(findings, 'dangerous_function')
|
|
715
|
+
.filter(f => f.title?.toLowerCase().includes('file path') || f.title?.toLowerCase().includes('path traversal'))
|
|
716
|
+
expect(pathFindings.length).toBe(0)
|
|
717
|
+
})
|
|
718
|
+
|
|
719
|
+
it('should skip readdir + .map() iteration', async () => {
|
|
720
|
+
// Paths from fs.readdir iterated via .map() are filesystem-controlled
|
|
721
|
+
const file: ScanFile = {
|
|
722
|
+
path: 'src/cache/loader.ts',
|
|
723
|
+
content: `
|
|
724
|
+
import fs from 'fs/promises'
|
|
725
|
+
|
|
726
|
+
const files = await fs.readdir(cachePath);
|
|
727
|
+
const results = files.map((file) => {
|
|
728
|
+
return \`\${cachePath}/\${file}\`;
|
|
729
|
+
});
|
|
730
|
+
`,
|
|
731
|
+
language: 'typescript',
|
|
732
|
+
size: 200,
|
|
733
|
+
}
|
|
734
|
+
const findings = await scanFile(file)
|
|
735
|
+
const pathFindings = findByCategory(findings, 'dangerous_function')
|
|
736
|
+
.filter(f => f.title?.toLowerCase().includes('file path') || f.title?.toLowerCase().includes('path traversal'))
|
|
737
|
+
expect(pathFindings.length).toBe(0)
|
|
738
|
+
})
|
|
739
|
+
|
|
740
|
+
it('should skip readdir + Promise.all iteration', async () => {
|
|
741
|
+
// Paths from fs.readdir with Promise.all mapping are filesystem-controlled
|
|
742
|
+
const file: ScanFile = {
|
|
743
|
+
path: 'src/cache/processor.ts',
|
|
744
|
+
content: `
|
|
745
|
+
import fs from 'fs/promises'
|
|
746
|
+
|
|
747
|
+
const files = await fs.readdir(cachePath);
|
|
748
|
+
const results = await Promise.all(files.map(async (file) => {
|
|
749
|
+
return fs.readFile(\`\${cachePath}/\${file}\`);
|
|
750
|
+
}));
|
|
751
|
+
`,
|
|
752
|
+
language: 'typescript',
|
|
753
|
+
size: 200,
|
|
754
|
+
}
|
|
755
|
+
const findings = await scanFile(file)
|
|
756
|
+
const pathFindings = findByCategory(findings, 'dangerous_function')
|
|
757
|
+
.filter(f => f.title?.toLowerCase().includes('file path') || f.title?.toLowerCase().includes('path traversal'))
|
|
758
|
+
expect(pathFindings.length).toBe(0)
|
|
759
|
+
})
|
|
760
|
+
|
|
761
|
+
it('should skip Object.entries over hardcoded object', async () => {
|
|
762
|
+
// Iterating over hardcoded objects is safe - not user input
|
|
763
|
+
const file: ScanFile = {
|
|
764
|
+
path: 'src/fonts/loader.ts',
|
|
765
|
+
content: `
|
|
766
|
+
import fs from 'fs/promises'
|
|
767
|
+
|
|
768
|
+
const fontFiles = {
|
|
769
|
+
'font1.ttf': 'https://example.com/font1.ttf',
|
|
770
|
+
'font2.ttf': 'https://example.com/font2.ttf',
|
|
771
|
+
};
|
|
772
|
+
for (const [filename, url] of Object.entries(fontFiles)) {
|
|
773
|
+
await fs.writeFile(\`/fonts/\${filename}\`, await fetch(url).then(r => r.arrayBuffer()));
|
|
774
|
+
}
|
|
775
|
+
`,
|
|
776
|
+
language: 'typescript',
|
|
777
|
+
size: 250,
|
|
778
|
+
}
|
|
779
|
+
const findings = await scanFile(file)
|
|
780
|
+
const pathFindings = findByCategory(findings, 'dangerous_function')
|
|
781
|
+
.filter(f => f.title?.toLowerCase().includes('file path') || f.title?.toLowerCase().includes('path traversal'))
|
|
782
|
+
expect(pathFindings.length).toBe(0)
|
|
783
|
+
})
|
|
784
|
+
|
|
785
|
+
it('should skip static subprocess list args split across multiple lines', async () => {
|
|
786
|
+
// Multi-line subprocess.Popen(["xprop", "-root", ...]) is safe — all static strings
|
|
787
|
+
const file: ScanFile = {
|
|
788
|
+
path: 'utils/wtf.py',
|
|
789
|
+
content: `
|
|
790
|
+
import subprocess
|
|
791
|
+
|
|
792
|
+
def get_active_window():
|
|
793
|
+
output = subprocess.Popen(
|
|
794
|
+
["xprop", "-root", "_NET_ACTIVE_WINDOW"],
|
|
795
|
+
stdout=subprocess.PIPE
|
|
796
|
+
).communicate()[0]
|
|
797
|
+
return output
|
|
798
|
+
`,
|
|
799
|
+
language: 'python',
|
|
800
|
+
size: 200,
|
|
801
|
+
}
|
|
802
|
+
const findings = await scanFile(file)
|
|
803
|
+
const subprocessFindings = findByCategory(findings, 'dangerous_function')
|
|
804
|
+
.filter(f => f.title?.toLowerCase().includes('subprocess') || f.title?.toLowerCase().includes('os.system'))
|
|
805
|
+
// Static multi-line list args should produce no HIGH finding
|
|
806
|
+
for (const f of subprocessFindings) {
|
|
807
|
+
expect(['info', 'low']).toContain(f.severity)
|
|
808
|
+
}
|
|
809
|
+
// Actually should be fully skipped (all static)
|
|
810
|
+
expect(subprocessFindings.length).toBe(0)
|
|
811
|
+
})
|
|
812
|
+
|
|
813
|
+
it('should produce LOW for multi-line subprocess list with variable args', async () => {
|
|
814
|
+
// subprocess.Popen(["xwininfo", "-id", window_id]) — list with a variable
|
|
815
|
+
const file: ScanFile = {
|
|
816
|
+
path: 'utils/wtf.py',
|
|
817
|
+
content: `
|
|
818
|
+
import subprocess
|
|
819
|
+
|
|
820
|
+
def get_window_info(window_id):
|
|
821
|
+
output = subprocess.Popen(
|
|
822
|
+
["xwininfo", "-id", window_id],
|
|
823
|
+
stdout=subprocess.PIPE
|
|
824
|
+
).communicate()[0]
|
|
825
|
+
return output
|
|
826
|
+
`,
|
|
827
|
+
language: 'python',
|
|
828
|
+
size: 200,
|
|
829
|
+
}
|
|
830
|
+
const findings = await scanFile(file)
|
|
831
|
+
const subprocessFindings = findByCategory(findings, 'dangerous_function')
|
|
832
|
+
.filter(f => f.title?.toLowerCase().includes('subprocess') || f.title?.toLowerCase().includes('os.system'))
|
|
833
|
+
// List args with variables should be LOW at most
|
|
834
|
+
for (const f of subprocessFindings) {
|
|
835
|
+
expect(['info', 'low']).toContain(f.severity)
|
|
836
|
+
}
|
|
837
|
+
})
|
|
838
|
+
|
|
839
|
+
it('should produce LOW for subprocess called with a variable that resolves to a list', async () => {
|
|
840
|
+
// args = ["osascript", "-e", script]; subprocess.check_output(args, ...)
|
|
841
|
+
const file: ScanFile = {
|
|
842
|
+
path: 'utils/run_applescript.py',
|
|
843
|
+
content: `
|
|
844
|
+
import subprocess
|
|
845
|
+
|
|
846
|
+
def run_applescript(script):
|
|
847
|
+
args = ["osascript", "-e", script]
|
|
848
|
+
return subprocess.check_output(args, stderr=subprocess.PIPE).decode()
|
|
849
|
+
`,
|
|
850
|
+
language: 'python',
|
|
851
|
+
size: 200,
|
|
852
|
+
}
|
|
853
|
+
const findings = await scanFile(file)
|
|
854
|
+
const subprocessFindings = findByCategory(findings, 'dangerous_function')
|
|
855
|
+
.filter(f => f.title?.toLowerCase().includes('subprocess') || f.title?.toLowerCase().includes('os.system'))
|
|
856
|
+
// Variable resolving to list with dynamic element → LOW
|
|
857
|
+
for (const f of subprocessFindings) {
|
|
858
|
+
expect(['info', 'low']).toContain(f.severity)
|
|
859
|
+
}
|
|
860
|
+
})
|
|
861
|
+
|
|
862
|
+
it('should preserve HIGH for f-string direct arg to subprocess', async () => {
|
|
863
|
+
// subprocess.check_output(f"curl {api_base}") — real command injection risk
|
|
864
|
+
const file: ScanFile = {
|
|
865
|
+
path: 'utils/system_debug_info.py',
|
|
866
|
+
content: `
|
|
867
|
+
import subprocess
|
|
868
|
+
|
|
869
|
+
def check_api(api_base):
|
|
870
|
+
result = subprocess.check_output(f"curl {api_base}", shell=False)
|
|
871
|
+
return result.decode()
|
|
872
|
+
`,
|
|
873
|
+
language: 'python',
|
|
874
|
+
size: 150,
|
|
875
|
+
}
|
|
876
|
+
const findings = await scanFile(file)
|
|
877
|
+
const subprocessFindings = findByCategory(findings, 'dangerous_function')
|
|
878
|
+
.filter(f => f.title?.toLowerCase().includes('subprocess') || f.title?.toLowerCase().includes('os.system'))
|
|
879
|
+
// f-string direct arg → HIGH (true positive)
|
|
880
|
+
const hasSevere = subprocessFindings.some(f => f.severity === 'high' || f.severity === 'critical')
|
|
881
|
+
expect(hasSevere).toBe(true)
|
|
882
|
+
})
|
|
883
|
+
|
|
884
|
+
it('should preserve HIGH for subprocess with shell=True', async () => {
|
|
885
|
+
// subprocess.run(f"cd {path} && semgrep", shell=True) — real injection risk
|
|
886
|
+
const file: ScanFile = {
|
|
887
|
+
path: 'utils/scan_code.py',
|
|
888
|
+
content: `
|
|
889
|
+
import subprocess
|
|
890
|
+
|
|
891
|
+
def scan(path):
|
|
892
|
+
result = subprocess.run(
|
|
893
|
+
f"cd {path} && semgrep --config auto",
|
|
894
|
+
shell=True,
|
|
895
|
+
capture_output=True
|
|
896
|
+
)
|
|
897
|
+
return result.stdout.decode()
|
|
898
|
+
`,
|
|
899
|
+
language: 'python',
|
|
900
|
+
size: 200,
|
|
901
|
+
}
|
|
902
|
+
const findings = await scanFile(file)
|
|
903
|
+
const subprocessFindings = findByCategory(findings, 'dangerous_function')
|
|
904
|
+
.filter(f => f.title?.toLowerCase().includes('subprocess') || f.title?.toLowerCase().includes('os.system'))
|
|
905
|
+
// shell=True → HIGH (true positive)
|
|
906
|
+
const hasSevere = subprocessFindings.some(f => f.severity === 'high' || f.severity === 'critical')
|
|
907
|
+
expect(hasSevere).toBe(true)
|
|
908
|
+
})
|
|
909
|
+
|
|
910
|
+
it('should skip subprocess.run with static list args on single line', async () => {
|
|
911
|
+
// subprocess.run(["ollama", "list"], ...) — fully static, single line
|
|
912
|
+
const file: ScanFile = {
|
|
913
|
+
path: 'utils/local_setup.py',
|
|
914
|
+
content: `
|
|
915
|
+
import subprocess
|
|
916
|
+
|
|
917
|
+
def check_ollama():
|
|
918
|
+
result = subprocess.run(
|
|
919
|
+
["ollama", "list"],
|
|
920
|
+
capture_output=True, text=True
|
|
921
|
+
)
|
|
922
|
+
return result.stdout
|
|
923
|
+
`,
|
|
924
|
+
language: 'python',
|
|
925
|
+
size: 150,
|
|
926
|
+
}
|
|
927
|
+
const findings = await scanFile(file)
|
|
928
|
+
const subprocessFindings = findByCategory(findings, 'dangerous_function')
|
|
929
|
+
.filter(f => f.title?.toLowerCase().includes('subprocess') || f.title?.toLowerCase().includes('os.system'))
|
|
930
|
+
expect(subprocessFindings.length).toBe(0)
|
|
931
|
+
})
|
|
932
|
+
|
|
933
|
+
it('should skip Object.keys over hardcoded object for file paths', async () => {
|
|
934
|
+
// Object.keys over const object is also safe
|
|
935
|
+
const file: ScanFile = {
|
|
936
|
+
path: 'src/assets/loader.ts',
|
|
937
|
+
content: `
|
|
938
|
+
import fs from 'fs/promises'
|
|
939
|
+
|
|
940
|
+
const assetPaths = {
|
|
941
|
+
logo: '/assets/logo.png',
|
|
942
|
+
icon: '/assets/icon.svg',
|
|
943
|
+
};
|
|
944
|
+
for (const key of Object.keys(assetPaths)) {
|
|
945
|
+
await fs.access(\`/public/\${key}\`);
|
|
946
|
+
}
|
|
947
|
+
`,
|
|
948
|
+
language: 'typescript',
|
|
949
|
+
size: 200,
|
|
950
|
+
}
|
|
951
|
+
const findings = await scanFile(file)
|
|
952
|
+
const pathFindings = findByCategory(findings, 'dangerous_function')
|
|
953
|
+
.filter(f => f.title?.toLowerCase().includes('file path') || f.title?.toLowerCase().includes('path traversal'))
|
|
954
|
+
expect(pathFindings.length).toBe(0)
|
|
955
|
+
})
|
|
956
|
+
})
|
|
467
957
|
})
|