@oculum/scanner 1.0.14 → 1.0.15
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/detect/ai-code/index.d.ts +6 -11
- package/dist/detect/ai-code/index.d.ts.map +1 -1
- package/dist/detect/ai-code/index.js +6 -24
- package/dist/detect/ai-code/index.js.map +1 -1
- package/dist/detect/ast-rules/agent-tools-ast.d.ts +14 -0
- package/dist/detect/ast-rules/agent-tools-ast.d.ts.map +1 -0
- package/dist/detect/ast-rules/agent-tools-ast.js +809 -0
- package/dist/detect/ast-rules/agent-tools-ast.js.map +1 -0
- package/dist/detect/ast-rules/ai-fingerprinting-ast.d.ts +14 -0
- package/dist/detect/ast-rules/ai-fingerprinting-ast.d.ts.map +1 -0
- package/dist/detect/ast-rules/ai-fingerprinting-ast.js +344 -0
- package/dist/detect/ast-rules/ai-fingerprinting-ast.js.map +1 -0
- package/dist/detect/ast-rules/auth-patterns-ast.d.ts +14 -0
- package/dist/detect/ast-rules/auth-patterns-ast.d.ts.map +1 -0
- package/dist/detect/ast-rules/auth-patterns-ast.js +280 -0
- package/dist/detect/ast-rules/auth-patterns-ast.js.map +1 -0
- package/dist/detect/ast-rules/byok-ast.d.ts +13 -0
- package/dist/detect/ast-rules/byok-ast.d.ts.map +1 -0
- package/dist/detect/ast-rules/byok-ast.js +180 -0
- package/dist/detect/ast-rules/byok-ast.js.map +1 -0
- package/dist/detect/ast-rules/child-process-ast.d.ts +13 -0
- package/dist/detect/ast-rules/child-process-ast.d.ts.map +1 -0
- package/dist/detect/ast-rules/child-process-ast.js +252 -0
- package/dist/detect/ast-rules/child-process-ast.js.map +1 -0
- package/dist/detect/ast-rules/dangerous-eval-ast.d.ts +13 -0
- package/dist/detect/ast-rules/dangerous-eval-ast.d.ts.map +1 -0
- package/dist/detect/ast-rules/dangerous-eval-ast.js +218 -0
- package/dist/detect/ast-rules/dangerous-eval-ast.js.map +1 -0
- package/dist/detect/ast-rules/data-exposure-ast.d.ts +13 -0
- package/dist/detect/ast-rules/data-exposure-ast.d.ts.map +1 -0
- package/dist/detect/ast-rules/data-exposure-ast.js +158 -0
- package/dist/detect/ast-rules/data-exposure-ast.js.map +1 -0
- package/dist/detect/ast-rules/dom-xss-ast.d.ts +14 -0
- package/dist/detect/ast-rules/dom-xss-ast.d.ts.map +1 -0
- package/dist/detect/ast-rules/dom-xss-ast.js +217 -0
- package/dist/detect/ast-rules/dom-xss-ast.js.map +1 -0
- package/dist/detect/ast-rules/endpoint-protection-ast.d.ts +13 -0
- package/dist/detect/ast-rules/endpoint-protection-ast.d.ts.map +1 -0
- package/dist/detect/ast-rules/endpoint-protection-ast.js +228 -0
- package/dist/detect/ast-rules/endpoint-protection-ast.js.map +1 -0
- package/dist/detect/ast-rules/entropy-ast.d.ts +17 -0
- package/dist/detect/ast-rules/entropy-ast.d.ts.map +1 -0
- package/dist/detect/ast-rules/entropy-ast.js +265 -0
- package/dist/detect/ast-rules/entropy-ast.js.map +1 -0
- package/dist/detect/ast-rules/flask-debug-ast.d.ts +10 -0
- package/dist/detect/ast-rules/flask-debug-ast.d.ts.map +1 -0
- package/dist/detect/ast-rules/flask-debug-ast.js +125 -0
- package/dist/detect/ast-rules/flask-debug-ast.js.map +1 -0
- package/dist/detect/ast-rules/framework-checks-ast.d.ts +13 -0
- package/dist/detect/ast-rules/framework-checks-ast.d.ts.map +1 -0
- package/dist/detect/ast-rules/framework-checks-ast.js +185 -0
- package/dist/detect/ast-rules/framework-checks-ast.js.map +1 -0
- package/dist/detect/ast-rules/helpers/call-analysis.d.ts +62 -0
- package/dist/detect/ast-rules/helpers/call-analysis.d.ts.map +1 -0
- package/dist/detect/ast-rules/helpers/call-analysis.js +217 -0
- package/dist/detect/ast-rules/helpers/call-analysis.js.map +1 -0
- package/dist/detect/ast-rules/helpers/context-detection.d.ts +33 -0
- package/dist/detect/ast-rules/helpers/context-detection.d.ts.map +1 -0
- package/dist/detect/ast-rules/helpers/context-detection.js +256 -0
- package/dist/detect/ast-rules/helpers/context-detection.js.map +1 -0
- package/dist/detect/ast-rules/helpers/control-flow.d.ts +40 -0
- package/dist/detect/ast-rules/helpers/control-flow.d.ts.map +1 -0
- package/dist/detect/ast-rules/helpers/control-flow.js +174 -0
- package/dist/detect/ast-rules/helpers/control-flow.js.map +1 -0
- package/dist/detect/ast-rules/helpers/import-analysis.d.ts +43 -0
- package/dist/detect/ast-rules/helpers/import-analysis.d.ts.map +1 -0
- package/dist/detect/ast-rules/helpers/import-analysis.js +149 -0
- package/dist/detect/ast-rules/helpers/import-analysis.js.map +1 -0
- package/dist/detect/ast-rules/helpers/index.d.ts +16 -0
- package/dist/detect/ast-rules/helpers/index.d.ts.map +1 -0
- package/dist/detect/ast-rules/helpers/index.js +112 -0
- package/dist/detect/ast-rules/helpers/index.js.map +1 -0
- package/dist/detect/ast-rules/helpers/python-helpers.d.ts +215 -0
- package/dist/detect/ast-rules/helpers/python-helpers.d.ts.map +1 -0
- package/dist/detect/ast-rules/helpers/python-helpers.js +935 -0
- package/dist/detect/ast-rules/helpers/python-helpers.js.map +1 -0
- package/dist/detect/ast-rules/helpers/scope-analysis.d.ts +50 -0
- package/dist/detect/ast-rules/helpers/scope-analysis.d.ts.map +1 -0
- package/dist/detect/ast-rules/helpers/scope-analysis.js +194 -0
- package/dist/detect/ast-rules/helpers/scope-analysis.js.map +1 -0
- package/dist/detect/ast-rules/helpers/string-analysis.d.ts +57 -0
- package/dist/detect/ast-rules/helpers/string-analysis.d.ts.map +1 -0
- package/dist/detect/ast-rules/helpers/string-analysis.js +184 -0
- package/dist/detect/ast-rules/helpers/string-analysis.js.map +1 -0
- package/dist/detect/ast-rules/helpers/type-extraction.d.ts +44 -0
- package/dist/detect/ast-rules/helpers/type-extraction.d.ts.map +1 -0
- package/dist/detect/ast-rules/helpers/type-extraction.js +125 -0
- package/dist/detect/ast-rules/helpers/type-extraction.js.map +1 -0
- package/dist/detect/ast-rules/helpers/user-input.d.ts +35 -0
- package/dist/detect/ast-rules/helpers/user-input.d.ts.map +1 -0
- package/dist/detect/ast-rules/helpers/user-input.js +243 -0
- package/dist/detect/ast-rules/helpers/user-input.js.map +1 -0
- package/dist/detect/ast-rules/index.d.ts +112 -0
- package/dist/detect/ast-rules/index.d.ts.map +1 -0
- package/dist/detect/ast-rules/index.js +232 -0
- package/dist/detect/ast-rules/index.js.map +1 -0
- package/dist/detect/ast-rules/json-parse-ast.d.ts +13 -0
- package/dist/detect/ast-rules/json-parse-ast.d.ts.map +1 -0
- package/dist/detect/ast-rules/json-parse-ast.js +143 -0
- package/dist/detect/ast-rules/json-parse-ast.js.map +1 -0
- package/dist/detect/ast-rules/log-injection-ast.d.ts +14 -0
- package/dist/detect/ast-rules/log-injection-ast.d.ts.map +1 -0
- package/dist/detect/ast-rules/log-injection-ast.js +235 -0
- package/dist/detect/ast-rules/log-injection-ast.js.map +1 -0
- package/dist/detect/ast-rules/logic-gates-ast.d.ts +14 -0
- package/dist/detect/ast-rules/logic-gates-ast.d.ts.map +1 -0
- package/dist/detect/ast-rules/logic-gates-ast.js +312 -0
- package/dist/detect/ast-rules/logic-gates-ast.js.map +1 -0
- package/dist/detect/ast-rules/mcp-security-ast.d.ts +14 -0
- package/dist/detect/ast-rules/mcp-security-ast.d.ts.map +1 -0
- package/dist/detect/ast-rules/mcp-security-ast.js +755 -0
- package/dist/detect/ast-rules/mcp-security-ast.js.map +1 -0
- package/dist/detect/ast-rules/model-supply-chain-ast.d.ts +13 -0
- package/dist/detect/ast-rules/model-supply-chain-ast.d.ts.map +1 -0
- package/dist/detect/ast-rules/model-supply-chain-ast.js +188 -0
- package/dist/detect/ast-rules/model-supply-chain-ast.js.map +1 -0
- package/dist/detect/ast-rules/package-hallucination-ast.d.ts +13 -0
- package/dist/detect/ast-rules/package-hallucination-ast.d.ts.map +1 -0
- package/dist/detect/ast-rules/package-hallucination-ast.js +607 -0
- package/dist/detect/ast-rules/package-hallucination-ast.js.map +1 -0
- package/dist/detect/ast-rules/prompt-hygiene-ast.d.ts +15 -0
- package/dist/detect/ast-rules/prompt-hygiene-ast.d.ts.map +1 -0
- package/dist/detect/ast-rules/prompt-hygiene-ast.js +332 -0
- package/dist/detect/ast-rules/prompt-hygiene-ast.js.map +1 -0
- package/dist/detect/ast-rules/rag-safety-ast.d.ts +18 -0
- package/dist/detect/ast-rules/rag-safety-ast.d.ts.map +1 -0
- package/dist/detect/ast-rules/rag-safety-ast.js +640 -0
- package/dist/detect/ast-rules/rag-safety-ast.js.map +1 -0
- package/dist/detect/ast-rules/request-validation-ast.d.ts +13 -0
- package/dist/detect/ast-rules/request-validation-ast.d.ts.map +1 -0
- package/dist/detect/ast-rules/request-validation-ast.js +116 -0
- package/dist/detect/ast-rules/request-validation-ast.js.map +1 -0
- package/dist/detect/ast-rules/risky-imports-ast.d.ts +14 -0
- package/dist/detect/ast-rules/risky-imports-ast.d.ts.map +1 -0
- package/dist/detect/ast-rules/risky-imports-ast.js +114 -0
- package/dist/detect/ast-rules/risky-imports-ast.js.map +1 -0
- package/dist/detect/ast-rules/schema-validation-ast.d.ts +14 -0
- package/dist/detect/ast-rules/schema-validation-ast.d.ts.map +1 -0
- package/dist/detect/ast-rules/schema-validation-ast.js +233 -0
- package/dist/detect/ast-rules/schema-validation-ast.js.map +1 -0
- package/dist/detect/ast-rules/secret-patterns-ast.d.ts +17 -0
- package/dist/detect/ast-rules/secret-patterns-ast.d.ts.map +1 -0
- package/dist/detect/ast-rules/secret-patterns-ast.js +199 -0
- package/dist/detect/ast-rules/secret-patterns-ast.js.map +1 -0
- package/dist/detect/ast-rules/security-headers-ast.d.ts +14 -0
- package/dist/detect/ast-rules/security-headers-ast.d.ts.map +1 -0
- package/dist/detect/ast-rules/security-headers-ast.js +187 -0
- package/dist/detect/ast-rules/security-headers-ast.js.map +1 -0
- package/dist/detect/ast-rules/sql-injection-ast.d.ts +17 -0
- package/dist/detect/ast-rules/sql-injection-ast.d.ts.map +1 -0
- package/dist/detect/ast-rules/sql-injection-ast.js +497 -0
- package/dist/detect/ast-rules/sql-injection-ast.js.map +1 -0
- package/dist/detect/ast-rules/ssrf-ast.d.ts +14 -0
- package/dist/detect/ast-rules/ssrf-ast.d.ts.map +1 -0
- package/dist/detect/ast-rules/ssrf-ast.js +573 -0
- package/dist/detect/ast-rules/ssrf-ast.js.map +1 -0
- package/dist/detect/ast-rules/taint-fix-templates.d.ts +18 -0
- package/dist/detect/ast-rules/taint-fix-templates.d.ts.map +1 -0
- package/dist/detect/ast-rules/taint-fix-templates.js +92 -0
- package/dist/detect/ast-rules/taint-fix-templates.js.map +1 -0
- package/dist/detect/ast-rules/taint-flow-ast.d.ts +24 -0
- package/dist/detect/ast-rules/taint-flow-ast.d.ts.map +1 -0
- package/dist/detect/ast-rules/taint-flow-ast.js +340 -0
- package/dist/detect/ast-rules/taint-flow-ast.js.map +1 -0
- package/dist/detect/ast-rules/variables-ast.d.ts +24 -0
- package/dist/detect/ast-rules/variables-ast.d.ts.map +1 -0
- package/dist/detect/ast-rules/variables-ast.js +362 -0
- package/dist/detect/ast-rules/variables-ast.js.map +1 -0
- package/dist/detect/ast-rules/weak-crypto-ast.d.ts +15 -0
- package/dist/detect/ast-rules/weak-crypto-ast.d.ts.map +1 -0
- package/dist/detect/ast-rules/weak-crypto-ast.js +406 -0
- package/dist/detect/ast-rules/weak-crypto-ast.js.map +1 -0
- package/dist/detect/ast-rules/xxe-ast.d.ts +13 -0
- package/dist/detect/ast-rules/xxe-ast.d.ts.map +1 -0
- package/dist/detect/ast-rules/xxe-ast.js +157 -0
- package/dist/detect/ast-rules/xxe-ast.js.map +1 -0
- package/dist/detect/config/agent-skill-injection.d.ts.map +1 -1
- package/dist/detect/config/agent-skill-injection.js +2 -24
- package/dist/detect/config/agent-skill-injection.js.map +1 -1
- package/dist/detect/config/index.d.ts +1 -0
- package/dist/detect/config/index.d.ts.map +1 -1
- package/dist/detect/config/index.js +3 -1
- package/dist/detect/config/index.js.map +1 -1
- package/dist/detect/config/osv-check.d.ts.map +1 -1
- package/dist/detect/config/osv-check.js +6 -1
- package/dist/detect/config/osv-check.js.map +1 -1
- package/dist/detect/config/package-check.d.ts.map +1 -1
- package/dist/detect/config/package-check.js +6 -1
- package/dist/detect/config/package-check.js.map +1 -1
- package/dist/detect/config/rules-file-backdoor.d.ts +36 -0
- package/dist/detect/config/rules-file-backdoor.d.ts.map +1 -0
- package/dist/detect/config/rules-file-backdoor.js +379 -0
- package/dist/detect/config/rules-file-backdoor.js.map +1 -0
- package/dist/detect/index.d.ts +43 -6
- package/dist/detect/index.d.ts.map +1 -1
- package/dist/detect/index.js +70 -7
- package/dist/detect/index.js.map +1 -1
- package/dist/detect/secrets/config-audit.d.ts.map +1 -1
- package/dist/detect/secrets/config-audit.js +36 -3
- package/dist/detect/secrets/config-audit.js.map +1 -1
- package/dist/detect/secrets/entropy.d.ts.map +1 -1
- package/dist/detect/secrets/entropy.js +180 -0
- package/dist/detect/secrets/entropy.js.map +1 -1
- package/dist/detect/secrets/index.d.ts +0 -2
- package/dist/detect/secrets/index.d.ts.map +1 -1
- package/dist/detect/secrets/index.js +7 -17
- package/dist/detect/secrets/index.js.map +1 -1
- package/dist/detect/structural/index.d.ts +15 -28
- package/dist/detect/structural/index.d.ts.map +1 -1
- package/dist/detect/structural/index.js +20 -497
- package/dist/detect/structural/index.js.map +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -1
- package/dist/index.js.map +1 -1
- package/dist/model/auth-helper-detector.d.ts.map +1 -1
- package/dist/model/auth-helper-detector.js +2 -7
- package/dist/model/auth-helper-detector.js.map +1 -1
- package/dist/model/import-resolver.d.ts.map +1 -1
- package/dist/model/import-resolver.js +94 -0
- package/dist/model/import-resolver.js.map +1 -1
- package/dist/model/imported-auth-detector.js +8 -8
- package/dist/model/imported-auth-detector.js.map +1 -1
- package/dist/model/index.d.ts +8 -0
- package/dist/model/index.d.ts.map +1 -1
- package/dist/model/index.js +198 -73
- package/dist/model/index.js.map +1 -1
- package/dist/model/module-graph.d.ts.map +1 -1
- package/dist/model/module-graph.js +22 -9
- package/dist/model/module-graph.js.map +1 -1
- package/dist/model/project-context.d.ts +1 -1
- package/dist/model/project-context.d.ts.map +1 -1
- package/dist/model/project-context.js +34 -0
- package/dist/model/project-context.js.map +1 -1
- package/dist/model/route-auth-resolver.d.ts.map +1 -1
- package/dist/model/route-auth-resolver.js +17 -2
- package/dist/model/route-auth-resolver.js.map +1 -1
- package/dist/model/route-discovery/index.js +1 -1
- package/dist/model/route-discovery/index.js.map +1 -1
- package/dist/model/route-discovery/nextjs.js +1 -1
- package/dist/model/route-discovery/nextjs.js.map +1 -1
- package/dist/model/route-discovery/python.d.ts +6 -3
- package/dist/model/route-discovery/python.d.ts.map +1 -1
- package/dist/model/route-discovery/python.js +132 -9
- package/dist/model/route-discovery/python.js.map +1 -1
- package/dist/model/route-discovery/types.d.ts +1 -1
- package/dist/model/route-discovery/types.d.ts.map +1 -1
- package/dist/model/route-discovery/utils.d.ts +8 -0
- package/dist/model/route-discovery/utils.d.ts.map +1 -1
- package/dist/model/route-discovery/utils.js +70 -0
- package/dist/model/route-discovery/utils.js.map +1 -1
- package/dist/model/taint-types.d.ts +0 -4
- package/dist/model/taint-types.d.ts.map +1 -1
- package/dist/parse/ast.d.ts +58 -0
- package/dist/parse/ast.d.ts.map +1 -0
- package/dist/parse/ast.js +230 -0
- package/dist/parse/ast.js.map +1 -0
- package/dist/parse/call-graph.d.ts +41 -0
- package/dist/parse/call-graph.d.ts.map +1 -0
- package/dist/parse/call-graph.js +386 -0
- package/dist/parse/call-graph.js.map +1 -0
- package/dist/parse/file-classifier.d.ts +11 -0
- package/dist/parse/file-classifier.d.ts.map +1 -1
- package/dist/parse/file-classifier.js +63 -15
- package/dist/parse/file-classifier.js.map +1 -1
- package/dist/parse/node-index.d.ts +32 -0
- package/dist/parse/node-index.d.ts.map +1 -0
- package/dist/parse/node-index.js +103 -0
- package/dist/parse/node-index.js.map +1 -0
- package/dist/parse/type-extractor.d.ts +50 -0
- package/dist/parse/type-extractor.d.ts.map +1 -0
- package/dist/parse/type-extractor.js +243 -0
- package/dist/parse/type-extractor.js.map +1 -0
- package/dist/pipeline/config.d.ts +7 -1
- package/dist/pipeline/config.d.ts.map +1 -1
- package/dist/pipeline/config.js.map +1 -1
- package/dist/pipeline/index.d.ts +3 -3
- package/dist/pipeline/index.d.ts.map +1 -1
- package/dist/pipeline/index.js +192 -64
- package/dist/pipeline/index.js.map +1 -1
- package/dist/pipeline/modes/incremental.d.ts.map +1 -1
- package/dist/pipeline/modes/incremental.js +2 -7
- package/dist/pipeline/modes/incremental.js.map +1 -1
- package/dist/postprocess/dedup.d.ts +5 -2
- package/dist/postprocess/dedup.d.ts.map +1 -1
- package/dist/postprocess/dedup.js +47 -16
- package/dist/postprocess/dedup.js.map +1 -1
- package/dist/report/build-result.d.ts +9 -4
- package/dist/report/build-result.d.ts.map +1 -1
- package/dist/report/build-result.js +15 -4
- package/dist/report/build-result.js.map +1 -1
- package/dist/report/formatters/cli-terminal.d.ts +1 -1
- package/dist/report/formatters/cli-terminal.d.ts.map +1 -1
- package/dist/report/formatters/cli-terminal.js +434 -231
- package/dist/report/formatters/cli-terminal.js.map +1 -1
- package/dist/report/sanitize.d.ts +10 -0
- package/dist/report/sanitize.d.ts.map +1 -0
- package/dist/report/sanitize.js +19 -0
- package/dist/report/sanitize.js.map +1 -0
- package/dist/score/adjustments.d.ts +20 -2
- package/dist/score/adjustments.d.ts.map +1 -1
- package/dist/score/adjustments.js +108 -37
- package/dist/score/adjustments.js.map +1 -1
- package/dist/score/confidence.d.ts +6 -0
- package/dist/score/confidence.d.ts.map +1 -1
- package/dist/score/confidence.js +10 -4
- package/dist/score/confidence.js.map +1 -1
- package/dist/score/evidence.d.ts +25 -0
- package/dist/score/evidence.d.ts.map +1 -0
- package/dist/score/evidence.js +51 -0
- package/dist/score/evidence.js.map +1 -0
- package/dist/score/index.d.ts +3 -1
- package/dist/score/index.d.ts.map +1 -1
- package/dist/score/index.js +25 -50
- package/dist/score/index.js.map +1 -1
- package/dist/score/types.d.ts +5 -1
- package/dist/score/types.d.ts.map +1 -1
- package/dist/shared/category-filter.d.ts.map +1 -1
- package/dist/shared/category-filter.js +12 -0
- package/dist/shared/category-filter.js.map +1 -1
- package/dist/shared/regex-utils.d.ts +3 -0
- package/dist/shared/regex-utils.d.ts.map +1 -0
- package/dist/shared/regex-utils.js +8 -0
- package/dist/shared/regex-utils.js.map +1 -0
- package/dist/shared/registry-clients.d.ts +7 -0
- package/dist/shared/registry-clients.d.ts.map +1 -1
- package/dist/shared/registry-clients.js +94 -17
- package/dist/shared/registry-clients.js.map +1 -1
- package/dist/shared/rules/metadata.d.ts.map +1 -1
- package/dist/shared/rules/metadata.js +17 -0
- package/dist/shared/rules/metadata.js.map +1 -1
- package/dist/shared/types.d.ts +59 -15
- package/dist/shared/types.d.ts.map +1 -1
- package/dist/shared/types.js +38 -21
- package/dist/shared/types.js.map +1 -1
- package/dist/taint/async-flow.d.ts +44 -0
- package/dist/taint/async-flow.d.ts.map +1 -0
- package/dist/taint/async-flow.js +271 -0
- package/dist/taint/async-flow.js.map +1 -0
- package/dist/taint/cfg-builder.d.ts +35 -0
- package/dist/taint/cfg-builder.d.ts.map +1 -0
- package/dist/taint/cfg-builder.js +980 -0
- package/dist/taint/cfg-builder.js.map +1 -0
- package/dist/taint/cfg-types.d.ts +76 -0
- package/dist/taint/cfg-types.d.ts.map +1 -0
- package/dist/taint/cfg-types.js +13 -0
- package/dist/taint/cfg-types.js.map +1 -0
- package/dist/taint/constant-propagation.d.ts +34 -0
- package/dist/taint/constant-propagation.d.ts.map +1 -0
- package/dist/taint/constant-propagation.js +164 -0
- package/dist/taint/constant-propagation.js.map +1 -0
- package/dist/taint/cross-file-analyzer.d.ts +27 -0
- package/dist/taint/cross-file-analyzer.d.ts.map +1 -0
- package/dist/taint/cross-file-analyzer.js +99 -0
- package/dist/taint/cross-file-analyzer.js.map +1 -0
- package/dist/taint/cross-file-index.d.ts +59 -0
- package/dist/taint/cross-file-index.d.ts.map +1 -0
- package/dist/taint/cross-file-index.js +183 -0
- package/dist/taint/cross-file-index.js.map +1 -0
- package/dist/taint/def-use.d.ts +27 -0
- package/dist/taint/def-use.d.ts.map +1 -0
- package/dist/taint/def-use.js +519 -0
- package/dist/taint/def-use.js.map +1 -0
- package/dist/taint/file-analysis-cache.d.ts +47 -0
- package/dist/taint/file-analysis-cache.d.ts.map +1 -0
- package/dist/taint/file-analysis-cache.js +107 -0
- package/dist/taint/file-analysis-cache.js.map +1 -0
- package/dist/taint/framework-models.d.ts +77 -0
- package/dist/taint/framework-models.d.ts.map +1 -0
- package/dist/taint/framework-models.js +258 -0
- package/dist/taint/framework-models.js.map +1 -0
- package/dist/taint/helpers.d.ts +31 -0
- package/dist/taint/helpers.d.ts.map +1 -0
- package/dist/taint/helpers.js +130 -0
- package/dist/taint/helpers.js.map +1 -0
- package/dist/taint/index.d.ts +28 -0
- package/dist/taint/index.d.ts.map +1 -0
- package/dist/taint/index.js +77 -0
- package/dist/taint/index.js.map +1 -0
- package/dist/taint/llm-registry.d.ts +47 -0
- package/dist/taint/llm-registry.d.ts.map +1 -0
- package/dist/taint/llm-registry.js +152 -0
- package/dist/taint/llm-registry.js.map +1 -0
- package/dist/taint/llm-risk-scoring.d.ts +54 -0
- package/dist/taint/llm-risk-scoring.d.ts.map +1 -0
- package/dist/taint/llm-risk-scoring.js +376 -0
- package/dist/taint/llm-risk-scoring.js.map +1 -0
- package/dist/taint/propagation-types.d.ts +104 -0
- package/dist/taint/propagation-types.d.ts.map +1 -0
- package/dist/taint/propagation-types.js +98 -0
- package/dist/taint/propagation-types.js.map +1 -0
- package/dist/taint/propagation.d.ts +111 -0
- package/dist/taint/propagation.d.ts.map +1 -0
- package/dist/taint/propagation.js +1576 -0
- package/dist/taint/propagation.js.map +1 -0
- package/dist/taint/sanitizer-registry.d.ts +26 -0
- package/dist/taint/sanitizer-registry.d.ts.map +1 -0
- package/dist/taint/sanitizer-registry.js +422 -0
- package/dist/taint/sanitizer-registry.js.map +1 -0
- package/dist/taint/sink-classifier.d.ts +27 -0
- package/dist/taint/sink-classifier.d.ts.map +1 -0
- package/dist/taint/sink-classifier.js +1166 -0
- package/dist/taint/sink-classifier.js.map +1 -0
- package/dist/taint/source-classifier.d.ts +29 -0
- package/dist/taint/source-classifier.d.ts.map +1 -0
- package/dist/taint/source-classifier.js +814 -0
- package/dist/taint/source-classifier.js.map +1 -0
- package/dist/taint/taint-analyzer.d.ts +33 -0
- package/dist/taint/taint-analyzer.d.ts.map +1 -0
- package/dist/taint/taint-analyzer.js +88 -0
- package/dist/taint/taint-analyzer.js.map +1 -0
- package/dist/taint/taint-summary.d.ts +37 -0
- package/dist/taint/taint-summary.d.ts.map +1 -0
- package/dist/taint/taint-summary.js +293 -0
- package/dist/taint/taint-summary.js.map +1 -0
- package/dist/taint/types.d.ts +47 -0
- package/dist/taint/types.d.ts.map +1 -0
- package/dist/taint/types.js +19 -0
- package/dist/taint/types.js.map +1 -0
- package/dist/validate/clients.d.ts +2 -1
- package/dist/validate/clients.d.ts.map +1 -1
- package/dist/validate/clients.js +3 -2
- package/dist/validate/clients.js.map +1 -1
- package/dist/validate/index.d.ts +5 -6
- package/dist/validate/index.d.ts.map +1 -1
- package/dist/validate/index.js +22 -21
- package/dist/validate/index.js.map +1 -1
- package/dist/validate/prompts/modules/ai-patterns.d.ts +1 -1
- package/dist/validate/prompts/modules/ai-patterns.d.ts.map +1 -1
- package/dist/validate/prompts/modules/ai-patterns.js +16 -0
- package/dist/validate/prompts/modules/ai-patterns.js.map +1 -1
- package/dist/validate/prompts/modules/common.d.ts +1 -1
- package/dist/validate/prompts/modules/common.d.ts.map +1 -1
- package/dist/validate/prompts/modules/common.js +12 -3
- package/dist/validate/prompts/modules/common.js.map +1 -1
- package/dist/validate/providers/anthropic.d.ts +4 -4
- package/dist/validate/providers/anthropic.d.ts.map +1 -1
- package/dist/validate/providers/anthropic.js +85 -58
- package/dist/validate/providers/anthropic.js.map +1 -1
- package/dist/validate/providers/openai.d.ts +4 -4
- package/dist/validate/providers/openai.d.ts.map +1 -1
- package/dist/validate/providers/openai.js +149 -99
- package/dist/validate/providers/openai.js.map +1 -1
- package/dist/validate/request-builder.d.ts +2 -8
- package/dist/validate/request-builder.d.ts.map +1 -1
- package/dist/validate/request-builder.js +4 -34
- package/dist/validate/request-builder.js.map +1 -1
- package/dist/validate/types.d.ts +9 -0
- package/dist/validate/types.d.ts.map +1 -1
- package/dist/validate/types.js.map +1 -1
- package/dist/validate/utils/path-helpers.js +2 -2
- package/dist/validate/utils/path-helpers.js.map +1 -1
- package/dist/validate/utils/response-parser.d.ts +10 -0
- package/dist/validate/utils/response-parser.d.ts.map +1 -1
- package/dist/validate/utils/response-parser.js +21 -2
- package/dist/validate/utils/response-parser.js.map +1 -1
- package/dist/validate/utils/retry.d.ts.map +1 -1
- package/dist/validate/utils/retry.js +19 -4
- package/dist/validate/utils/retry.js.map +1 -1
- package/package.json +7 -4
- package/src/__tests__/benchmark/fixtures/layer2/ai-execution-sinks.ts +1 -1
- package/src/__tests__/benchmark/planted-benchmark.test.ts +337 -0
- package/src/__tests__/benchmark/utils/test-runner.ts +38 -4
- package/src/__tests__/category-filter.test.ts +5 -1
- package/src/__tests__/context-engine/route-discovery/python.test.ts +726 -0
- package/src/__tests__/detect/ast-rules.test.ts +1043 -0
- package/src/__tests__/detect/offline-mode.test.ts +147 -0
- package/src/__tests__/detect/python-ast-rules.test.ts +569 -0
- package/src/__tests__/detect/python-helpers.test.ts +536 -0
- package/src/__tests__/detect/python-sast-rules.test.ts +453 -0
- package/src/__tests__/detect/rules-file-backdoor-decoders.test.ts +151 -0
- package/src/__tests__/detect/rules-file-backdoor.test.ts +284 -0
- package/src/__tests__/detect/taint-fix-templates.test.ts +150 -0
- package/src/__tests__/detect/taint-path-serialization.test.ts +170 -0
- package/src/__tests__/parse/call-graph.test.ts +300 -0
- package/src/__tests__/parse/python-parser.test.ts +274 -0
- package/src/__tests__/regression/known-false-positives.test.ts +491 -9
- package/src/__tests__/regression/rules-file-backdoor.test.ts +137 -0
- package/src/__tests__/score/adjustments.test.ts +34 -16
- package/src/__tests__/score/confidence.test.ts +84 -57
- package/src/__tests__/score/evidence-scoring.test.ts +249 -0
- package/src/__tests__/score/evidence.test.ts +144 -0
- package/src/__tests__/score/scoring-integration.test.ts +56 -34
- package/src/__tests__/score/taint-adjustments.test.ts +14 -228
- package/src/__tests__/snapshots/__snapshots__/scan-depth.test.ts.snap +65 -59
- package/src/__tests__/snapshots/scan-depth.test.ts +39 -7
- package/src/__tests__/taint/async-flow.test.ts +247 -0
- package/src/__tests__/taint/cfg-builder.test.ts +835 -0
- package/src/__tests__/taint/constant-propagation.test.ts +302 -0
- package/src/__tests__/taint/cross-file-index.test.ts +683 -0
- package/src/__tests__/taint/cross-file-integration.test.ts +275 -0
- package/src/__tests__/taint/cross-file-propagation.test.ts +910 -0
- package/src/__tests__/taint/def-use.test.ts +132 -0
- package/src/__tests__/taint/field-sensitive-sinks.test.ts +179 -0
- package/src/__tests__/taint/field-sensitivity.test.ts +342 -0
- package/src/__tests__/taint/file-analysis-cache.test.ts +290 -0
- package/src/__tests__/taint/framework-models.test.ts +227 -0
- package/src/__tests__/taint/llm-flow-graph.test.ts +850 -0
- package/src/__tests__/taint/llm-risk-scoring.test.ts +439 -0
- package/src/__tests__/taint/performance-parity.test.ts +315 -0
- package/src/__tests__/taint/propagation.test.ts +621 -0
- package/src/__tests__/taint/python-cross-file.test.ts +494 -0
- package/src/__tests__/taint/python-taint.test.ts +1344 -0
- package/src/__tests__/taint/sanitizer-registry.test.ts +304 -0
- package/src/__tests__/taint/sanitizer-regression.test.ts +111 -0
- package/src/__tests__/taint/sink-classifier.test.ts +537 -0
- package/src/__tests__/taint/source-classifier.test.ts +367 -0
- package/src/__tests__/taint/taint-pipeline.test.ts +418 -0
- package/src/__tests__/taint/taint-smoke.test.ts +400 -0
- package/src/__tests__/taint/taint-summary.test.ts +472 -0
- package/src/detect/ai-code/index.ts +6 -11
- package/src/detect/ast-rules/agent-tools-ast.ts +861 -0
- package/src/detect/ast-rules/ai-fingerprinting-ast.ts +451 -0
- package/src/detect/ast-rules/auth-patterns-ast.ts +304 -0
- package/src/detect/ast-rules/byok-ast.ts +195 -0
- package/src/detect/ast-rules/child-process-ast.ts +276 -0
- package/src/detect/ast-rules/dangerous-eval-ast.ts +227 -0
- package/src/detect/ast-rules/data-exposure-ast.ts +162 -0
- package/src/detect/ast-rules/dom-xss-ast.ts +260 -0
- package/src/detect/ast-rules/endpoint-protection-ast.ts +231 -0
- package/src/detect/ast-rules/entropy-ast.ts +268 -0
- package/src/detect/ast-rules/flask-debug-ast.ts +148 -0
- package/src/detect/ast-rules/framework-checks-ast.ts +200 -0
- package/src/detect/ast-rules/helpers/call-analysis.ts +256 -0
- package/src/detect/ast-rules/helpers/context-detection.ts +277 -0
- package/src/detect/ast-rules/helpers/control-flow.ts +179 -0
- package/src/detect/ast-rules/helpers/import-analysis.ts +185 -0
- package/src/detect/ast-rules/helpers/index.ts +133 -0
- package/src/detect/ast-rules/helpers/python-helpers.ts +1054 -0
- package/src/detect/ast-rules/helpers/scope-analysis.ts +224 -0
- package/src/detect/ast-rules/helpers/string-analysis.ts +215 -0
- package/src/detect/ast-rules/helpers/type-extraction.ts +138 -0
- package/src/detect/ast-rules/helpers/user-input.ts +256 -0
- package/src/detect/ast-rules/index.ts +311 -0
- package/src/detect/ast-rules/json-parse-ast.ts +162 -0
- package/src/detect/ast-rules/log-injection-ast.ts +243 -0
- package/src/detect/ast-rules/logic-gates-ast.ts +343 -0
- package/src/detect/ast-rules/mcp-security-ast.ts +808 -0
- package/src/detect/ast-rules/model-supply-chain-ast.ts +202 -0
- package/src/detect/ast-rules/package-hallucination-ast.ts +664 -0
- package/src/detect/ast-rules/prompt-hygiene-ast.ts +329 -0
- package/src/detect/ast-rules/rag-safety-ast.ts +689 -0
- package/src/detect/ast-rules/request-validation-ast.ts +122 -0
- package/src/detect/ast-rules/risky-imports-ast.ts +133 -0
- package/src/detect/ast-rules/schema-validation-ast.ts +244 -0
- package/src/detect/ast-rules/secret-patterns-ast.ts +223 -0
- package/src/detect/ast-rules/security-headers-ast.ts +206 -0
- package/src/detect/ast-rules/sql-injection-ast.ts +614 -0
- package/src/detect/ast-rules/ssrf-ast.ts +601 -0
- package/src/detect/ast-rules/taint-fix-templates.ts +108 -0
- package/src/detect/ast-rules/taint-flow-ast.ts +416 -0
- package/src/detect/ast-rules/variables-ast.ts +446 -0
- package/src/detect/ast-rules/weak-crypto-ast.ts +441 -0
- package/src/detect/ast-rules/xxe-ast.ts +184 -0
- package/src/detect/config/agent-skill-injection.ts +2 -24
- package/src/detect/config/index.ts +1 -0
- package/src/detect/config/osv-check.ts +6 -1
- package/src/detect/config/package-check.ts +6 -1
- package/src/detect/config/rules-file-backdoor.ts +438 -0
- package/src/detect/index.ts +146 -52
- package/src/detect/secrets/config-audit.ts +37 -3
- package/src/detect/secrets/entropy.ts +195 -0
- package/src/detect/secrets/index.ts +7 -16
- package/src/detect/structural/index.ts +23 -566
- package/src/index.ts +7 -0
- package/src/model/auth-helper-detector.ts +1 -7
- package/src/model/import-resolver.ts +104 -0
- package/src/model/imported-auth-detector.ts +1 -1
- package/src/model/index.ts +240 -80
- package/src/model/module-graph.ts +17 -5
- package/src/model/project-context.ts +28 -1
- package/src/model/route-auth-resolver.ts +18 -3
- package/src/model/route-discovery/index.ts +1 -1
- package/src/model/route-discovery/nextjs.ts +1 -1
- package/src/model/route-discovery/python.ts +156 -9
- package/src/model/route-discovery/types.ts +1 -1
- package/src/model/route-discovery/utils.ts +73 -0
- package/src/model/taint-types.ts +1 -6
- package/src/parse/ast.ts +271 -0
- package/src/parse/call-graph.ts +419 -0
- package/src/parse/file-classifier.ts +69 -15
- package/src/parse/node-index.ts +118 -0
- package/src/parse/type-extractor.ts +293 -0
- package/src/pipeline/config.ts +7 -0
- package/src/pipeline/index.ts +464 -199
- package/src/pipeline/modes/incremental.ts +1 -7
- package/src/postprocess/dedup.ts +48 -17
- package/src/report/build-result.ts +57 -29
- package/src/report/formatters/cli-terminal.ts +731 -415
- package/src/report/sanitize.ts +27 -0
- package/src/score/adjustments.ts +113 -40
- package/src/score/confidence.ts +10 -5
- package/src/score/evidence.ts +55 -0
- package/src/score/index.ts +27 -55
- package/src/score/types.ts +4 -0
- package/src/shared/category-filter.ts +12 -0
- package/src/shared/regex-utils.ts +4 -0
- package/src/shared/registry-clients.ts +106 -18
- package/src/shared/rules/__tests__/metadata.test.ts +5 -1
- package/src/shared/rules/metadata.ts +19 -0
- package/src/shared/types.ts +372 -253
- package/src/taint/async-flow.ts +301 -0
- package/src/taint/cfg-builder.ts +1127 -0
- package/src/taint/cfg-types.ts +110 -0
- package/src/taint/constant-propagation.ts +170 -0
- package/src/taint/cross-file-analyzer.ts +118 -0
- package/src/taint/cross-file-index.ts +275 -0
- package/src/taint/def-use.ts +556 -0
- package/src/taint/file-analysis-cache.ts +145 -0
- package/src/taint/framework-models.ts +313 -0
- package/src/taint/helpers.ts +138 -0
- package/src/taint/index.ts +71 -0
- package/src/taint/llm-registry.ts +174 -0
- package/src/taint/llm-risk-scoring.ts +412 -0
- package/src/taint/propagation-types.ts +188 -0
- package/src/taint/propagation.ts +1750 -0
- package/src/taint/sanitizer-registry.ts +490 -0
- package/src/taint/sink-classifier.ts +1402 -0
- package/src/taint/source-classifier.ts +859 -0
- package/src/taint/taint-analyzer.ts +112 -0
- package/src/taint/taint-summary.ts +341 -0
- package/src/taint/types.ts +86 -0
- package/src/validate/clients.ts +3 -2
- package/src/validate/index.ts +89 -53
- package/src/validate/prompts/modules/ai-patterns.ts +16 -0
- package/src/validate/prompts/modules/common.ts +12 -3
- package/src/validate/providers/anthropic.ts +254 -148
- package/src/validate/providers/openai.ts +363 -218
- package/src/validate/request-builder.ts +2 -45
- package/src/validate/types.ts +9 -0
- package/src/validate/utils/path-helpers.ts +2 -2
- package/src/validate/utils/response-parser.ts +32 -3
- package/src/validate/utils/retry.ts +19 -4
- package/dist/ai-context/index.d.ts +0 -6
- package/dist/ai-context/index.d.ts.map +0 -1
- package/dist/ai-context/index.js +0 -13
- package/dist/ai-context/index.js.map +0 -1
- package/dist/ai-context/manager.d.ts +0 -67
- package/dist/ai-context/manager.d.ts.map +0 -1
- package/dist/ai-context/manager.js +0 -104
- package/dist/ai-context/manager.js.map +0 -1
- package/dist/baseline/diff.d.ts +0 -32
- package/dist/baseline/diff.d.ts.map +0 -1
- package/dist/baseline/diff.js +0 -119
- package/dist/baseline/diff.js.map +0 -1
- package/dist/baseline/index.d.ts +0 -9
- package/dist/baseline/index.d.ts.map +0 -1
- package/dist/baseline/index.js +0 -19
- package/dist/baseline/index.js.map +0 -1
- package/dist/baseline/manager.d.ts +0 -67
- package/dist/baseline/manager.d.ts.map +0 -1
- package/dist/baseline/manager.js +0 -180
- package/dist/baseline/manager.js.map +0 -1
- package/dist/baseline/types.d.ts +0 -91
- package/dist/baseline/types.d.ts.map +0 -1
- package/dist/baseline/types.js +0 -12
- package/dist/baseline/types.js.map +0 -1
- package/dist/category-filter.d.ts +0 -125
- package/dist/category-filter.d.ts.map +0 -1
- package/dist/category-filter.js +0 -360
- package/dist/category-filter.js.map +0 -1
- package/dist/detect/ai-code/agent-tools.d.ts +0 -22
- package/dist/detect/ai-code/agent-tools.d.ts.map +0 -1
- package/dist/detect/ai-code/agent-tools.js +0 -1509
- package/dist/detect/ai-code/agent-tools.js.map +0 -1
- package/dist/detect/ai-code/byok-patterns.d.ts +0 -15
- package/dist/detect/ai-code/byok-patterns.d.ts.map +0 -1
- package/dist/detect/ai-code/byok-patterns.js +0 -313
- package/dist/detect/ai-code/byok-patterns.js.map +0 -1
- package/dist/detect/ai-code/endpoint-protection.d.ts +0 -38
- package/dist/detect/ai-code/endpoint-protection.d.ts.map +0 -1
- package/dist/detect/ai-code/endpoint-protection.js +0 -349
- package/dist/detect/ai-code/endpoint-protection.js.map +0 -1
- package/dist/detect/ai-code/execution-sinks.d.ts +0 -21
- package/dist/detect/ai-code/execution-sinks.d.ts.map +0 -1
- package/dist/detect/ai-code/execution-sinks.js +0 -1158
- package/dist/detect/ai-code/execution-sinks.js.map +0 -1
- package/dist/detect/ai-code/fingerprinting.d.ts +0 -10
- package/dist/detect/ai-code/fingerprinting.d.ts.map +0 -1
- package/dist/detect/ai-code/fingerprinting.js +0 -665
- package/dist/detect/ai-code/fingerprinting.js.map +0 -1
- package/dist/detect/ai-code/mcp-security.d.ts +0 -20
- package/dist/detect/ai-code/mcp-security.d.ts.map +0 -1
- package/dist/detect/ai-code/mcp-security.js +0 -880
- package/dist/detect/ai-code/mcp-security.js.map +0 -1
- package/dist/detect/ai-code/model-supply-chain.d.ts +0 -23
- package/dist/detect/ai-code/model-supply-chain.d.ts.map +0 -1
- package/dist/detect/ai-code/model-supply-chain.js +0 -447
- package/dist/detect/ai-code/model-supply-chain.js.map +0 -1
- package/dist/detect/ai-code/package-hallucination.d.ts +0 -22
- package/dist/detect/ai-code/package-hallucination.d.ts.map +0 -1
- package/dist/detect/ai-code/package-hallucination.js +0 -841
- package/dist/detect/ai-code/package-hallucination.js.map +0 -1
- package/dist/detect/ai-code/prompt-hygiene.d.ts +0 -22
- package/dist/detect/ai-code/prompt-hygiene.d.ts.map +0 -1
- package/dist/detect/ai-code/prompt-hygiene.js +0 -1177
- package/dist/detect/ai-code/prompt-hygiene.js.map +0 -1
- package/dist/detect/ai-code/rag-safety.d.ts +0 -24
- package/dist/detect/ai-code/rag-safety.d.ts.map +0 -1
- package/dist/detect/ai-code/rag-safety.js +0 -913
- package/dist/detect/ai-code/rag-safety.js.map +0 -1
- package/dist/detect/ai-code/schema-validation.d.ts +0 -28
- package/dist/detect/ai-code/schema-validation.d.ts.map +0 -1
- package/dist/detect/ai-code/schema-validation.js +0 -378
- package/dist/detect/ai-code/schema-validation.js.map +0 -1
- package/dist/detect/secrets/patterns.d.ts +0 -11
- package/dist/detect/secrets/patterns.d.ts.map +0 -1
- package/dist/detect/secrets/patterns.js +0 -518
- package/dist/detect/secrets/patterns.js.map +0 -1
- package/dist/detect/secrets/weak-crypto.d.ts +0 -10
- package/dist/detect/secrets/weak-crypto.d.ts.map +0 -1
- package/dist/detect/secrets/weak-crypto.js +0 -432
- package/dist/detect/secrets/weak-crypto.js.map +0 -1
- package/dist/detect/structural/auth-patterns.d.ts +0 -22
- package/dist/detect/structural/auth-patterns.d.ts.map +0 -1
- package/dist/detect/structural/auth-patterns.js +0 -533
- package/dist/detect/structural/auth-patterns.js.map +0 -1
- package/dist/detect/structural/dangerous-functions/child-process.d.ts +0 -16
- package/dist/detect/structural/dangerous-functions/child-process.d.ts.map +0 -1
- package/dist/detect/structural/dangerous-functions/child-process.js +0 -74
- package/dist/detect/structural/dangerous-functions/child-process.js.map +0 -1
- package/dist/detect/structural/dangerous-functions/dom-xss.d.ts +0 -34
- package/dist/detect/structural/dangerous-functions/dom-xss.d.ts.map +0 -1
- package/dist/detect/structural/dangerous-functions/dom-xss.js +0 -230
- package/dist/detect/structural/dangerous-functions/dom-xss.js.map +0 -1
- package/dist/detect/structural/dangerous-functions/index.d.ts +0 -16
- package/dist/detect/structural/dangerous-functions/index.d.ts.map +0 -1
- package/dist/detect/structural/dangerous-functions/index.js +0 -1193
- package/dist/detect/structural/dangerous-functions/index.js.map +0 -1
- package/dist/detect/structural/dangerous-functions/json-parse.d.ts +0 -31
- package/dist/detect/structural/dangerous-functions/json-parse.d.ts.map +0 -1
- package/dist/detect/structural/dangerous-functions/json-parse.js +0 -326
- package/dist/detect/structural/dangerous-functions/json-parse.js.map +0 -1
- package/dist/detect/structural/dangerous-functions/math-random.d.ts +0 -111
- package/dist/detect/structural/dangerous-functions/math-random.d.ts.map +0 -1
- package/dist/detect/structural/dangerous-functions/math-random.js +0 -684
- package/dist/detect/structural/dangerous-functions/math-random.js.map +0 -1
- package/dist/detect/structural/dangerous-functions/patterns.d.ts +0 -21
- package/dist/detect/structural/dangerous-functions/patterns.d.ts.map +0 -1
- package/dist/detect/structural/dangerous-functions/patterns.js +0 -163
- package/dist/detect/structural/dangerous-functions/patterns.js.map +0 -1
- package/dist/detect/structural/dangerous-functions/request-validation.d.ts +0 -13
- package/dist/detect/structural/dangerous-functions/request-validation.d.ts.map +0 -1
- package/dist/detect/structural/dangerous-functions/request-validation.js +0 -126
- package/dist/detect/structural/dangerous-functions/request-validation.js.map +0 -1
- package/dist/detect/structural/dangerous-functions/utils/control-flow.d.ts +0 -24
- package/dist/detect/structural/dangerous-functions/utils/control-flow.d.ts.map +0 -1
- package/dist/detect/structural/dangerous-functions/utils/control-flow.js +0 -70
- package/dist/detect/structural/dangerous-functions/utils/control-flow.js.map +0 -1
- package/dist/detect/structural/dangerous-functions/utils/helpers.d.ts +0 -31
- package/dist/detect/structural/dangerous-functions/utils/helpers.d.ts.map +0 -1
- package/dist/detect/structural/dangerous-functions/utils/helpers.js +0 -147
- package/dist/detect/structural/dangerous-functions/utils/helpers.js.map +0 -1
- package/dist/detect/structural/dangerous-functions/utils/index.d.ts +0 -9
- package/dist/detect/structural/dangerous-functions/utils/index.d.ts.map +0 -1
- package/dist/detect/structural/dangerous-functions/utils/index.js +0 -23
- package/dist/detect/structural/dangerous-functions/utils/index.js.map +0 -1
- package/dist/detect/structural/dangerous-functions/utils/schema-validation.d.ts +0 -22
- package/dist/detect/structural/dangerous-functions/utils/schema-validation.d.ts.map +0 -1
- package/dist/detect/structural/dangerous-functions/utils/schema-validation.js +0 -102
- package/dist/detect/structural/dangerous-functions/utils/schema-validation.js.map +0 -1
- package/dist/detect/structural/data-exposure.d.ts +0 -19
- package/dist/detect/structural/data-exposure.d.ts.map +0 -1
- package/dist/detect/structural/data-exposure.js +0 -262
- package/dist/detect/structural/data-exposure.js.map +0 -1
- package/dist/detect/structural/framework-checks.d.ts +0 -10
- package/dist/detect/structural/framework-checks.d.ts.map +0 -1
- package/dist/detect/structural/framework-checks.js +0 -389
- package/dist/detect/structural/framework-checks.js.map +0 -1
- package/dist/detect/structural/log-injection.d.ts +0 -18
- package/dist/detect/structural/log-injection.d.ts.map +0 -1
- package/dist/detect/structural/log-injection.js +0 -217
- package/dist/detect/structural/log-injection.js.map +0 -1
- package/dist/detect/structural/logic-gates.d.ts +0 -10
- package/dist/detect/structural/logic-gates.d.ts.map +0 -1
- package/dist/detect/structural/logic-gates.js +0 -227
- package/dist/detect/structural/logic-gates.js.map +0 -1
- package/dist/detect/structural/risky-imports.d.ts +0 -10
- package/dist/detect/structural/risky-imports.d.ts.map +0 -1
- package/dist/detect/structural/risky-imports.js +0 -168
- package/dist/detect/structural/risky-imports.js.map +0 -1
- package/dist/detect/structural/security-headers.d.ts +0 -18
- package/dist/detect/structural/security-headers.d.ts.map +0 -1
- package/dist/detect/structural/security-headers.js +0 -196
- package/dist/detect/structural/security-headers.js.map +0 -1
- package/dist/detect/structural/ssrf-detection.d.ts +0 -18
- package/dist/detect/structural/ssrf-detection.d.ts.map +0 -1
- package/dist/detect/structural/ssrf-detection.js +0 -263
- package/dist/detect/structural/ssrf-detection.js.map +0 -1
- package/dist/detect/structural/variables.d.ts +0 -11
- package/dist/detect/structural/variables.d.ts.map +0 -1
- package/dist/detect/structural/variables.js +0 -159
- package/dist/detect/structural/variables.js.map +0 -1
- package/dist/detect/structural/xxe-detection.d.ts +0 -18
- package/dist/detect/structural/xxe-detection.d.ts.map +0 -1
- package/dist/detect/structural/xxe-detection.js +0 -245
- package/dist/detect/structural/xxe-detection.js.map +0 -1
- package/dist/filtering/context-adjustments.d.ts +0 -23
- package/dist/filtering/context-adjustments.d.ts.map +0 -1
- package/dist/filtering/context-adjustments.js +0 -100
- package/dist/filtering/context-adjustments.js.map +0 -1
- package/dist/filtering/index.d.ts +0 -3
- package/dist/filtering/index.d.ts.map +0 -1
- package/dist/filtering/index.js +0 -8
- package/dist/filtering/index.js.map +0 -1
- package/dist/filtering/pipeline.d.ts +0 -48
- package/dist/filtering/pipeline.d.ts.map +0 -1
- package/dist/filtering/pipeline.js +0 -76
- package/dist/filtering/pipeline.js.map +0 -1
- package/dist/formatters/ai-context.d.ts +0 -23
- package/dist/formatters/ai-context.d.ts.map +0 -1
- package/dist/formatters/ai-context.js +0 -238
- package/dist/formatters/ai-context.js.map +0 -1
- package/dist/formatters/cli-terminal.d.ts +0 -65
- package/dist/formatters/cli-terminal.d.ts.map +0 -1
- package/dist/formatters/cli-terminal.js +0 -735
- package/dist/formatters/cli-terminal.js.map +0 -1
- package/dist/formatters/github-comment.d.ts +0 -41
- package/dist/formatters/github-comment.d.ts.map +0 -1
- package/dist/formatters/github-comment.js +0 -370
- package/dist/formatters/github-comment.js.map +0 -1
- package/dist/formatters/grouping.d.ts +0 -52
- package/dist/formatters/grouping.d.ts.map +0 -1
- package/dist/formatters/grouping.js +0 -152
- package/dist/formatters/grouping.js.map +0 -1
- package/dist/formatters/ide/claude-code.d.ts +0 -17
- package/dist/formatters/ide/claude-code.d.ts.map +0 -1
- package/dist/formatters/ide/claude-code.js +0 -94
- package/dist/formatters/ide/claude-code.js.map +0 -1
- package/dist/formatters/ide/cursor.d.ts +0 -13
- package/dist/formatters/ide/cursor.d.ts.map +0 -1
- package/dist/formatters/ide/cursor.js +0 -125
- package/dist/formatters/ide/cursor.js.map +0 -1
- package/dist/formatters/ide/index.d.ts +0 -62
- package/dist/formatters/ide/index.d.ts.map +0 -1
- package/dist/formatters/ide/index.js +0 -184
- package/dist/formatters/ide/index.js.map +0 -1
- package/dist/formatters/ide/windsurf.d.ts +0 -13
- package/dist/formatters/ide/windsurf.d.ts.map +0 -1
- package/dist/formatters/ide/windsurf.js +0 -117
- package/dist/formatters/ide/windsurf.js.map +0 -1
- package/dist/formatters/index.d.ts +0 -11
- package/dist/formatters/index.d.ts.map +0 -1
- package/dist/formatters/index.js +0 -54
- package/dist/formatters/index.js.map +0 -1
- package/dist/formatters/vscode-diagnostic.d.ts +0 -103
- package/dist/formatters/vscode-diagnostic.d.ts.map +0 -1
- package/dist/formatters/vscode-diagnostic.js +0 -151
- package/dist/formatters/vscode-diagnostic.js.map +0 -1
- package/dist/layer1/comments.d.ts +0 -11
- package/dist/layer1/comments.d.ts.map +0 -1
- package/dist/layer1/comments.js +0 -203
- package/dist/layer1/comments.js.map +0 -1
- package/dist/layer1/config-audit.d.ts +0 -11
- package/dist/layer1/config-audit.d.ts.map +0 -1
- package/dist/layer1/config-audit.js +0 -311
- package/dist/layer1/config-audit.js.map +0 -1
- package/dist/layer1/config-mcp-audit.d.ts +0 -23
- package/dist/layer1/config-mcp-audit.d.ts.map +0 -1
- package/dist/layer1/config-mcp-audit.js +0 -239
- package/dist/layer1/config-mcp-audit.js.map +0 -1
- package/dist/layer1/entropy.d.ts +0 -11
- package/dist/layer1/entropy.d.ts.map +0 -1
- package/dist/layer1/entropy.js +0 -741
- package/dist/layer1/entropy.js.map +0 -1
- package/dist/layer1/file-flags.d.ts +0 -10
- package/dist/layer1/file-flags.d.ts.map +0 -1
- package/dist/layer1/file-flags.js +0 -119
- package/dist/layer1/file-flags.js.map +0 -1
- package/dist/layer1/index.d.ts +0 -38
- package/dist/layer1/index.d.ts.map +0 -1
- package/dist/layer1/index.js +0 -170
- package/dist/layer1/index.js.map +0 -1
- package/dist/layer1/patterns.d.ts +0 -11
- package/dist/layer1/patterns.d.ts.map +0 -1
- package/dist/layer1/patterns.js +0 -512
- package/dist/layer1/patterns.js.map +0 -1
- package/dist/layer1/urls.d.ts +0 -11
- package/dist/layer1/urls.d.ts.map +0 -1
- package/dist/layer1/urls.js +0 -444
- package/dist/layer1/urls.js.map +0 -1
- package/dist/layer1/weak-crypto.d.ts +0 -10
- package/dist/layer1/weak-crypto.d.ts.map +0 -1
- package/dist/layer1/weak-crypto.js +0 -428
- package/dist/layer1/weak-crypto.js.map +0 -1
- package/dist/layer2/ai-agent-tools.d.ts +0 -22
- package/dist/layer2/ai-agent-tools.d.ts.map +0 -1
- package/dist/layer2/ai-agent-tools.js +0 -1490
- package/dist/layer2/ai-agent-tools.js.map +0 -1
- package/dist/layer2/ai-endpoint-protection.d.ts +0 -38
- package/dist/layer2/ai-endpoint-protection.d.ts.map +0 -1
- package/dist/layer2/ai-endpoint-protection.js +0 -346
- package/dist/layer2/ai-endpoint-protection.js.map +0 -1
- package/dist/layer2/ai-execution-sinks.d.ts +0 -21
- package/dist/layer2/ai-execution-sinks.d.ts.map +0 -1
- package/dist/layer2/ai-execution-sinks.js +0 -1155
- package/dist/layer2/ai-execution-sinks.js.map +0 -1
- package/dist/layer2/ai-fingerprinting.d.ts +0 -10
- package/dist/layer2/ai-fingerprinting.d.ts.map +0 -1
- package/dist/layer2/ai-fingerprinting.js +0 -650
- package/dist/layer2/ai-fingerprinting.js.map +0 -1
- package/dist/layer2/ai-mcp-security.d.ts +0 -20
- package/dist/layer2/ai-mcp-security.d.ts.map +0 -1
- package/dist/layer2/ai-mcp-security.js +0 -877
- package/dist/layer2/ai-mcp-security.js.map +0 -1
- package/dist/layer2/ai-package-hallucination.d.ts +0 -22
- package/dist/layer2/ai-package-hallucination.d.ts.map +0 -1
- package/dist/layer2/ai-package-hallucination.js +0 -828
- package/dist/layer2/ai-package-hallucination.js.map +0 -1
- package/dist/layer2/ai-prompt-hygiene.d.ts +0 -22
- package/dist/layer2/ai-prompt-hygiene.d.ts.map +0 -1
- package/dist/layer2/ai-prompt-hygiene.js +0 -1156
- package/dist/layer2/ai-prompt-hygiene.js.map +0 -1
- package/dist/layer2/ai-rag-safety.d.ts +0 -24
- package/dist/layer2/ai-rag-safety.d.ts.map +0 -1
- package/dist/layer2/ai-rag-safety.js +0 -910
- package/dist/layer2/ai-rag-safety.js.map +0 -1
- package/dist/layer2/ai-schema-validation.d.ts +0 -28
- package/dist/layer2/ai-schema-validation.d.ts.map +0 -1
- package/dist/layer2/ai-schema-validation.js +0 -375
- package/dist/layer2/ai-schema-validation.js.map +0 -1
- package/dist/layer2/auth-antipatterns.d.ts +0 -22
- package/dist/layer2/auth-antipatterns.d.ts.map +0 -1
- package/dist/layer2/auth-antipatterns.js +0 -522
- package/dist/layer2/auth-antipatterns.js.map +0 -1
- package/dist/layer2/byok-patterns.d.ts +0 -15
- package/dist/layer2/byok-patterns.d.ts.map +0 -1
- package/dist/layer2/byok-patterns.js +0 -302
- package/dist/layer2/byok-patterns.js.map +0 -1
- package/dist/layer2/dangerous-functions/child-process.d.ts +0 -16
- package/dist/layer2/dangerous-functions/child-process.d.ts.map +0 -1
- package/dist/layer2/dangerous-functions/child-process.js +0 -74
- package/dist/layer2/dangerous-functions/child-process.js.map +0 -1
- package/dist/layer2/dangerous-functions/dom-xss.d.ts +0 -34
- package/dist/layer2/dangerous-functions/dom-xss.d.ts.map +0 -1
- package/dist/layer2/dangerous-functions/dom-xss.js +0 -230
- package/dist/layer2/dangerous-functions/dom-xss.js.map +0 -1
- package/dist/layer2/dangerous-functions/index.d.ts +0 -16
- package/dist/layer2/dangerous-functions/index.d.ts.map +0 -1
- package/dist/layer2/dangerous-functions/index.js +0 -1152
- package/dist/layer2/dangerous-functions/index.js.map +0 -1
- package/dist/layer2/dangerous-functions/json-parse.d.ts +0 -31
- package/dist/layer2/dangerous-functions/json-parse.d.ts.map +0 -1
- package/dist/layer2/dangerous-functions/json-parse.js +0 -319
- package/dist/layer2/dangerous-functions/json-parse.js.map +0 -1
- package/dist/layer2/dangerous-functions/math-random.d.ts +0 -111
- package/dist/layer2/dangerous-functions/math-random.d.ts.map +0 -1
- package/dist/layer2/dangerous-functions/math-random.js +0 -684
- package/dist/layer2/dangerous-functions/math-random.js.map +0 -1
- package/dist/layer2/dangerous-functions/patterns.d.ts +0 -21
- package/dist/layer2/dangerous-functions/patterns.d.ts.map +0 -1
- package/dist/layer2/dangerous-functions/patterns.js +0 -163
- package/dist/layer2/dangerous-functions/patterns.js.map +0 -1
- package/dist/layer2/dangerous-functions/request-validation.d.ts +0 -13
- package/dist/layer2/dangerous-functions/request-validation.d.ts.map +0 -1
- package/dist/layer2/dangerous-functions/request-validation.js +0 -119
- package/dist/layer2/dangerous-functions/request-validation.js.map +0 -1
- package/dist/layer2/dangerous-functions/utils/control-flow.d.ts +0 -24
- package/dist/layer2/dangerous-functions/utils/control-flow.d.ts.map +0 -1
- package/dist/layer2/dangerous-functions/utils/control-flow.js +0 -70
- package/dist/layer2/dangerous-functions/utils/control-flow.js.map +0 -1
- package/dist/layer2/dangerous-functions/utils/helpers.d.ts +0 -31
- package/dist/layer2/dangerous-functions/utils/helpers.d.ts.map +0 -1
- package/dist/layer2/dangerous-functions/utils/helpers.js +0 -147
- package/dist/layer2/dangerous-functions/utils/helpers.js.map +0 -1
- package/dist/layer2/dangerous-functions/utils/index.d.ts +0 -9
- package/dist/layer2/dangerous-functions/utils/index.d.ts.map +0 -1
- package/dist/layer2/dangerous-functions/utils/index.js +0 -23
- package/dist/layer2/dangerous-functions/utils/index.js.map +0 -1
- package/dist/layer2/dangerous-functions/utils/schema-validation.d.ts +0 -22
- package/dist/layer2/dangerous-functions/utils/schema-validation.d.ts.map +0 -1
- package/dist/layer2/dangerous-functions/utils/schema-validation.js +0 -102
- package/dist/layer2/dangerous-functions/utils/schema-validation.js.map +0 -1
- package/dist/layer2/data-exposure.d.ts +0 -19
- package/dist/layer2/data-exposure.d.ts.map +0 -1
- package/dist/layer2/data-exposure.js +0 -255
- package/dist/layer2/data-exposure.js.map +0 -1
- package/dist/layer2/framework-checks.d.ts +0 -10
- package/dist/layer2/framework-checks.d.ts.map +0 -1
- package/dist/layer2/framework-checks.js +0 -384
- package/dist/layer2/framework-checks.js.map +0 -1
- package/dist/layer2/index.d.ts +0 -74
- package/dist/layer2/index.d.ts.map +0 -1
- package/dist/layer2/index.js +0 -544
- package/dist/layer2/index.js.map +0 -1
- package/dist/layer2/log-injection.d.ts +0 -18
- package/dist/layer2/log-injection.d.ts.map +0 -1
- package/dist/layer2/log-injection.js +0 -214
- package/dist/layer2/log-injection.js.map +0 -1
- package/dist/layer2/logic-gates.d.ts +0 -10
- package/dist/layer2/logic-gates.d.ts.map +0 -1
- package/dist/layer2/logic-gates.js +0 -220
- package/dist/layer2/logic-gates.js.map +0 -1
- package/dist/layer2/model-supply-chain.d.ts +0 -23
- package/dist/layer2/model-supply-chain.d.ts.map +0 -1
- package/dist/layer2/model-supply-chain.js +0 -444
- package/dist/layer2/model-supply-chain.js.map +0 -1
- package/dist/layer2/risky-imports.d.ts +0 -10
- package/dist/layer2/risky-imports.d.ts.map +0 -1
- package/dist/layer2/risky-imports.js +0 -165
- package/dist/layer2/risky-imports.js.map +0 -1
- package/dist/layer2/security-headers.d.ts +0 -18
- package/dist/layer2/security-headers.d.ts.map +0 -1
- package/dist/layer2/security-headers.js +0 -187
- package/dist/layer2/security-headers.js.map +0 -1
- package/dist/layer2/ssrf-detection.d.ts +0 -18
- package/dist/layer2/ssrf-detection.d.ts.map +0 -1
- package/dist/layer2/ssrf-detection.js +0 -252
- package/dist/layer2/ssrf-detection.js.map +0 -1
- package/dist/layer2/variables.d.ts +0 -11
- package/dist/layer2/variables.d.ts.map +0 -1
- package/dist/layer2/variables.js +0 -156
- package/dist/layer2/variables.js.map +0 -1
- package/dist/layer2/xxe-detection.d.ts +0 -18
- package/dist/layer2/xxe-detection.d.ts.map +0 -1
- package/dist/layer2/xxe-detection.js +0 -242
- package/dist/layer2/xxe-detection.js.map +0 -1
- package/dist/layer3/anthropic/auto-dismiss.d.ts +0 -24
- package/dist/layer3/anthropic/auto-dismiss.d.ts.map +0 -1
- package/dist/layer3/anthropic/auto-dismiss.js +0 -199
- package/dist/layer3/anthropic/auto-dismiss.js.map +0 -1
- package/dist/layer3/anthropic/clients.d.ts +0 -44
- package/dist/layer3/anthropic/clients.d.ts.map +0 -1
- package/dist/layer3/anthropic/clients.js +0 -81
- package/dist/layer3/anthropic/clients.js.map +0 -1
- package/dist/layer3/anthropic/index.d.ts +0 -41
- package/dist/layer3/anthropic/index.d.ts.map +0 -1
- package/dist/layer3/anthropic/index.js +0 -141
- package/dist/layer3/anthropic/index.js.map +0 -1
- package/dist/layer3/anthropic/prompts/index.d.ts +0 -8
- package/dist/layer3/anthropic/prompts/index.d.ts.map +0 -1
- package/dist/layer3/anthropic/prompts/index.js +0 -16
- package/dist/layer3/anthropic/prompts/index.js.map +0 -1
- package/dist/layer3/anthropic/prompts/modules/ai-patterns.d.ts +0 -19
- package/dist/layer3/anthropic/prompts/modules/ai-patterns.d.ts.map +0 -1
- package/dist/layer3/anthropic/prompts/modules/ai-patterns.js +0 -156
- package/dist/layer3/anthropic/prompts/modules/ai-patterns.js.map +0 -1
- package/dist/layer3/anthropic/prompts/modules/auth-access.d.ts +0 -9
- package/dist/layer3/anthropic/prompts/modules/auth-access.d.ts.map +0 -1
- package/dist/layer3/anthropic/prompts/modules/auth-access.js +0 -25
- package/dist/layer3/anthropic/prompts/modules/auth-access.js.map +0 -1
- package/dist/layer3/anthropic/prompts/modules/common.d.ts +0 -11
- package/dist/layer3/anthropic/prompts/modules/common.d.ts.map +0 -1
- package/dist/layer3/anthropic/prompts/modules/common.js +0 -152
- package/dist/layer3/anthropic/prompts/modules/common.js.map +0 -1
- package/dist/layer3/anthropic/prompts/modules/index.d.ts +0 -54
- package/dist/layer3/anthropic/prompts/modules/index.d.ts.map +0 -1
- package/dist/layer3/anthropic/prompts/modules/index.js +0 -185
- package/dist/layer3/anthropic/prompts/modules/index.js.map +0 -1
- package/dist/layer3/anthropic/prompts/modules/owasp-classic.d.ts +0 -8
- package/dist/layer3/anthropic/prompts/modules/owasp-classic.d.ts.map +0 -1
- package/dist/layer3/anthropic/prompts/modules/owasp-classic.js +0 -84
- package/dist/layer3/anthropic/prompts/modules/owasp-classic.js.map +0 -1
- package/dist/layer3/anthropic/prompts/modules/secrets-crypto.d.ts +0 -8
- package/dist/layer3/anthropic/prompts/modules/secrets-crypto.d.ts.map +0 -1
- package/dist/layer3/anthropic/prompts/modules/secrets-crypto.js +0 -68
- package/dist/layer3/anthropic/prompts/modules/secrets-crypto.js.map +0 -1
- package/dist/layer3/anthropic/prompts/modules/xss-prompt.d.ts +0 -8
- package/dist/layer3/anthropic/prompts/modules/xss-prompt.d.ts.map +0 -1
- package/dist/layer3/anthropic/prompts/modules/xss-prompt.js +0 -22
- package/dist/layer3/anthropic/prompts/modules/xss-prompt.js.map +0 -1
- package/dist/layer3/anthropic/prompts/semantic-analysis.d.ts +0 -15
- package/dist/layer3/anthropic/prompts/semantic-analysis.d.ts.map +0 -1
- package/dist/layer3/anthropic/prompts/semantic-analysis.js +0 -169
- package/dist/layer3/anthropic/prompts/semantic-analysis.js.map +0 -1
- package/dist/layer3/anthropic/prompts/validation.d.ts +0 -18
- package/dist/layer3/anthropic/prompts/validation.d.ts.map +0 -1
- package/dist/layer3/anthropic/prompts/validation.js +0 -25
- package/dist/layer3/anthropic/prompts/validation.js.map +0 -1
- package/dist/layer3/anthropic/providers/anthropic.d.ts +0 -21
- package/dist/layer3/anthropic/providers/anthropic.d.ts.map +0 -1
- package/dist/layer3/anthropic/providers/anthropic.js +0 -269
- package/dist/layer3/anthropic/providers/anthropic.js.map +0 -1
- package/dist/layer3/anthropic/providers/index.d.ts +0 -8
- package/dist/layer3/anthropic/providers/index.d.ts.map +0 -1
- package/dist/layer3/anthropic/providers/index.js +0 -15
- package/dist/layer3/anthropic/providers/index.js.map +0 -1
- package/dist/layer3/anthropic/providers/openai.d.ts +0 -18
- package/dist/layer3/anthropic/providers/openai.d.ts.map +0 -1
- package/dist/layer3/anthropic/providers/openai.js +0 -343
- package/dist/layer3/anthropic/providers/openai.js.map +0 -1
- package/dist/layer3/anthropic/request-builder.d.ts +0 -27
- package/dist/layer3/anthropic/request-builder.d.ts.map +0 -1
- package/dist/layer3/anthropic/request-builder.js +0 -150
- package/dist/layer3/anthropic/request-builder.js.map +0 -1
- package/dist/layer3/anthropic/types.d.ts +0 -88
- package/dist/layer3/anthropic/types.d.ts.map +0 -1
- package/dist/layer3/anthropic/types.js +0 -38
- package/dist/layer3/anthropic/types.js.map +0 -1
- package/dist/layer3/anthropic/utils/context-extractor.d.ts +0 -55
- package/dist/layer3/anthropic/utils/context-extractor.d.ts.map +0 -1
- package/dist/layer3/anthropic/utils/context-extractor.js +0 -161
- package/dist/layer3/anthropic/utils/context-extractor.js.map +0 -1
- package/dist/layer3/anthropic/utils/index.d.ts +0 -11
- package/dist/layer3/anthropic/utils/index.d.ts.map +0 -1
- package/dist/layer3/anthropic/utils/index.js +0 -27
- package/dist/layer3/anthropic/utils/index.js.map +0 -1
- package/dist/layer3/anthropic/utils/path-helpers.d.ts +0 -21
- package/dist/layer3/anthropic/utils/path-helpers.d.ts.map +0 -1
- package/dist/layer3/anthropic/utils/path-helpers.js +0 -69
- package/dist/layer3/anthropic/utils/path-helpers.js.map +0 -1
- package/dist/layer3/anthropic/utils/response-parser.d.ts +0 -40
- package/dist/layer3/anthropic/utils/response-parser.d.ts.map +0 -1
- package/dist/layer3/anthropic/utils/response-parser.js +0 -285
- package/dist/layer3/anthropic/utils/response-parser.js.map +0 -1
- package/dist/layer3/anthropic/utils/retry.d.ts +0 -15
- package/dist/layer3/anthropic/utils/retry.d.ts.map +0 -1
- package/dist/layer3/anthropic/utils/retry.js +0 -62
- package/dist/layer3/anthropic/utils/retry.js.map +0 -1
- package/dist/layer3/index.d.ts +0 -27
- package/dist/layer3/index.d.ts.map +0 -1
- package/dist/layer3/index.js +0 -150
- package/dist/layer3/index.js.map +0 -1
- package/dist/layer3/osv-check.d.ts +0 -75
- package/dist/layer3/osv-check.d.ts.map +0 -1
- package/dist/layer3/osv-check.js +0 -308
- package/dist/layer3/osv-check.js.map +0 -1
- package/dist/layer3/package-check.d.ts +0 -63
- package/dist/layer3/package-check.d.ts.map +0 -1
- package/dist/layer3/package-check.js +0 -508
- package/dist/layer3/package-check.js.map +0 -1
- package/dist/model/cross-file-taint.d.ts +0 -40
- package/dist/model/cross-file-taint.d.ts.map +0 -1
- package/dist/model/cross-file-taint.js +0 -290
- package/dist/model/cross-file-taint.js.map +0 -1
- package/dist/model/function-classifier.d.ts +0 -32
- package/dist/model/function-classifier.d.ts.map +0 -1
- package/dist/model/function-classifier.js +0 -143
- package/dist/model/function-classifier.js.map +0 -1
- package/dist/model/sanitiser-detection.d.ts +0 -27
- package/dist/model/sanitiser-detection.d.ts.map +0 -1
- package/dist/model/sanitiser-detection.js +0 -224
- package/dist/model/sanitiser-detection.js.map +0 -1
- package/dist/model/sink-matcher.d.ts +0 -17
- package/dist/model/sink-matcher.d.ts.map +0 -1
- package/dist/model/sink-matcher.js +0 -141
- package/dist/model/sink-matcher.js.map +0 -1
- package/dist/model/sink-patterns.d.ts +0 -19
- package/dist/model/sink-patterns.d.ts.map +0 -1
- package/dist/model/sink-patterns.js +0 -88
- package/dist/model/sink-patterns.js.map +0 -1
- package/dist/model/source-discovery.d.ts +0 -15
- package/dist/model/source-discovery.d.ts.map +0 -1
- package/dist/model/source-discovery.js +0 -170
- package/dist/model/source-discovery.js.map +0 -1
- package/dist/model/taint-tracker.d.ts +0 -21
- package/dist/model/taint-tracker.d.ts.map +0 -1
- package/dist/model/taint-tracker.js +0 -281
- package/dist/model/taint-tracker.js.map +0 -1
- package/dist/modes/incremental.d.ts +0 -66
- package/dist/modes/incremental.d.ts.map +0 -1
- package/dist/modes/incremental.js +0 -200
- package/dist/modes/incremental.js.map +0 -1
- package/dist/rules/framework-fixes.d.ts +0 -48
- package/dist/rules/framework-fixes.d.ts.map +0 -1
- package/dist/rules/framework-fixes.js +0 -439
- package/dist/rules/framework-fixes.js.map +0 -1
- package/dist/rules/index.d.ts +0 -8
- package/dist/rules/index.d.ts.map +0 -1
- package/dist/rules/index.js +0 -18
- package/dist/rules/index.js.map +0 -1
- package/dist/rules/metadata.d.ts +0 -43
- package/dist/rules/metadata.d.ts.map +0 -1
- package/dist/rules/metadata.js +0 -800
- package/dist/rules/metadata.js.map +0 -1
- package/dist/score/auto-dismiss.d.ts +0 -28
- package/dist/score/auto-dismiss.d.ts.map +0 -1
- package/dist/score/auto-dismiss.js +0 -200
- package/dist/score/auto-dismiss.js.map +0 -1
- package/dist/suppression/config-loader.d.ts +0 -74
- package/dist/suppression/config-loader.d.ts.map +0 -1
- package/dist/suppression/config-loader.js +0 -424
- package/dist/suppression/config-loader.js.map +0 -1
- package/dist/suppression/hash.d.ts +0 -48
- package/dist/suppression/hash.d.ts.map +0 -1
- package/dist/suppression/hash.js +0 -88
- package/dist/suppression/hash.js.map +0 -1
- package/dist/suppression/index.d.ts +0 -11
- package/dist/suppression/index.d.ts.map +0 -1
- package/dist/suppression/index.js +0 -39
- package/dist/suppression/index.js.map +0 -1
- package/dist/suppression/inline-parser.d.ts +0 -39
- package/dist/suppression/inline-parser.d.ts.map +0 -1
- package/dist/suppression/inline-parser.js +0 -218
- package/dist/suppression/inline-parser.js.map +0 -1
- package/dist/suppression/manager.d.ts +0 -94
- package/dist/suppression/manager.d.ts.map +0 -1
- package/dist/suppression/manager.js +0 -292
- package/dist/suppression/manager.js.map +0 -1
- package/dist/suppression/types.d.ts +0 -151
- package/dist/suppression/types.d.ts.map +0 -1
- package/dist/suppression/types.js +0 -28
- package/dist/suppression/types.js.map +0 -1
- package/dist/types.d.ts +0 -331
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js +0 -124
- package/dist/types.js.map +0 -1
- package/dist/utils/auth-helper-detector.d.ts +0 -56
- package/dist/utils/auth-helper-detector.d.ts.map +0 -1
- package/dist/utils/auth-helper-detector.js +0 -360
- package/dist/utils/auth-helper-detector.js.map +0 -1
- package/dist/utils/code-analysis.d.ts +0 -39
- package/dist/utils/code-analysis.d.ts.map +0 -1
- package/dist/utils/code-analysis.js +0 -159
- package/dist/utils/code-analysis.js.map +0 -1
- package/dist/utils/comment-analyzer.d.ts +0 -38
- package/dist/utils/comment-analyzer.d.ts.map +0 -1
- package/dist/utils/comment-analyzer.js +0 -218
- package/dist/utils/comment-analyzer.js.map +0 -1
- package/dist/utils/context-helpers.d.ts +0 -219
- package/dist/utils/context-helpers.d.ts.map +0 -1
- package/dist/utils/context-helpers.js +0 -886
- package/dist/utils/context-helpers.js.map +0 -1
- package/dist/utils/diff-detector.d.ts +0 -53
- package/dist/utils/diff-detector.d.ts.map +0 -1
- package/dist/utils/diff-detector.js +0 -104
- package/dist/utils/diff-detector.js.map +0 -1
- package/dist/utils/diff-parser.d.ts +0 -80
- package/dist/utils/diff-parser.d.ts.map +0 -1
- package/dist/utils/diff-parser.js +0 -202
- package/dist/utils/diff-parser.js.map +0 -1
- package/dist/utils/environment-context.d.ts +0 -76
- package/dist/utils/environment-context.d.ts.map +0 -1
- package/dist/utils/environment-context.js +0 -271
- package/dist/utils/environment-context.js.map +0 -1
- package/dist/utils/imported-auth-detector.d.ts +0 -37
- package/dist/utils/imported-auth-detector.d.ts.map +0 -1
- package/dist/utils/imported-auth-detector.js +0 -251
- package/dist/utils/imported-auth-detector.js.map +0 -1
- package/dist/utils/intent-detector.d.ts +0 -66
- package/dist/utils/intent-detector.d.ts.map +0 -1
- package/dist/utils/intent-detector.js +0 -282
- package/dist/utils/intent-detector.js.map +0 -1
- package/dist/utils/middleware-detector.d.ts +0 -55
- package/dist/utils/middleware-detector.d.ts.map +0 -1
- package/dist/utils/middleware-detector.js +0 -260
- package/dist/utils/middleware-detector.js.map +0 -1
- package/dist/utils/oauth-flow-detector.d.ts +0 -41
- package/dist/utils/oauth-flow-detector.d.ts.map +0 -1
- package/dist/utils/oauth-flow-detector.js +0 -202
- package/dist/utils/oauth-flow-detector.js.map +0 -1
- package/dist/utils/parsed-file.d.ts +0 -51
- package/dist/utils/parsed-file.d.ts.map +0 -1
- package/dist/utils/parsed-file.js +0 -95
- package/dist/utils/parsed-file.js.map +0 -1
- package/dist/utils/path-exclusions.d.ts +0 -55
- package/dist/utils/path-exclusions.d.ts.map +0 -1
- package/dist/utils/path-exclusions.js +0 -224
- package/dist/utils/path-exclusions.js.map +0 -1
- package/dist/utils/project-context-builder.d.ts +0 -119
- package/dist/utils/project-context-builder.d.ts.map +0 -1
- package/dist/utils/project-context-builder.js +0 -534
- package/dist/utils/project-context-builder.js.map +0 -1
- package/dist/utils/registry-clients.d.ts +0 -93
- package/dist/utils/registry-clients.d.ts.map +0 -1
- package/dist/utils/registry-clients.js +0 -273
- package/dist/utils/registry-clients.js.map +0 -1
- package/dist/utils/route-hierarchy.d.ts +0 -50
- package/dist/utils/route-hierarchy.d.ts.map +0 -1
- package/dist/utils/route-hierarchy.js +0 -226
- package/dist/utils/route-hierarchy.js.map +0 -1
- package/dist/utils/schema-semantics.d.ts +0 -45
- package/dist/utils/schema-semantics.d.ts.map +0 -1
- package/dist/utils/schema-semantics.js +0 -193
- package/dist/utils/schema-semantics.js.map +0 -1
- package/dist/utils/trpc-analyzer.d.ts +0 -78
- package/dist/utils/trpc-analyzer.d.ts.map +0 -1
- package/dist/utils/trpc-analyzer.js +0 -297
- package/dist/utils/trpc-analyzer.js.map +0 -1
- package/src/__tests__/context-engine/cross-file-taint.test.ts +0 -284
- package/src/__tests__/context-engine/function-classifier.test.ts +0 -146
- package/src/__tests__/context-engine/integration.test.ts +0 -320
- package/src/__tests__/context-engine/sanitiser-detection.test.ts +0 -187
- package/src/__tests__/context-engine/sink-matcher.test.ts +0 -251
- package/src/__tests__/context-engine/source-discovery.test.ts +0 -186
- package/src/__tests__/context-engine/taint-tracker.test.ts +0 -182
- package/src/__tests__/snapshots/__snapshots__/anthropic-validation-refactor.test.ts.snap +0 -750
- package/src/__tests__/snapshots/__snapshots__/dangerous-functions-refactor.test.ts.snap +0 -555
- package/src/__tests__/snapshots/anthropic-validation-refactor.test.ts +0 -321
- package/src/__tests__/snapshots/dangerous-functions-refactor.test.ts +0 -439
- package/src/detect/ai-code/agent-tools.ts +0 -1662
- package/src/detect/ai-code/byok-patterns.ts +0 -354
- package/src/detect/ai-code/endpoint-protection.ts +0 -406
- package/src/detect/ai-code/execution-sinks.ts +0 -1310
- package/src/detect/ai-code/fingerprinting.ts +0 -774
- package/src/detect/ai-code/mcp-security.ts +0 -937
- package/src/detect/ai-code/model-supply-chain.ts +0 -535
- package/src/detect/ai-code/package-hallucination.ts +0 -955
- package/src/detect/ai-code/prompt-hygiene.ts +0 -1314
- package/src/detect/ai-code/rag-safety.ts +0 -977
- package/src/detect/ai-code/schema-validation.ts +0 -427
- package/src/detect/secrets/patterns.ts +0 -561
- package/src/detect/secrets/weak-crypto.ts +0 -485
- package/src/detect/structural/__tests__/math-random-enhanced.test.ts +0 -405
- package/src/detect/structural/auth-patterns.ts +0 -621
- package/src/detect/structural/dangerous-functions/child-process.ts +0 -98
- package/src/detect/structural/dangerous-functions/dom-xss.ts +0 -292
- package/src/detect/structural/dangerous-functions/index.ts +0 -1556
- package/src/detect/structural/dangerous-functions/json-parse.ts +0 -393
- package/src/detect/structural/dangerous-functions/math-random.ts +0 -789
- package/src/detect/structural/dangerous-functions/patterns.ts +0 -176
- package/src/detect/structural/dangerous-functions/request-validation.ts +0 -153
- package/src/detect/structural/dangerous-functions/utils/control-flow.ts +0 -35
- package/src/detect/structural/dangerous-functions/utils/helpers.ts +0 -170
- package/src/detect/structural/dangerous-functions/utils/index.ts +0 -25
- package/src/detect/structural/dangerous-functions/utils/schema-validation.ts +0 -106
- package/src/detect/structural/data-exposure.ts +0 -302
- package/src/detect/structural/framework-checks.ts +0 -439
- package/src/detect/structural/log-injection.ts +0 -254
- package/src/detect/structural/logic-gates.ts +0 -256
- package/src/detect/structural/risky-imports.ts +0 -197
- package/src/detect/structural/security-headers.ts +0 -231
- package/src/detect/structural/ssrf-detection.ts +0 -300
- package/src/detect/structural/variables.ts +0 -177
- package/src/detect/structural/xxe-detection.ts +0 -295
- package/src/model/cross-file-taint.ts +0 -374
- package/src/model/function-classifier.ts +0 -184
- package/src/model/sanitiser-detection.ts +0 -268
- package/src/model/sink-matcher.ts +0 -178
- package/src/model/sink-patterns.ts +0 -109
- package/src/model/source-discovery.ts +0 -209
- package/src/model/taint-tracker.ts +0 -333
- package/src/score/auto-dismiss.ts +0 -224
|
@@ -0,0 +1,1750 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Taint Propagation Engine
|
|
3
|
+
*
|
|
4
|
+
* Forward worklist dataflow analysis. Seeds taint at sources, propagates
|
|
5
|
+
* through CFG to fixpoint, then checks if tainted data reaches sinks
|
|
6
|
+
* without proper sanitization.
|
|
7
|
+
*
|
|
8
|
+
* Algorithm:
|
|
9
|
+
* 1. Map sources/sinks/sanitizers to CFG nodes via AST containment
|
|
10
|
+
* 2. Seed source nodes with taint state
|
|
11
|
+
* 3. Worklist: propagate taint forward through CFG, applying transfer
|
|
12
|
+
* functions at each node (assignment, call, destructuring, etc.)
|
|
13
|
+
* 4. At fixpoint: check each sink node for matching taint kinds
|
|
14
|
+
* 5. Reconstruct source-to-sink paths via backward slice
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import type Parser from 'tree-sitter'
|
|
18
|
+
import type { CFG, CFGNode } from './cfg-types'
|
|
19
|
+
import { getSuccessors, getPredecessors, buildFileCFGs } from './cfg-builder'
|
|
20
|
+
import type { TaintKind, TaintSource, TaintSink, TaintSanitizer } from './types'
|
|
21
|
+
import type { TaintState, TaintStep, TaintFinding, CallResolutionContext, CalleeResult } from './propagation-types'
|
|
22
|
+
import { getTaint, setTaint, deleteTaint, getFieldTaint, setFieldTaint, WHOLE_OBJECT } from './propagation-types'
|
|
23
|
+
import type { FieldKey } from './propagation-types'
|
|
24
|
+
import { walkAST } from '../parse/ast'
|
|
25
|
+
import { classifySources } from './source-classifier'
|
|
26
|
+
import { classifySinks } from './sink-classifier'
|
|
27
|
+
import { buildSanitizerRegistry } from './sanitizer-registry'
|
|
28
|
+
import { resolveCallTarget } from '../detect/ast-rules/helpers/call-analysis'
|
|
29
|
+
import { resolvePythonCallTarget } from '../detect/ast-rules/helpers/python-helpers'
|
|
30
|
+
import { collectImportBindings } from './helpers'
|
|
31
|
+
import { injectPromiseChainTaint, injectCallbackTaint } from './async-flow'
|
|
32
|
+
import { collectConstants, isConstantExpression } from './constant-propagation'
|
|
33
|
+
import type { ConstantMap } from './constant-propagation'
|
|
34
|
+
import { getOrBuildSummary, composeSummary } from './taint-summary'
|
|
35
|
+
|
|
36
|
+
// ============================================================================
|
|
37
|
+
// AST → CFGNode Matching
|
|
38
|
+
// ============================================================================
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Check if `child` is a descendant of (or equal to) `ancestor` in the AST.
|
|
42
|
+
*/
|
|
43
|
+
export function isDescendantOf(
|
|
44
|
+
child: Parser.SyntaxNode,
|
|
45
|
+
ancestor: Parser.SyntaxNode,
|
|
46
|
+
): boolean {
|
|
47
|
+
let current: Parser.SyntaxNode | null = child
|
|
48
|
+
while (current) {
|
|
49
|
+
if (current.id === ancestor.id) return true
|
|
50
|
+
current = current.parent
|
|
51
|
+
}
|
|
52
|
+
return false
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Find the CFG node that contains a given AST node.
|
|
57
|
+
* Returns the first CFG node whose astNode is an ancestor of (or equal to) the target.
|
|
58
|
+
*/
|
|
59
|
+
export function findContainingCFGNode(
|
|
60
|
+
cfg: CFG,
|
|
61
|
+
astNode: Parser.SyntaxNode,
|
|
62
|
+
): CFGNode | null {
|
|
63
|
+
for (const [, cfgNode] of cfg.nodes) {
|
|
64
|
+
if (!cfgNode.astNode) continue
|
|
65
|
+
if (isDescendantOf(astNode, cfgNode.astNode)) return cfgNode
|
|
66
|
+
}
|
|
67
|
+
return null
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Phase 8 (Part 4): O(D) parent-chain walk using pre-built CFG node map.
|
|
72
|
+
* Walks up the AST parent chain until a mapped node is found.
|
|
73
|
+
* D = AST depth (5-15), vs O(N) where N = CFG nodes (50-200).
|
|
74
|
+
*/
|
|
75
|
+
export function findContainingCFGNodeFast(
|
|
76
|
+
cfgNodeMap: Map<number, CFGNode>,
|
|
77
|
+
astNode: Parser.SyntaxNode,
|
|
78
|
+
): CFGNode | null {
|
|
79
|
+
let current: Parser.SyntaxNode | null = astNode
|
|
80
|
+
while (current) {
|
|
81
|
+
const cfgNode = cfgNodeMap.get(current.id)
|
|
82
|
+
if (cfgNode) return cfgNode
|
|
83
|
+
current = current.parent
|
|
84
|
+
}
|
|
85
|
+
return null
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Build a map from AST node ID → CFGNode for all nodes in a CFG.
|
|
90
|
+
*/
|
|
91
|
+
export function buildCFGNodeMap(cfg: CFG): Map<number, CFGNode> {
|
|
92
|
+
const map = new Map<number, CFGNode>()
|
|
93
|
+
for (const [, cfgNode] of cfg.nodes) {
|
|
94
|
+
if (cfgNode.astNode) {
|
|
95
|
+
map.set(cfgNode.astNode.id, cfgNode)
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return map
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// ============================================================================
|
|
102
|
+
// Taint State Helpers
|
|
103
|
+
// ============================================================================
|
|
104
|
+
|
|
105
|
+
/** Clone a TaintState (deep copy of field maps and sets). */
|
|
106
|
+
function cloneState(state: TaintState): TaintState {
|
|
107
|
+
const clone: TaintState = new Map()
|
|
108
|
+
for (const [varName, fieldMap] of state) {
|
|
109
|
+
const clonedFieldMap = new Map<FieldKey, Set<TaintKind>>()
|
|
110
|
+
for (const [field, kinds] of fieldMap) {
|
|
111
|
+
clonedFieldMap.set(field, new Set(kinds))
|
|
112
|
+
}
|
|
113
|
+
clone.set(varName, clonedFieldMap)
|
|
114
|
+
}
|
|
115
|
+
return clone
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/** Merge multiple predecessor out-states into one in-state (union). */
|
|
119
|
+
export function mergeStates(states: TaintState[]): TaintState {
|
|
120
|
+
const merged: TaintState = new Map()
|
|
121
|
+
for (const state of states) {
|
|
122
|
+
for (const [varName, fieldMap] of state) {
|
|
123
|
+
let existingFieldMap = merged.get(varName)
|
|
124
|
+
if (!existingFieldMap) {
|
|
125
|
+
existingFieldMap = new Map<FieldKey, Set<TaintKind>>()
|
|
126
|
+
merged.set(varName, existingFieldMap)
|
|
127
|
+
}
|
|
128
|
+
for (const [field, kinds] of fieldMap) {
|
|
129
|
+
const existingKinds = existingFieldMap.get(field)
|
|
130
|
+
if (existingKinds) {
|
|
131
|
+
for (const k of kinds) existingKinds.add(k)
|
|
132
|
+
} else {
|
|
133
|
+
existingFieldMap.set(field, new Set(kinds))
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return merged
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/** Check if two TaintStates are equal. */
|
|
142
|
+
function statesEqual(a: TaintState, b: TaintState): boolean {
|
|
143
|
+
// Phase 8 (3c): identity fast path — catches cases where applyTransfer returned same ref
|
|
144
|
+
if (a === b) return true
|
|
145
|
+
if (a.size !== b.size) return false
|
|
146
|
+
for (const [varName, aFieldMap] of a) {
|
|
147
|
+
const bFieldMap = b.get(varName)
|
|
148
|
+
if (!bFieldMap) return false
|
|
149
|
+
if (aFieldMap.size !== bFieldMap.size) return false
|
|
150
|
+
for (const [field, aKinds] of aFieldMap) {
|
|
151
|
+
const bKinds = bFieldMap.get(field)
|
|
152
|
+
if (!bKinds) return false
|
|
153
|
+
if (aKinds.size !== bKinds.size) return false
|
|
154
|
+
for (const k of aKinds) {
|
|
155
|
+
if (!bKinds.has(k)) return false
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return true
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/** Collect taint from a set of used variables in the current state. */
|
|
163
|
+
function collectUseTaint(
|
|
164
|
+
uses: Iterable<string>,
|
|
165
|
+
state: TaintState,
|
|
166
|
+
): Set<TaintKind> {
|
|
167
|
+
const result = new Set<TaintKind>()
|
|
168
|
+
for (const u of uses) {
|
|
169
|
+
const kinds = getTaint(state, u)
|
|
170
|
+
if (kinds) {
|
|
171
|
+
for (const k of kinds) result.add(k)
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
return result
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// ============================================================================
|
|
178
|
+
// Sanitizer Map
|
|
179
|
+
// ============================================================================
|
|
180
|
+
|
|
181
|
+
export interface SanitizerMapping {
|
|
182
|
+
cfgNodeId: number
|
|
183
|
+
sanitizer: TaintSanitizer
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Build a map from CFG node ID → sanitizers active in that node.
|
|
188
|
+
* Phase 8: accepts optional cfgNodeMap for O(D) lookup instead of O(N).
|
|
189
|
+
*/
|
|
190
|
+
export function buildSanitizerMap(
|
|
191
|
+
cfg: CFG,
|
|
192
|
+
sanitizers: TaintSanitizer[],
|
|
193
|
+
cfgNodeMap?: Map<number, CFGNode>,
|
|
194
|
+
): Map<number, TaintSanitizer[]> {
|
|
195
|
+
const map = new Map<number, TaintSanitizer[]>()
|
|
196
|
+
for (const s of sanitizers) {
|
|
197
|
+
const cfgNode = cfgNodeMap
|
|
198
|
+
? findContainingCFGNodeFast(cfgNodeMap, s.node)
|
|
199
|
+
: findContainingCFGNode(cfg, s.node)
|
|
200
|
+
if (!cfgNode) continue
|
|
201
|
+
const existing = map.get(cfgNode.id)
|
|
202
|
+
if (existing) {
|
|
203
|
+
existing.push(s)
|
|
204
|
+
} else {
|
|
205
|
+
map.set(cfgNode.id, [s])
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
return map
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// ============================================================================
|
|
212
|
+
// Expression-level Transfer Function
|
|
213
|
+
// ============================================================================
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Resolve taint for the RHS of an assignment by walking its AST.
|
|
217
|
+
*
|
|
218
|
+
* Returns the set of taint kinds that the RHS expression carries,
|
|
219
|
+
* considering sanitizer calls and optionally cross-file call resolution.
|
|
220
|
+
*/
|
|
221
|
+
function resolveExpressionTaint(
|
|
222
|
+
exprNode: Parser.SyntaxNode,
|
|
223
|
+
inState: TaintState,
|
|
224
|
+
sanitizers: TaintSanitizer[],
|
|
225
|
+
callContext?: CallResolutionContext,
|
|
226
|
+
callerFile?: string,
|
|
227
|
+
): Set<TaintKind> {
|
|
228
|
+
const result = new Set<TaintKind>()
|
|
229
|
+
|
|
230
|
+
// Literal values: no taint
|
|
231
|
+
if (isLiteral(exprNode)) return result
|
|
232
|
+
|
|
233
|
+
// Identifier: look up in state
|
|
234
|
+
if (exprNode.type === 'identifier') {
|
|
235
|
+
const kinds = getTaint(inState, exprNode.text)
|
|
236
|
+
if (kinds) for (const k of kinds) result.add(k)
|
|
237
|
+
return result
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// Helper: shorthand for recursive calls with context forwarding
|
|
241
|
+
const resolve = (node: Parser.SyntaxNode) =>
|
|
242
|
+
resolveExpressionTaint(node, inState, sanitizers, callContext, callerFile)
|
|
243
|
+
|
|
244
|
+
// Await: taint propagates through (Python: 'await', JS: 'await_expression')
|
|
245
|
+
if (exprNode.type === 'await_expression' || exprNode.type === 'await') {
|
|
246
|
+
const inner = exprNode.namedChildren[0]
|
|
247
|
+
if (inner) {
|
|
248
|
+
const innerTaint = resolve(inner)
|
|
249
|
+
for (const k of innerTaint) result.add(k)
|
|
250
|
+
}
|
|
251
|
+
return result
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// Parenthesized: pass through
|
|
255
|
+
if (exprNode.type === 'parenthesized_expression') {
|
|
256
|
+
const inner = exprNode.namedChildren[0]
|
|
257
|
+
if (inner) {
|
|
258
|
+
const innerTaint = resolve(inner)
|
|
259
|
+
for (const k of innerTaint) result.add(k)
|
|
260
|
+
}
|
|
261
|
+
return result
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// Call expression: check sanitizers, then cross-file resolution, then conservative
|
|
265
|
+
// Python: 'call', JS: 'call_expression'
|
|
266
|
+
if (exprNode.type === 'call_expression' || exprNode.type === 'call') {
|
|
267
|
+
return resolveCallTaint(exprNode, inState, sanitizers, callContext, callerFile)
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// New expression: union of arg taint
|
|
271
|
+
if (exprNode.type === 'new_expression') {
|
|
272
|
+
const args = exprNode.childForFieldName('arguments')
|
|
273
|
+
if (args) {
|
|
274
|
+
for (const arg of args.namedChildren) {
|
|
275
|
+
const argTaint = resolve(arg)
|
|
276
|
+
for (const k of argTaint) result.add(k)
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
return result
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// Binary expression / template literal: union of both sides
|
|
283
|
+
// Python: 'binary_operator', JS: 'binary_expression'
|
|
284
|
+
if (exprNode.type === 'binary_expression' || exprNode.type === 'binary_operator') {
|
|
285
|
+
const left = exprNode.childForFieldName('left')
|
|
286
|
+
const right = exprNode.childForFieldName('right')
|
|
287
|
+
if (left) {
|
|
288
|
+
const lt = resolve(left)
|
|
289
|
+
for (const k of lt) result.add(k)
|
|
290
|
+
}
|
|
291
|
+
if (right) {
|
|
292
|
+
const rt = resolve(right)
|
|
293
|
+
for (const k of rt) result.add(k)
|
|
294
|
+
}
|
|
295
|
+
return result
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// Python boolean/comparison operators: walk children for taint
|
|
299
|
+
if (exprNode.type === 'boolean_operator' || exprNode.type === 'comparison_operator' ||
|
|
300
|
+
exprNode.type === 'not_operator') {
|
|
301
|
+
for (const child of exprNode.namedChildren) {
|
|
302
|
+
const ct = resolve(child)
|
|
303
|
+
for (const k of ct) result.add(k)
|
|
304
|
+
}
|
|
305
|
+
return result
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// Python f-string: string node with interpolation children
|
|
309
|
+
// Must check BEFORE the fallback walkAST to properly handle f-string taint
|
|
310
|
+
if (exprNode.type === 'string' && exprNode.descendantsOfType('interpolation').length > 0) {
|
|
311
|
+
for (const interp of exprNode.descendantsOfType('interpolation')) {
|
|
312
|
+
for (const child of interp.namedChildren) {
|
|
313
|
+
const childTaint = resolve(child)
|
|
314
|
+
for (const k of childTaint) result.add(k)
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
return result
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// Python concatenated_string: "foo" + "bar" or "a" "b" style
|
|
321
|
+
if (exprNode.type === 'concatenated_string') {
|
|
322
|
+
for (const child of exprNode.namedChildren) {
|
|
323
|
+
const ct = resolve(child)
|
|
324
|
+
for (const k of ct) result.add(k)
|
|
325
|
+
}
|
|
326
|
+
return result
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// Template string: union of all substitutions
|
|
330
|
+
if (exprNode.type === 'template_string') {
|
|
331
|
+
const subs = exprNode.descendantsOfType('template_substitution')
|
|
332
|
+
for (const sub of subs) {
|
|
333
|
+
for (const child of sub.namedChildren) {
|
|
334
|
+
const childTaint = resolve(child)
|
|
335
|
+
for (const k of childTaint) result.add(k)
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
return result
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// Member expression: field-aware taint resolution
|
|
342
|
+
// Python: 'attribute' + 'subscript', JS: 'member_expression' + 'subscript_expression'
|
|
343
|
+
if (exprNode.type === 'member_expression' || exprNode.type === 'subscript_expression' ||
|
|
344
|
+
exprNode.type === 'attribute' || exprNode.type === 'subscript') {
|
|
345
|
+
const obj = exprNode.childForFieldName('object')
|
|
346
|
+
?? exprNode.childForFieldName('value') // Python subscript uses 'value' field
|
|
347
|
+
if (obj) {
|
|
348
|
+
// Try field-specific taint: obj.field → getFieldTaint(state, "obj", "field")
|
|
349
|
+
if ((exprNode.type === 'member_expression' || exprNode.type === 'attribute') && obj.type === 'identifier') {
|
|
350
|
+
const prop = exprNode.type === 'attribute'
|
|
351
|
+
? exprNode.childForFieldName('attribute')
|
|
352
|
+
: exprNode.childForFieldName('property')
|
|
353
|
+
if (prop && (prop.type === 'property_identifier' || prop.type === 'identifier')) {
|
|
354
|
+
const fieldTaint = getFieldTaint(inState, obj.text, prop.text)
|
|
355
|
+
if (fieldTaint) for (const k of fieldTaint) result.add(k)
|
|
356
|
+
return result
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
// Computed/dynamic access or deep chain: fall back to whole-object taint
|
|
360
|
+
const objTaint = resolve(obj)
|
|
361
|
+
for (const k of objTaint) result.add(k)
|
|
362
|
+
}
|
|
363
|
+
// Computed index may also carry taint
|
|
364
|
+
if (exprNode.type === 'subscript_expression' || exprNode.type === 'subscript') {
|
|
365
|
+
const index = exprNode.childForFieldName('index')
|
|
366
|
+
?? exprNode.childForFieldName('subscript') // Python subscript field name
|
|
367
|
+
if (index) {
|
|
368
|
+
const idxTaint = resolve(index)
|
|
369
|
+
for (const k of idxTaint) result.add(k)
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
return result
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// Spread element
|
|
376
|
+
if (exprNode.type === 'spread_element') {
|
|
377
|
+
const inner = exprNode.namedChildren[0]
|
|
378
|
+
if (inner) {
|
|
379
|
+
const innerTaint = resolve(inner)
|
|
380
|
+
for (const k of innerTaint) result.add(k)
|
|
381
|
+
}
|
|
382
|
+
return result
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
// Object literal: union of all value taint (whole-object view for backward compat)
|
|
386
|
+
if (exprNode.type === 'object') {
|
|
387
|
+
for (const child of exprNode.namedChildren) {
|
|
388
|
+
if (child.type === 'pair') {
|
|
389
|
+
const value = child.childForFieldName('value')
|
|
390
|
+
if (value) {
|
|
391
|
+
const vt = resolve(value)
|
|
392
|
+
for (const k of vt) result.add(k)
|
|
393
|
+
}
|
|
394
|
+
} else if (child.type === 'shorthand_property_identifier') {
|
|
395
|
+
const kinds = getTaint(inState, child.text)
|
|
396
|
+
if (kinds) for (const k of kinds) result.add(k)
|
|
397
|
+
} else if (child.type === 'spread_element') {
|
|
398
|
+
const inner = child.namedChildren[0]
|
|
399
|
+
if (inner) {
|
|
400
|
+
const innerTaint = resolve(inner)
|
|
401
|
+
for (const k of innerTaint) result.add(k)
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
return result
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
// Array literal / Python list/tuple: union of all element taint
|
|
409
|
+
if (exprNode.type === 'array' || exprNode.type === 'list' || exprNode.type === 'tuple') {
|
|
410
|
+
for (const child of exprNode.namedChildren) {
|
|
411
|
+
const ct = resolve(child)
|
|
412
|
+
for (const k of ct) result.add(k)
|
|
413
|
+
}
|
|
414
|
+
return result
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
// Python dictionary: union of all value taint
|
|
418
|
+
if (exprNode.type === 'dictionary') {
|
|
419
|
+
for (const child of exprNode.namedChildren) {
|
|
420
|
+
if (child.type === 'pair') {
|
|
421
|
+
const value = child.childForFieldName('value')
|
|
422
|
+
if (value) {
|
|
423
|
+
const vt = resolve(value)
|
|
424
|
+
for (const k of vt) result.add(k)
|
|
425
|
+
}
|
|
426
|
+
} else {
|
|
427
|
+
const ct = resolve(child)
|
|
428
|
+
for (const k of ct) result.add(k)
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
return result
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
// Ternary: union of both branches (conservative)
|
|
435
|
+
// Python: 'conditional_expression', JS: 'ternary_expression'
|
|
436
|
+
if (exprNode.type === 'ternary_expression' || exprNode.type === 'conditional_expression') {
|
|
437
|
+
for (const child of exprNode.namedChildren) {
|
|
438
|
+
const ct = resolve(child)
|
|
439
|
+
for (const k of ct) result.add(k)
|
|
440
|
+
}
|
|
441
|
+
return result
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
// Unary expression: propagate from operand
|
|
445
|
+
// Python: 'unary_operator', JS: 'unary_expression'
|
|
446
|
+
if (exprNode.type === 'unary_expression' || exprNode.type === 'unary_operator') {
|
|
447
|
+
const arg = exprNode.namedChildren[0]
|
|
448
|
+
if (arg) {
|
|
449
|
+
const at = resolve(arg)
|
|
450
|
+
for (const k of at) result.add(k)
|
|
451
|
+
}
|
|
452
|
+
return result
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
// Python keyword_argument: extract taint from value
|
|
456
|
+
if (exprNode.type === 'keyword_argument') {
|
|
457
|
+
const value = exprNode.childForFieldName('value')
|
|
458
|
+
if (value) {
|
|
459
|
+
const vt = resolve(value)
|
|
460
|
+
for (const k of vt) result.add(k)
|
|
461
|
+
}
|
|
462
|
+
return result
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
// Type assertion / as expression: propagate through
|
|
466
|
+
if (exprNode.type === 'as_expression' || exprNode.type === 'type_assertion' ||
|
|
467
|
+
exprNode.type === 'non_null_expression' || exprNode.type === 'satisfies_expression') {
|
|
468
|
+
const inner = exprNode.namedChildren[0]
|
|
469
|
+
if (inner) {
|
|
470
|
+
const innerTaint = resolve(inner)
|
|
471
|
+
for (const k of innerTaint) result.add(k)
|
|
472
|
+
}
|
|
473
|
+
return result
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
// Fallback: conservatively collect taint from all identifiers in the expression
|
|
477
|
+
walkAST(exprNode, (node) => {
|
|
478
|
+
if (node.type === 'identifier') {
|
|
479
|
+
const kinds = getTaint(inState, node.text)
|
|
480
|
+
if (kinds) for (const k of kinds) result.add(k)
|
|
481
|
+
}
|
|
482
|
+
// Don't descend into nested function expressions
|
|
483
|
+
if (node.type === 'arrow_function' || node.type === 'function_expression' ||
|
|
484
|
+
node.type === 'function_definition' || node.type === 'lambda') return true
|
|
485
|
+
return false
|
|
486
|
+
})
|
|
487
|
+
|
|
488
|
+
return result
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
/**
|
|
492
|
+
* Resolve taint for a call expression, checking sanitizers and cross-file callees.
|
|
493
|
+
*/
|
|
494
|
+
function resolveCallTaint(
|
|
495
|
+
callNode: Parser.SyntaxNode,
|
|
496
|
+
inState: TaintState,
|
|
497
|
+
sanitizers: TaintSanitizer[],
|
|
498
|
+
callContext?: CallResolutionContext,
|
|
499
|
+
callerFile?: string,
|
|
500
|
+
): Set<TaintKind> {
|
|
501
|
+
const result = new Set<TaintKind>()
|
|
502
|
+
const resolve = (node: Parser.SyntaxNode) =>
|
|
503
|
+
resolveExpressionTaint(node, inState, sanitizers, callContext, callerFile)
|
|
504
|
+
|
|
505
|
+
// First, collect taint from all arguments (and per-argument taint for cross-file)
|
|
506
|
+
const argsNode = callNode.childForFieldName('arguments')
|
|
507
|
+
const argTaint = new Set<TaintKind>()
|
|
508
|
+
const perArgTaint: Set<TaintKind>[] = []
|
|
509
|
+
if (argsNode) {
|
|
510
|
+
for (const arg of argsNode.namedChildren) {
|
|
511
|
+
const at = resolve(arg)
|
|
512
|
+
perArgTaint.push(at)
|
|
513
|
+
for (const k of at) argTaint.add(k)
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
// Also get taint from the callee (for method chains: tainted.toString())
|
|
518
|
+
// Python: 'attribute', JS: 'member_expression'
|
|
519
|
+
const fnNode = callNode.childForFieldName('function')
|
|
520
|
+
if (fnNode?.type === 'member_expression' || fnNode?.type === 'attribute') {
|
|
521
|
+
const obj = fnNode.childForFieldName('object')
|
|
522
|
+
if (obj) {
|
|
523
|
+
const objTaint = resolve(obj)
|
|
524
|
+
for (const k of objTaint) argTaint.add(k)
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
// Check if this call is a sanitizer
|
|
529
|
+
const matchingSanitizer = findMatchingSanitizer(callNode, sanitizers)
|
|
530
|
+
if (matchingSanitizer) {
|
|
531
|
+
// Sanitizer: result = argTaint minus clearsKinds
|
|
532
|
+
for (const k of argTaint) {
|
|
533
|
+
if (!matchingSanitizer.clearsKinds.has(k)) {
|
|
534
|
+
result.add(k)
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
return result
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
// Cross-file / inter-procedural resolution
|
|
541
|
+
if (callContext && callerFile && argTaint.size > 0) {
|
|
542
|
+
const calleeResult = resolveCalleeReturnTaint(
|
|
543
|
+
callNode, perArgTaint, callContext, callerFile,
|
|
544
|
+
)
|
|
545
|
+
if (calleeResult !== null) {
|
|
546
|
+
return calleeResult.returnTaint
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
// Not a sanitizer, not cross-file resolved: conservatively propagate all arg taint
|
|
551
|
+
return argTaint
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
// ============================================================================
|
|
555
|
+
// Cross-File Call Resolution
|
|
556
|
+
// ============================================================================
|
|
557
|
+
|
|
558
|
+
/**
|
|
559
|
+
* Resolve the return taint from following a call into another function/file.
|
|
560
|
+
*
|
|
561
|
+
* 1. Resolve call target name
|
|
562
|
+
* 2. Look up in CrossFileIndex (resolveImport or functionDefs)
|
|
563
|
+
* 3. If found and not in callStack (recursion guard):
|
|
564
|
+
* a. Parse callee file's AST, build CFG
|
|
565
|
+
* b. Create synthetic sources from argument taint → parameter names
|
|
566
|
+
* c. Run propagation on callee's CFG
|
|
567
|
+
* d. Collect return-value taint from return statements
|
|
568
|
+
* 4. Return the taint kinds flowing out + path steps
|
|
569
|
+
*/
|
|
570
|
+
export function resolveCalleeReturnTaint(
|
|
571
|
+
callNode: Parser.SyntaxNode,
|
|
572
|
+
perArgTaint: Set<TaintKind>[],
|
|
573
|
+
ctx: CallResolutionContext,
|
|
574
|
+
callerFile: string,
|
|
575
|
+
): CalleeResult | null {
|
|
576
|
+
// Resolve the call target (try JS first, then Python fallback)
|
|
577
|
+
let resolved: { type: string; name: string; object?: string } | null = resolveCallTarget(callNode)
|
|
578
|
+
if (!resolved) {
|
|
579
|
+
const pyResolved = resolvePythonCallTarget(callNode)
|
|
580
|
+
if (pyResolved) {
|
|
581
|
+
resolved = { type: pyResolved.type, name: pyResolved.name, object: pyResolved.object }
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
if (!resolved) return null
|
|
585
|
+
|
|
586
|
+
const { crossFileIndex, allASTs, callStack, maxDepth } = ctx
|
|
587
|
+
|
|
588
|
+
// Try to find the function definition
|
|
589
|
+
let funcDef = crossFileIndex.resolveImport.get(`${callerFile}:${resolved.name}`)
|
|
590
|
+
|
|
591
|
+
// Also try namespace.method pattern
|
|
592
|
+
if (!funcDef && resolved.type === 'member' && resolved.object) {
|
|
593
|
+
funcDef = crossFileIndex.resolveImport.get(
|
|
594
|
+
`${callerFile}:${resolved.object}.${resolved.name}`,
|
|
595
|
+
)
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
// Also try local function
|
|
599
|
+
if (!funcDef) {
|
|
600
|
+
funcDef = crossFileIndex.functionDefs.get(`${callerFile}:${resolved.name}`)
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
if (!funcDef) return null
|
|
604
|
+
|
|
605
|
+
// Recursion guard
|
|
606
|
+
const callKey = `${funcDef.filePath}:${funcDef.functionName}`
|
|
607
|
+
if (callStack.has(callKey)) return null
|
|
608
|
+
if (callStack.size >= maxDepth) return null
|
|
609
|
+
|
|
610
|
+
// Check cache
|
|
611
|
+
const taintSig = perArgTaint.map(s => [...s].sort().join(',')).join('|')
|
|
612
|
+
const cacheKey = `${callKey}:${taintSig}`
|
|
613
|
+
if (ctx.resultCache.has(cacheKey)) {
|
|
614
|
+
return ctx.resultCache.get(cacheKey)!
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
// Phase 8: Try summary-based composition first (much faster than full analysis)
|
|
618
|
+
if (ctx.summaryCache && ctx.analysisCache) {
|
|
619
|
+
const summary = getOrBuildSummary(funcDef, ctx)
|
|
620
|
+
if (summary) {
|
|
621
|
+
const result = composeSummary(summary, perArgTaint, callerFile, callNode)
|
|
622
|
+
ctx.resultCache.set(cacheKey, result.returnTaint.size > 0 || result.calleeFindings.length > 0 ? result : null)
|
|
623
|
+
return result.returnTaint.size > 0 || result.calleeFindings.length > 0 ? result : null
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
// Get callee AST
|
|
628
|
+
const calleeAST = allASTs.get(funcDef.filePath)
|
|
629
|
+
if (!calleeAST) {
|
|
630
|
+
ctx.resultCache.set(cacheKey, null)
|
|
631
|
+
return null
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
// Phase 8: Use analysis cache if available, otherwise recompute
|
|
635
|
+
const fileCache = ctx.analysisCache?.get(funcDef.filePath)
|
|
636
|
+
const calleeCFGs = fileCache?.cfgs ?? buildFileCFGs(calleeAST)
|
|
637
|
+
|
|
638
|
+
// Find the specific function's CFG
|
|
639
|
+
let calleeCFG: CFG | undefined
|
|
640
|
+
for (const [name, cfg] of calleeCFGs) {
|
|
641
|
+
if (name === funcDef.functionName) {
|
|
642
|
+
calleeCFG = cfg
|
|
643
|
+
break
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
if (!calleeCFG) {
|
|
648
|
+
ctx.resultCache.set(cacheKey, null)
|
|
649
|
+
return null
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
// Create synthetic sources from argument taint → parameter names
|
|
653
|
+
const syntheticSources: TaintSource[] = []
|
|
654
|
+
for (let i = 0; i < funcDef.params.length && i < perArgTaint.length; i++) {
|
|
655
|
+
const paramTaint = perArgTaint[i]
|
|
656
|
+
if (paramTaint.size === 0) continue
|
|
657
|
+
|
|
658
|
+
// Find the parameter's AST node in the callee function
|
|
659
|
+
const paramNode = findParamNode(funcDef.astNode, funcDef.params[i])
|
|
660
|
+
if (!paramNode) continue
|
|
661
|
+
|
|
662
|
+
syntheticSources.push({
|
|
663
|
+
node: paramNode,
|
|
664
|
+
line: paramNode.startPosition.row + 1,
|
|
665
|
+
variable: funcDef.params[i],
|
|
666
|
+
expression: `${funcDef.functionName}(${funcDef.params[i]})`,
|
|
667
|
+
sourceType: 'external_api', // synthetic source
|
|
668
|
+
taintKinds: new Set(paramTaint),
|
|
669
|
+
confidence: 'high',
|
|
670
|
+
})
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
if (syntheticSources.length === 0) {
|
|
674
|
+
ctx.resultCache.set(cacheKey, null)
|
|
675
|
+
return null
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
// Phase 8: Get sinks and sanitizers from cache or recompute
|
|
679
|
+
const calleeSinks = fileCache?.sinks ?? classifySinks(calleeAST)
|
|
680
|
+
const calleeSanitizers = fileCache?.sanitizers ?? buildSanitizerRegistry(calleeAST)
|
|
681
|
+
|
|
682
|
+
// Map synthetic parameter sources to the CFG entry node.
|
|
683
|
+
// Parameters aren't inside any statement node, so mapSourcesToCFG would fail.
|
|
684
|
+
// Instead, seed them at the entry node — parameters are defined at function entry.
|
|
685
|
+
let entryNodeId = -1
|
|
686
|
+
for (const [id, node] of calleeCFG.nodes) {
|
|
687
|
+
if (node.kind === 'entry') {
|
|
688
|
+
entryNodeId = id
|
|
689
|
+
break
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
if (entryNodeId === -1) {
|
|
693
|
+
ctx.resultCache.set(cacheKey, null)
|
|
694
|
+
return null
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
const sourceMappings: SourceMapping[] = syntheticSources.map(source => ({
|
|
698
|
+
source,
|
|
699
|
+
cfgNodeId: entryNodeId,
|
|
700
|
+
}))
|
|
701
|
+
// Phase 8 (Part 4): use cached CFG node map for O(D) lookup
|
|
702
|
+
const calleeCFGNodeMap = fileCache?.cfgNodeMaps.get(funcDef.functionName)
|
|
703
|
+
const sinkMappings = mapSinksToCFG(calleeCFG, calleeSinks, calleeCFGNodeMap)
|
|
704
|
+
const sanitizerMap = buildSanitizerMap(calleeCFG, calleeSanitizers, calleeCFGNodeMap)
|
|
705
|
+
|
|
706
|
+
// Push to call stack
|
|
707
|
+
callStack.add(callKey)
|
|
708
|
+
|
|
709
|
+
// Create child context for deeper calls
|
|
710
|
+
const childContext: CallResolutionContext = {
|
|
711
|
+
...ctx,
|
|
712
|
+
callStack: new Set(callStack),
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
// Run fixpoint ONCE — used for both callee findings and return taint
|
|
716
|
+
const calleeConstants = calleeCFG.functionNode ? collectConstants(calleeCFG.functionNode) : new Map()
|
|
717
|
+
const outStates = runFixpointInternal(calleeCFG, sourceMappings, sanitizerMap, funcDef.filePath, childContext, calleeConstants)
|
|
718
|
+
const calleeFindings = deduplicateFindings(
|
|
719
|
+
checkSinksInternal(calleeCFG, outStates, sinkMappings, sourceMappings, funcDef.filePath),
|
|
720
|
+
)
|
|
721
|
+
|
|
722
|
+
// Pop from call stack
|
|
723
|
+
callStack.delete(callKey)
|
|
724
|
+
|
|
725
|
+
// Extract return-value taint from the already-computed out-states
|
|
726
|
+
const returnTaint = extractReturnTaint(calleeCFG, outStates, calleeSanitizers, childContext, funcDef.filePath)
|
|
727
|
+
|
|
728
|
+
// Build path steps
|
|
729
|
+
const steps: TaintStep[] = [
|
|
730
|
+
{
|
|
731
|
+
nodeId: 0,
|
|
732
|
+
line: callNode.startPosition.row + 1,
|
|
733
|
+
variable: funcDef.functionName,
|
|
734
|
+
taintKinds: new Set(returnTaint),
|
|
735
|
+
description: `CALL_ENTRY ${funcDef.functionName}() in ${funcDef.filePath}`,
|
|
736
|
+
stepType: 'call_entry',
|
|
737
|
+
filePath: funcDef.filePath,
|
|
738
|
+
functionName: funcDef.functionName,
|
|
739
|
+
},
|
|
740
|
+
{
|
|
741
|
+
nodeId: 0,
|
|
742
|
+
line: callNode.startPosition.row + 1,
|
|
743
|
+
variable: funcDef.functionName,
|
|
744
|
+
taintKinds: new Set(returnTaint),
|
|
745
|
+
description: `CALL_RETURN ${funcDef.functionName}() → ${[...returnTaint].join(',')}`,
|
|
746
|
+
stepType: 'call_return',
|
|
747
|
+
filePath: callerFile,
|
|
748
|
+
functionName: funcDef.functionName,
|
|
749
|
+
},
|
|
750
|
+
]
|
|
751
|
+
|
|
752
|
+
const calleeResult: CalleeResult = {
|
|
753
|
+
returnTaint,
|
|
754
|
+
steps,
|
|
755
|
+
calleeFindings,
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
ctx.resultCache.set(cacheKey, calleeResult)
|
|
759
|
+
return calleeResult
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
/**
|
|
763
|
+
* Extract taint kinds from return statements using pre-computed out-states.
|
|
764
|
+
* Walks all return statements in the CFG and resolves expression taint.
|
|
765
|
+
*/
|
|
766
|
+
function extractReturnTaint(
|
|
767
|
+
cfg: CFG,
|
|
768
|
+
outStates: Map<number, TaintState>,
|
|
769
|
+
sanitizers: TaintSanitizer[],
|
|
770
|
+
callContext?: CallResolutionContext,
|
|
771
|
+
filePath?: string,
|
|
772
|
+
): Set<TaintKind> {
|
|
773
|
+
const returnTaint = new Set<TaintKind>()
|
|
774
|
+
|
|
775
|
+
for (const [, cfgNode] of cfg.nodes) {
|
|
776
|
+
if (!cfgNode.astNode) continue
|
|
777
|
+
const ast = cfgNode.astNode
|
|
778
|
+
|
|
779
|
+
if (ast.type !== 'return_statement') continue
|
|
780
|
+
|
|
781
|
+
const returnExpr = ast.namedChildren[0]
|
|
782
|
+
if (!returnExpr) continue
|
|
783
|
+
|
|
784
|
+
// Get the taint state at this node: merge predecessors + own out-state
|
|
785
|
+
const predIds = getPredecessors(cfg, cfgNode.id)
|
|
786
|
+
const predStates: TaintState[] = []
|
|
787
|
+
for (const pId of predIds) {
|
|
788
|
+
const ps = outStates.get(pId)
|
|
789
|
+
if (ps) predStates.push(ps)
|
|
790
|
+
}
|
|
791
|
+
const nodeInState = mergeStates(predStates)
|
|
792
|
+
const nodeState = mergeStates([nodeInState, outStates.get(cfgNode.id) ?? new Map()])
|
|
793
|
+
|
|
794
|
+
const exprTaint = resolveExpressionTaint(returnExpr, nodeState, sanitizers, callContext, filePath)
|
|
795
|
+
for (const k of exprTaint) returnTaint.add(k)
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
return returnTaint
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
/** Find a parameter's AST node in a function definition. */
|
|
802
|
+
function findParamNode(
|
|
803
|
+
fnNode: Parser.SyntaxNode,
|
|
804
|
+
paramName: string,
|
|
805
|
+
): Parser.SyntaxNode | null {
|
|
806
|
+
const params = fnNode.childForFieldName('parameters')
|
|
807
|
+
if (!params) return null
|
|
808
|
+
|
|
809
|
+
for (const child of params.namedChildren) {
|
|
810
|
+
if (child.type === 'identifier' && child.text === paramName) return child
|
|
811
|
+
// JS/TS param types
|
|
812
|
+
if (child.type === 'required_parameter' || child.type === 'optional_parameter') {
|
|
813
|
+
const pattern = child.childForFieldName('pattern')
|
|
814
|
+
if (pattern?.type === 'identifier' && pattern.text === paramName) return pattern
|
|
815
|
+
}
|
|
816
|
+
if (child.type === 'assignment_pattern') {
|
|
817
|
+
const left = child.childForFieldName('left')
|
|
818
|
+
if (left?.type === 'identifier' && left.text === paramName) return left
|
|
819
|
+
}
|
|
820
|
+
// Python param types
|
|
821
|
+
if (child.type === 'typed_parameter' || child.type === 'default_parameter' ||
|
|
822
|
+
child.type === 'typed_default_parameter') {
|
|
823
|
+
const nameNode = child.namedChildren.find(c => c.type === 'identifier')
|
|
824
|
+
if (nameNode && nameNode.text === paramName) return nameNode
|
|
825
|
+
}
|
|
826
|
+
if (child.type === 'list_splat_pattern' || child.type === 'dictionary_splat_pattern') {
|
|
827
|
+
const ident = child.namedChildren.find(c => c.type === 'identifier')
|
|
828
|
+
if (ident && ident.text === paramName) return ident
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
// Fallback: search for identifier in params subtree
|
|
833
|
+
let found: Parser.SyntaxNode | null = null
|
|
834
|
+
walkAST(params, (node) => {
|
|
835
|
+
if (node.type === 'identifier' && node.text === paramName && !found) {
|
|
836
|
+
found = node
|
|
837
|
+
return true
|
|
838
|
+
}
|
|
839
|
+
return false
|
|
840
|
+
})
|
|
841
|
+
return found
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
/**
|
|
845
|
+
* Find a sanitizer whose AST node matches this call expression.
|
|
846
|
+
*/
|
|
847
|
+
function findMatchingSanitizer(
|
|
848
|
+
callNode: Parser.SyntaxNode,
|
|
849
|
+
sanitizers: TaintSanitizer[],
|
|
850
|
+
): TaintSanitizer | null {
|
|
851
|
+
for (const s of sanitizers) {
|
|
852
|
+
if (s.node.id === callNode.id) return s
|
|
853
|
+
// Also check if the sanitizer's node is a parent/ancestor of this call
|
|
854
|
+
if (isDescendantOf(callNode, s.node)) return s
|
|
855
|
+
}
|
|
856
|
+
return null
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
/** Check if a node represents a literal value (no taint). */
|
|
860
|
+
function isLiteral(node: Parser.SyntaxNode): boolean {
|
|
861
|
+
switch (node.type) {
|
|
862
|
+
case 'string':
|
|
863
|
+
// Python f-strings have type 'string' but contain interpolation children — NOT a literal
|
|
864
|
+
if (node.descendantsOfType('interpolation').length > 0) return false
|
|
865
|
+
return true
|
|
866
|
+
case 'number':
|
|
867
|
+
case 'true':
|
|
868
|
+
case 'false':
|
|
869
|
+
case 'null':
|
|
870
|
+
case 'undefined':
|
|
871
|
+
case 'regex':
|
|
872
|
+
// Python literals
|
|
873
|
+
case 'integer':
|
|
874
|
+
case 'float':
|
|
875
|
+
case 'none':
|
|
876
|
+
return true
|
|
877
|
+
default:
|
|
878
|
+
return false
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
// ============================================================================
|
|
883
|
+
// Statement-level Transfer Function
|
|
884
|
+
// ============================================================================
|
|
885
|
+
|
|
886
|
+
/**
|
|
887
|
+
* Apply the transfer function for a single CFG node.
|
|
888
|
+
* Given the incoming taint state, compute the outgoing taint state.
|
|
889
|
+
*/
|
|
890
|
+
export function applyTransfer(
|
|
891
|
+
cfgNode: CFGNode,
|
|
892
|
+
inState: TaintState,
|
|
893
|
+
sanitizerMap: Map<number, TaintSanitizer[]>,
|
|
894
|
+
callContext?: CallResolutionContext,
|
|
895
|
+
callerFile?: string,
|
|
896
|
+
constantMap?: ConstantMap,
|
|
897
|
+
): TaintState {
|
|
898
|
+
// Phase 8 (3b): skip clone for no-op nodes — entry, exit, and nodes without AST
|
|
899
|
+
if (!cfgNode.astNode) return inState
|
|
900
|
+
if (cfgNode.kind === 'entry' || cfgNode.kind === 'exit') return inState
|
|
901
|
+
|
|
902
|
+
// Phase 8 (3b): condition nodes with no defs and no sanitizers are read-only
|
|
903
|
+
if (cfgNode.kind === 'condition' && cfgNode.defs.size === 0) {
|
|
904
|
+
const hasSanitizers = sanitizerMap.has(cfgNode.id)
|
|
905
|
+
if (!hasSanitizers) return inState
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
const outState = cloneState(inState)
|
|
909
|
+
const sanitizers = sanitizerMap.get(cfgNode.id) ?? []
|
|
910
|
+
const ast = cfgNode.astNode
|
|
911
|
+
|
|
912
|
+
applyNodeTransfer(ast, outState, sanitizers, callContext, callerFile, constantMap)
|
|
913
|
+
|
|
914
|
+
return outState
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
/**
|
|
918
|
+
* Recursively apply transfer for an AST node within a CFG statement.
|
|
919
|
+
*/
|
|
920
|
+
function applyNodeTransfer(
|
|
921
|
+
node: Parser.SyntaxNode,
|
|
922
|
+
state: TaintState,
|
|
923
|
+
sanitizers: TaintSanitizer[],
|
|
924
|
+
callContext?: CallResolutionContext,
|
|
925
|
+
callerFile?: string,
|
|
926
|
+
constantMap?: ConstantMap,
|
|
927
|
+
): void {
|
|
928
|
+
// Helper for expression taint resolution with context forwarding
|
|
929
|
+
const resolve = (expr: Parser.SyntaxNode) =>
|
|
930
|
+
resolveExpressionTaint(expr, state, sanitizers, callContext, callerFile)
|
|
931
|
+
|
|
932
|
+
// Variable declaration: const x = <expr>
|
|
933
|
+
if (node.type === 'lexical_declaration' || node.type === 'variable_declaration') {
|
|
934
|
+
for (const child of node.namedChildren) {
|
|
935
|
+
applyNodeTransfer(child, state, sanitizers, callContext, callerFile, constantMap)
|
|
936
|
+
}
|
|
937
|
+
return
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
if (node.type === 'variable_declarator') {
|
|
941
|
+
const nameNode = node.childForFieldName('name')
|
|
942
|
+
const valueNode = node.childForFieldName('value')
|
|
943
|
+
if (!nameNode || !valueNode) return
|
|
944
|
+
|
|
945
|
+
if (nameNode.type === 'identifier') {
|
|
946
|
+
// Constant propagation: if RHS is entirely constant-derived, clear taint
|
|
947
|
+
if (constantMap && isConstantExpression(valueNode, constantMap)) {
|
|
948
|
+
deleteTaint(state, nameNode.text)
|
|
949
|
+
return
|
|
950
|
+
}
|
|
951
|
+
// Simple: const x = expr
|
|
952
|
+
const rhsTaint = resolve(valueNode)
|
|
953
|
+
if (rhsTaint.size > 0) {
|
|
954
|
+
setTaint(state, nameNode.text, rhsTaint)
|
|
955
|
+
} else {
|
|
956
|
+
deleteTaint(state, nameNode.text)
|
|
957
|
+
}
|
|
958
|
+
} else if (nameNode.type === 'object_pattern' || nameNode.type === 'array_pattern') {
|
|
959
|
+
// Destructuring: const { a, b } = expr
|
|
960
|
+
const rhsTaint = resolve(valueNode)
|
|
961
|
+
const rhsVarName = valueNode.type === 'identifier' ? valueNode.text
|
|
962
|
+
: (valueNode.type === 'member_expression' ? getMemberRoot(valueNode) : null)
|
|
963
|
+
applyDestructuringTaint(nameNode, rhsTaint, state, rhsVarName ?? undefined)
|
|
964
|
+
}
|
|
965
|
+
return
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
// Assignment: x = expr
|
|
969
|
+
// Python: 'assignment', JS: 'assignment_expression'
|
|
970
|
+
if (node.type === 'assignment_expression' || node.type === 'assignment') {
|
|
971
|
+
const left = node.childForFieldName('left')
|
|
972
|
+
const right = node.childForFieldName('right')
|
|
973
|
+
if (!left || !right) return
|
|
974
|
+
|
|
975
|
+
if (left.type === 'identifier') {
|
|
976
|
+
// Constant propagation: if RHS is entirely constant-derived, clear taint
|
|
977
|
+
if (constantMap && isConstantExpression(right, constantMap)) {
|
|
978
|
+
deleteTaint(state, left.text)
|
|
979
|
+
return
|
|
980
|
+
}
|
|
981
|
+
const rhsTaint = resolve(right)
|
|
982
|
+
if (rhsTaint.size > 0) {
|
|
983
|
+
setTaint(state, left.text, rhsTaint)
|
|
984
|
+
} else {
|
|
985
|
+
deleteTaint(state, left.text)
|
|
986
|
+
}
|
|
987
|
+
} else if (left.type === 'object_pattern' || left.type === 'array_pattern' ||
|
|
988
|
+
left.type === 'tuple_pattern' || left.type === 'list_pattern' ||
|
|
989
|
+
left.type === 'pattern_list' || left.type === 'tuple' || left.type === 'list') {
|
|
990
|
+
// Destructuring / Python unpacking: a, b = expr
|
|
991
|
+
const rhsTaint = resolve(right)
|
|
992
|
+
const rhsVarName = right.type === 'identifier' ? right.text
|
|
993
|
+
: (right.type === 'member_expression' || right.type === 'attribute' ? getMemberRoot(right) : null)
|
|
994
|
+
applyDestructuringTaint(left, rhsTaint, state, rhsVarName ?? undefined)
|
|
995
|
+
} else if (left.type === 'member_expression' || left.type === 'attribute') {
|
|
996
|
+
// obj.prop = val — field-level taint
|
|
997
|
+
// Python: 'attribute' with fields 'object'/'attribute', JS: 'member_expression' with 'object'/'property'
|
|
998
|
+
const obj = left.childForFieldName('object')
|
|
999
|
+
const prop = left.type === 'attribute'
|
|
1000
|
+
? left.childForFieldName('attribute')
|
|
1001
|
+
: left.childForFieldName('property')
|
|
1002
|
+
if (obj?.type === 'identifier' && prop && (prop.type === 'property_identifier' || prop.type === 'identifier')) {
|
|
1003
|
+
// One-level field assignment: obj.field = val
|
|
1004
|
+
const rhsTaint = resolve(right)
|
|
1005
|
+
if (rhsTaint.size > 0) {
|
|
1006
|
+
setFieldTaint(state, obj.text, prop.text, rhsTaint)
|
|
1007
|
+
}
|
|
1008
|
+
} else {
|
|
1009
|
+
// Deep chain or computed: fall back to root object taint
|
|
1010
|
+
const root = getMemberRoot(left)
|
|
1011
|
+
if (root) {
|
|
1012
|
+
const rhsTaint = resolve(right)
|
|
1013
|
+
// Phase 8: clone before mutating — getTaint may return internal Set reference
|
|
1014
|
+
const existing = new Set(getTaint(state, root) ?? [])
|
|
1015
|
+
for (const k of rhsTaint) existing.add(k)
|
|
1016
|
+
if (existing.size > 0) setTaint(state, root, existing)
|
|
1017
|
+
}
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
return
|
|
1021
|
+
}
|
|
1022
|
+
|
|
1023
|
+
// Augmented assignment: x += expr
|
|
1024
|
+
// Python: 'augmented_assignment', JS: 'augmented_assignment_expression'
|
|
1025
|
+
if (node.type === 'augmented_assignment_expression' || node.type === 'augmented_assignment') {
|
|
1026
|
+
const left = node.childForFieldName('left')
|
|
1027
|
+
const right = node.childForFieldName('right')
|
|
1028
|
+
if (!left || !right) return
|
|
1029
|
+
|
|
1030
|
+
if (left.type === 'identifier') {
|
|
1031
|
+
const rhsTaint = resolve(right)
|
|
1032
|
+
// Phase 8: clone before mutating — getTaint may return internal Set reference
|
|
1033
|
+
const existing = new Set(getTaint(state, left.text) ?? [])
|
|
1034
|
+
for (const k of rhsTaint) existing.add(k)
|
|
1035
|
+
if (existing.size > 0) setTaint(state, left.text, existing)
|
|
1036
|
+
}
|
|
1037
|
+
return
|
|
1038
|
+
}
|
|
1039
|
+
|
|
1040
|
+
// Expression statement: unwrap the inner expression
|
|
1041
|
+
if (node.type === 'expression_statement') {
|
|
1042
|
+
for (const child of node.namedChildren) {
|
|
1043
|
+
applyNodeTransfer(child, state, sanitizers, callContext, callerFile, constantMap)
|
|
1044
|
+
}
|
|
1045
|
+
return
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1048
|
+
// For-in header: captures iteration variable taint from the right side
|
|
1049
|
+
if (node.type === 'for_in_statement') {
|
|
1050
|
+
const left = node.childForFieldName('left')
|
|
1051
|
+
const right = node.childForFieldName('right')
|
|
1052
|
+
if (left && right) {
|
|
1053
|
+
const rhsTaint = resolve(right)
|
|
1054
|
+
if (left.type === 'identifier') {
|
|
1055
|
+
if (rhsTaint.size > 0) setTaint(state, left.text, rhsTaint)
|
|
1056
|
+
} else {
|
|
1057
|
+
// Destructuring in for-of/for-in
|
|
1058
|
+
applyDestructuringTaint(left, rhsTaint, state)
|
|
1059
|
+
}
|
|
1060
|
+
}
|
|
1061
|
+
return
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
// Python for loop with left/right fields: for x in iterable
|
|
1065
|
+
if (node.type === 'for_statement') {
|
|
1066
|
+
const left = node.childForFieldName('left')
|
|
1067
|
+
const right = node.childForFieldName('right')
|
|
1068
|
+
if (left && right) {
|
|
1069
|
+
const rhsTaint = resolve(right)
|
|
1070
|
+
if (left.type === 'identifier') {
|
|
1071
|
+
if (rhsTaint.size > 0) setTaint(state, left.text, rhsTaint)
|
|
1072
|
+
} else {
|
|
1073
|
+
applyDestructuringTaint(left, rhsTaint, state)
|
|
1074
|
+
}
|
|
1075
|
+
}
|
|
1076
|
+
return
|
|
1077
|
+
}
|
|
1078
|
+
|
|
1079
|
+
// Python with statement: with expr as var
|
|
1080
|
+
if (node.type === 'with_statement') {
|
|
1081
|
+
// The with_clause may contain 'as_pattern' nodes: expr as name
|
|
1082
|
+
for (const child of node.namedChildren) {
|
|
1083
|
+
if (child.type === 'with_clause') {
|
|
1084
|
+
for (const item of child.namedChildren) {
|
|
1085
|
+
if (item.type === 'with_item' || item.type === 'as_pattern') {
|
|
1086
|
+
const expr = item.namedChildren[0]
|
|
1087
|
+
const alias = item.namedChildren[1]
|
|
1088
|
+
if (expr && alias?.type === 'identifier') {
|
|
1089
|
+
const exprTaint = resolve(expr)
|
|
1090
|
+
if (exprTaint.size > 0) setTaint(state, alias.text, exprTaint)
|
|
1091
|
+
}
|
|
1092
|
+
}
|
|
1093
|
+
}
|
|
1094
|
+
}
|
|
1095
|
+
}
|
|
1096
|
+
return
|
|
1097
|
+
}
|
|
1098
|
+
|
|
1099
|
+
// Standalone call expression: trigger expression resolution so cross-file
|
|
1100
|
+
// analysis discovers callee findings (sinks reached inside the called function).
|
|
1101
|
+
// Without this, standalone calls like `query(input)` would be silently ignored.
|
|
1102
|
+
// Python: 'call', JS: 'call_expression'
|
|
1103
|
+
if (node.type === 'call_expression' || node.type === 'call') {
|
|
1104
|
+
// Inject taint into callback parameters for promise chains and callback APIs.
|
|
1105
|
+
// This must happen BEFORE resolve() so the taint state is available when
|
|
1106
|
+
// analyzing the callback body's expressions.
|
|
1107
|
+
injectPromiseChainTaint(node, state)
|
|
1108
|
+
injectCallbackTaint(node, state)
|
|
1109
|
+
resolve(node)
|
|
1110
|
+
return
|
|
1111
|
+
}
|
|
1112
|
+
|
|
1113
|
+
// Return / throw: no defs, but the expression is "used"
|
|
1114
|
+
// (no state changes needed — uses are tracked by def-use)
|
|
1115
|
+
}
|
|
1116
|
+
|
|
1117
|
+
/**
|
|
1118
|
+
* Apply taint from a destructuring pattern.
|
|
1119
|
+
* When rhsVarName is provided and object_pattern is used, field-specific
|
|
1120
|
+
* taint is extracted per destructured key. Otherwise falls back to whole-object taint.
|
|
1121
|
+
*/
|
|
1122
|
+
function applyDestructuringTaint(
|
|
1123
|
+
pattern: Parser.SyntaxNode,
|
|
1124
|
+
rhsTaint: Set<TaintKind>,
|
|
1125
|
+
state: TaintState,
|
|
1126
|
+
rhsVarName?: string,
|
|
1127
|
+
): void {
|
|
1128
|
+
if (rhsTaint.size === 0) return
|
|
1129
|
+
|
|
1130
|
+
if (pattern.type === 'identifier') {
|
|
1131
|
+
setTaint(state, pattern.text, new Set(rhsTaint))
|
|
1132
|
+
return
|
|
1133
|
+
}
|
|
1134
|
+
|
|
1135
|
+
if (pattern.type === 'object_pattern') {
|
|
1136
|
+
for (const prop of pattern.namedChildren) {
|
|
1137
|
+
if (prop.type === 'shorthand_property_identifier_pattern') {
|
|
1138
|
+
// { email } = obj → email gets field-specific taint from obj.email
|
|
1139
|
+
if (rhsVarName) {
|
|
1140
|
+
const fieldTaint = getFieldTaint(state, rhsVarName, prop.text)
|
|
1141
|
+
if (fieldTaint && fieldTaint.size > 0) {
|
|
1142
|
+
setTaint(state, prop.text, new Set(fieldTaint))
|
|
1143
|
+
} else {
|
|
1144
|
+
// Field has no taint — don't assign whole-object taint
|
|
1145
|
+
deleteTaint(state, prop.text)
|
|
1146
|
+
}
|
|
1147
|
+
} else {
|
|
1148
|
+
setTaint(state, prop.text, new Set(rhsTaint))
|
|
1149
|
+
}
|
|
1150
|
+
} else if (prop.type === 'pair_pattern') {
|
|
1151
|
+
// { email: e } = obj → e gets taint from obj.email
|
|
1152
|
+
const key = prop.childForFieldName('key')
|
|
1153
|
+
const value = prop.childForFieldName('value')
|
|
1154
|
+
if (value && rhsVarName && key) {
|
|
1155
|
+
const fieldName = key.text
|
|
1156
|
+
const fieldTaint = getFieldTaint(state, rhsVarName, fieldName)
|
|
1157
|
+
if (fieldTaint && fieldTaint.size > 0) {
|
|
1158
|
+
applyDestructuringTaint(value, fieldTaint, state)
|
|
1159
|
+
}
|
|
1160
|
+
} else if (value) {
|
|
1161
|
+
applyDestructuringTaint(value, rhsTaint, state)
|
|
1162
|
+
}
|
|
1163
|
+
} else if (prop.type === 'rest_pattern') {
|
|
1164
|
+
// { ...rest } = obj → rest gets whole-object taint (conservative)
|
|
1165
|
+
const inner = prop.namedChildren[0]
|
|
1166
|
+
if (inner) applyDestructuringTaint(inner, rhsTaint, state)
|
|
1167
|
+
}
|
|
1168
|
+
}
|
|
1169
|
+
return
|
|
1170
|
+
}
|
|
1171
|
+
|
|
1172
|
+
if (pattern.type === 'array_pattern') {
|
|
1173
|
+
for (const elem of pattern.namedChildren) {
|
|
1174
|
+
applyDestructuringTaint(elem, rhsTaint, state)
|
|
1175
|
+
}
|
|
1176
|
+
return
|
|
1177
|
+
}
|
|
1178
|
+
|
|
1179
|
+
// Python unpacking: a, b = expr / (a, b) = expr / [a, b] = expr
|
|
1180
|
+
if (pattern.type === 'tuple_pattern' || pattern.type === 'list_pattern' ||
|
|
1181
|
+
pattern.type === 'pattern_list' || pattern.type === 'expression_list' ||
|
|
1182
|
+
pattern.type === 'tuple' || pattern.type === 'list') {
|
|
1183
|
+
for (const elem of pattern.namedChildren) {
|
|
1184
|
+
applyDestructuringTaint(elem, rhsTaint, state)
|
|
1185
|
+
}
|
|
1186
|
+
return
|
|
1187
|
+
}
|
|
1188
|
+
|
|
1189
|
+
// Python list_splat_pattern: *rest
|
|
1190
|
+
if (pattern.type === 'list_splat_pattern') {
|
|
1191
|
+
const inner = pattern.namedChildren[0]
|
|
1192
|
+
if (inner) applyDestructuringTaint(inner, rhsTaint, state)
|
|
1193
|
+
return
|
|
1194
|
+
}
|
|
1195
|
+
|
|
1196
|
+
if (pattern.type === 'assignment_pattern') {
|
|
1197
|
+
const left = pattern.childForFieldName('left')
|
|
1198
|
+
if (left) applyDestructuringTaint(left, rhsTaint, state, rhsVarName)
|
|
1199
|
+
return
|
|
1200
|
+
}
|
|
1201
|
+
|
|
1202
|
+
if (pattern.type === 'rest_pattern') {
|
|
1203
|
+
const inner = pattern.namedChildren[0]
|
|
1204
|
+
if (inner) applyDestructuringTaint(inner, rhsTaint, state)
|
|
1205
|
+
}
|
|
1206
|
+
}
|
|
1207
|
+
|
|
1208
|
+
/** Get the root identifier of a member expression chain: `a.b.c` → 'a'. */
|
|
1209
|
+
function getMemberRoot(node: Parser.SyntaxNode): string | null {
|
|
1210
|
+
let current = node
|
|
1211
|
+
while (current.type === 'member_expression' || current.type === 'subscript_expression' ||
|
|
1212
|
+
current.type === 'attribute' || current.type === 'subscript') {
|
|
1213
|
+
const obj = current.childForFieldName('object')
|
|
1214
|
+
?? current.childForFieldName('value') // Python subscript uses 'value' field
|
|
1215
|
+
if (!obj) return null
|
|
1216
|
+
current = obj
|
|
1217
|
+
}
|
|
1218
|
+
return current.type === 'identifier' ? current.text : null
|
|
1219
|
+
}
|
|
1220
|
+
|
|
1221
|
+
// ============================================================================
|
|
1222
|
+
// Source / Sink Mapping
|
|
1223
|
+
// ============================================================================
|
|
1224
|
+
|
|
1225
|
+
export interface SourceMapping {
|
|
1226
|
+
source: TaintSource
|
|
1227
|
+
cfgNodeId: number
|
|
1228
|
+
}
|
|
1229
|
+
|
|
1230
|
+
export interface SinkMapping {
|
|
1231
|
+
sink: TaintSink
|
|
1232
|
+
cfgNodeId: number
|
|
1233
|
+
}
|
|
1234
|
+
|
|
1235
|
+
/**
|
|
1236
|
+
* Map sources to their containing CFG nodes.
|
|
1237
|
+
* Phase 8: accepts optional cfgNodeMap for O(D) lookup instead of O(N).
|
|
1238
|
+
*/
|
|
1239
|
+
export function mapSourcesToCFG(
|
|
1240
|
+
cfg: CFG,
|
|
1241
|
+
sources: TaintSource[],
|
|
1242
|
+
cfgNodeMap?: Map<number, CFGNode>,
|
|
1243
|
+
): SourceMapping[] {
|
|
1244
|
+
const mappings: SourceMapping[] = []
|
|
1245
|
+
for (const source of sources) {
|
|
1246
|
+
const cfgNode = cfgNodeMap
|
|
1247
|
+
? findContainingCFGNodeFast(cfgNodeMap, source.node)
|
|
1248
|
+
: findContainingCFGNode(cfg, source.node)
|
|
1249
|
+
if (cfgNode) {
|
|
1250
|
+
mappings.push({ source, cfgNodeId: cfgNode.id })
|
|
1251
|
+
}
|
|
1252
|
+
}
|
|
1253
|
+
return mappings
|
|
1254
|
+
}
|
|
1255
|
+
|
|
1256
|
+
/**
|
|
1257
|
+
* Map sinks to their containing CFG nodes.
|
|
1258
|
+
* Phase 8: accepts optional cfgNodeMap for O(D) lookup instead of O(N).
|
|
1259
|
+
*/
|
|
1260
|
+
export function mapSinksToCFG(
|
|
1261
|
+
cfg: CFG,
|
|
1262
|
+
sinks: TaintSink[],
|
|
1263
|
+
cfgNodeMap?: Map<number, CFGNode>,
|
|
1264
|
+
): SinkMapping[] {
|
|
1265
|
+
const mappings: SinkMapping[] = []
|
|
1266
|
+
for (const sink of sinks) {
|
|
1267
|
+
const cfgNode = cfgNodeMap
|
|
1268
|
+
? findContainingCFGNodeFast(cfgNodeMap, sink.node)
|
|
1269
|
+
: findContainingCFGNode(cfg, sink.node)
|
|
1270
|
+
if (cfgNode) {
|
|
1271
|
+
mappings.push({ sink, cfgNodeId: cfgNode.id })
|
|
1272
|
+
}
|
|
1273
|
+
}
|
|
1274
|
+
return mappings
|
|
1275
|
+
}
|
|
1276
|
+
|
|
1277
|
+
// ============================================================================
|
|
1278
|
+
// Worklist Algorithm
|
|
1279
|
+
// ============================================================================
|
|
1280
|
+
|
|
1281
|
+
const MAX_ITERATIONS = 1000
|
|
1282
|
+
|
|
1283
|
+
/**
|
|
1284
|
+
* Run the worklist fixpoint algorithm on a CFG.
|
|
1285
|
+
*
|
|
1286
|
+
* Seeds taint at source nodes, propagates forward through the CFG until
|
|
1287
|
+
* no more state changes occur (fixpoint). Returns the per-node out-states.
|
|
1288
|
+
*
|
|
1289
|
+
* Exported as runFixpointInternal for use by taint-summary.ts (Phase 8).
|
|
1290
|
+
*/
|
|
1291
|
+
export function runFixpointInternal(
|
|
1292
|
+
cfg: CFG,
|
|
1293
|
+
sourceMappings: SourceMapping[],
|
|
1294
|
+
sanitizerMap: Map<number, TaintSanitizer[]>,
|
|
1295
|
+
filePath: string,
|
|
1296
|
+
callContext?: CallResolutionContext,
|
|
1297
|
+
constantMap?: ConstantMap,
|
|
1298
|
+
): Map<number, TaintState> {
|
|
1299
|
+
// Per-node out-states
|
|
1300
|
+
const outStates = new Map<number, TaintState>()
|
|
1301
|
+
for (const [id] of cfg.nodes) {
|
|
1302
|
+
outStates.set(id, new Map())
|
|
1303
|
+
}
|
|
1304
|
+
|
|
1305
|
+
// Seed sources: set source variable taint in the source node's out-state
|
|
1306
|
+
const worklist = new Set<number>()
|
|
1307
|
+
|
|
1308
|
+
// Build a per-node source seed map so seeds survive worklist overwrites.
|
|
1309
|
+
// Without this, when a predecessor's propagation causes node N to be
|
|
1310
|
+
// re-processed, applyTransfer overwrites the seeded out-state with just
|
|
1311
|
+
// the propagated in-state — losing any source taint that was seeded here.
|
|
1312
|
+
const sourceSeeds = new Map<number, Array<{ variable: string; kinds: Set<TaintKind>; isDestructuring: boolean; defs: Set<string> }>>()
|
|
1313
|
+
|
|
1314
|
+
for (const { source, cfgNodeId } of sourceMappings) {
|
|
1315
|
+
const state = outStates.get(cfgNodeId)!
|
|
1316
|
+
const cfgNode = cfg.nodes.get(cfgNodeId)
|
|
1317
|
+
const varName = source.variable
|
|
1318
|
+
|
|
1319
|
+
const isDestructuring = varName.startsWith('{') || varName.startsWith('[')
|
|
1320
|
+
if (isDestructuring && cfgNode && cfgNode.defs.size > 0) {
|
|
1321
|
+
for (const defVar of cfgNode.defs) {
|
|
1322
|
+
// Phase 8: clone before mutating — getTaint may return internal Set reference
|
|
1323
|
+
const existing = new Set(getTaint(state, defVar) ?? [])
|
|
1324
|
+
for (const k of source.taintKinds) existing.add(k)
|
|
1325
|
+
setTaint(state, defVar, existing)
|
|
1326
|
+
}
|
|
1327
|
+
} else {
|
|
1328
|
+
// Phase 8: clone before mutating — getTaint may return internal Set reference
|
|
1329
|
+
const existing = new Set(getTaint(state, varName) ?? [])
|
|
1330
|
+
for (const k of source.taintKinds) existing.add(k)
|
|
1331
|
+
setTaint(state, varName, existing)
|
|
1332
|
+
|
|
1333
|
+
// For compound source variables (e.g. "req.body", "req.query"), also seed
|
|
1334
|
+
// the root identifier. The expression-level resolver walks member chains
|
|
1335
|
+
// from the root, so `req.body.email` resolves `req` → tainted → propagates.
|
|
1336
|
+
if (varName.includes('.')) {
|
|
1337
|
+
const root = varName.split('.')[0]
|
|
1338
|
+
const rootExisting = new Set(getTaint(state, root) ?? [])
|
|
1339
|
+
for (const k of source.taintKinds) rootExisting.add(k)
|
|
1340
|
+
setTaint(state, root, rootExisting)
|
|
1341
|
+
}
|
|
1342
|
+
}
|
|
1343
|
+
|
|
1344
|
+
// Record seed for re-injection during worklist
|
|
1345
|
+
if (!sourceSeeds.has(cfgNodeId)) sourceSeeds.set(cfgNodeId, [])
|
|
1346
|
+
sourceSeeds.get(cfgNodeId)!.push({
|
|
1347
|
+
variable: varName,
|
|
1348
|
+
kinds: new Set(source.taintKinds),
|
|
1349
|
+
isDestructuring,
|
|
1350
|
+
defs: cfgNode?.defs ?? new Set(),
|
|
1351
|
+
})
|
|
1352
|
+
|
|
1353
|
+
// Push successors to worklist (the source node's out-state is now seeded)
|
|
1354
|
+
for (const succ of getSuccessors(cfg, cfgNodeId)) {
|
|
1355
|
+
worklist.add(succ)
|
|
1356
|
+
}
|
|
1357
|
+
}
|
|
1358
|
+
|
|
1359
|
+
// Iterate to fixpoint
|
|
1360
|
+
let iterations = 0
|
|
1361
|
+
|
|
1362
|
+
while (worklist.size > 0 && iterations < MAX_ITERATIONS) {
|
|
1363
|
+
iterations++
|
|
1364
|
+
|
|
1365
|
+
// Pop first element from worklist
|
|
1366
|
+
const nodeId = worklist.values().next().value!
|
|
1367
|
+
worklist.delete(nodeId)
|
|
1368
|
+
|
|
1369
|
+
const cfgNode = cfg.nodes.get(nodeId)
|
|
1370
|
+
if (!cfgNode) continue
|
|
1371
|
+
|
|
1372
|
+
// Compute in-state = merge of all predecessor out-states
|
|
1373
|
+
// Phase 8 (3a): single-predecessor fast path — skip mergeStates allocation for ~70% of nodes
|
|
1374
|
+
const predIds = getPredecessors(cfg, nodeId)
|
|
1375
|
+
let inState: TaintState
|
|
1376
|
+
if (predIds.length === 1) {
|
|
1377
|
+
const ps = outStates.get(predIds[0])
|
|
1378
|
+
inState = ps ?? new Map()
|
|
1379
|
+
} else {
|
|
1380
|
+
const predStates: TaintState[] = []
|
|
1381
|
+
for (const pId of predIds) {
|
|
1382
|
+
const ps = outStates.get(pId)
|
|
1383
|
+
if (ps) predStates.push(ps)
|
|
1384
|
+
}
|
|
1385
|
+
inState = mergeStates(predStates)
|
|
1386
|
+
}
|
|
1387
|
+
|
|
1388
|
+
// Apply transfer function
|
|
1389
|
+
const newOutState = applyTransfer(cfgNode, inState, sanitizerMap, callContext, filePath, constantMap)
|
|
1390
|
+
|
|
1391
|
+
// Re-inject source seeds: if this node has sources, ensure their taint
|
|
1392
|
+
// survives the transfer function (which only sees predecessor in-state).
|
|
1393
|
+
// Phase 8: clone before mutating — getTaint may return internal Set reference
|
|
1394
|
+
const seeds = sourceSeeds.get(nodeId)
|
|
1395
|
+
if (seeds) {
|
|
1396
|
+
for (const seed of seeds) {
|
|
1397
|
+
if (seed.isDestructuring && seed.defs.size > 0) {
|
|
1398
|
+
for (const defVar of seed.defs) {
|
|
1399
|
+
const existing = new Set(getTaint(newOutState, defVar) ?? [])
|
|
1400
|
+
for (const k of seed.kinds) existing.add(k)
|
|
1401
|
+
setTaint(newOutState, defVar, existing)
|
|
1402
|
+
}
|
|
1403
|
+
} else {
|
|
1404
|
+
const existing = new Set(getTaint(newOutState, seed.variable) ?? [])
|
|
1405
|
+
for (const k of seed.kinds) existing.add(k)
|
|
1406
|
+
setTaint(newOutState, seed.variable, existing)
|
|
1407
|
+
|
|
1408
|
+
if (seed.variable.includes('.')) {
|
|
1409
|
+
const root = seed.variable.split('.')[0]
|
|
1410
|
+
const rootExisting = new Set(getTaint(newOutState, root) ?? [])
|
|
1411
|
+
for (const k of seed.kinds) rootExisting.add(k)
|
|
1412
|
+
setTaint(newOutState, root, rootExisting)
|
|
1413
|
+
}
|
|
1414
|
+
}
|
|
1415
|
+
}
|
|
1416
|
+
}
|
|
1417
|
+
|
|
1418
|
+
// Check if state changed
|
|
1419
|
+
const oldOutState = outStates.get(nodeId)!
|
|
1420
|
+
// Phase 8 (3d): identity check — if applyTransfer returned same ref, no change possible
|
|
1421
|
+
if (newOutState !== oldOutState && !statesEqual(newOutState, oldOutState)) {
|
|
1422
|
+
outStates.set(nodeId, newOutState)
|
|
1423
|
+
// Push all successors to worklist
|
|
1424
|
+
for (const succ of getSuccessors(cfg, nodeId)) {
|
|
1425
|
+
worklist.add(succ)
|
|
1426
|
+
}
|
|
1427
|
+
}
|
|
1428
|
+
}
|
|
1429
|
+
|
|
1430
|
+
if (iterations >= MAX_ITERATIONS) {
|
|
1431
|
+
console.warn(`[taint] Worklist hit iteration limit (${MAX_ITERATIONS}) for ${cfg.functionName} — results may be incomplete`)
|
|
1432
|
+
}
|
|
1433
|
+
|
|
1434
|
+
return outStates
|
|
1435
|
+
}
|
|
1436
|
+
|
|
1437
|
+
/**
|
|
1438
|
+
* Check sinks for tainted data given computed out-states.
|
|
1439
|
+
* Returns findings for each source→sink pair with matching taint kinds.
|
|
1440
|
+
*
|
|
1441
|
+
* Exported as checkSinksInternal for use by taint-summary.ts (Phase 8).
|
|
1442
|
+
*/
|
|
1443
|
+
export function checkSinksInternal(
|
|
1444
|
+
cfg: CFG,
|
|
1445
|
+
outStates: Map<number, TaintState>,
|
|
1446
|
+
sinkMappings: SinkMapping[],
|
|
1447
|
+
sourceMappings: SourceMapping[],
|
|
1448
|
+
filePath: string,
|
|
1449
|
+
): TaintFinding[] {
|
|
1450
|
+
const findings: TaintFinding[] = []
|
|
1451
|
+
|
|
1452
|
+
for (const { sink, cfgNodeId } of sinkMappings) {
|
|
1453
|
+
// The taint state at the sink is the in-state (merge of predecessor outs)
|
|
1454
|
+
const predIds = getPredecessors(cfg, cfgNodeId)
|
|
1455
|
+
const predStates: TaintState[] = []
|
|
1456
|
+
for (const pId of predIds) {
|
|
1457
|
+
const ps = outStates.get(pId)
|
|
1458
|
+
if (ps) predStates.push(ps)
|
|
1459
|
+
}
|
|
1460
|
+
// Also include the sink node's own out-state predecessor contributions
|
|
1461
|
+
// But more importantly, check the in-state to the sink node
|
|
1462
|
+
const sinkInState = mergeStates(predStates)
|
|
1463
|
+
|
|
1464
|
+
// Also check the out-state of the sink's own node (for cases where
|
|
1465
|
+
// source and sink are in the same statement or sink uses vars from its own node)
|
|
1466
|
+
const sinkOutState = outStates.get(cfgNodeId) ?? new Map()
|
|
1467
|
+
|
|
1468
|
+
// Merge both for maximum coverage
|
|
1469
|
+
const combined = mergeStates([sinkInState, sinkOutState])
|
|
1470
|
+
|
|
1471
|
+
// Find which variables at this sink are tainted with matching kinds
|
|
1472
|
+
const sinkNode = cfg.nodes.get(cfgNodeId)
|
|
1473
|
+
if (!sinkNode) continue
|
|
1474
|
+
|
|
1475
|
+
// Get all variables used by the sink expression
|
|
1476
|
+
const sinkUses = extractSinkUses(sink, sinkNode)
|
|
1477
|
+
|
|
1478
|
+
// Extract field-level accesses from the sink for precise taint checking
|
|
1479
|
+
const sinkFieldAccesses = extractSinkFieldAccesses(sink)
|
|
1480
|
+
|
|
1481
|
+
for (const varName of sinkUses) {
|
|
1482
|
+
// Check if this variable has a field-level access in the sink
|
|
1483
|
+
const fieldAccess = sinkFieldAccesses.get(varName)
|
|
1484
|
+
const varTaint = fieldAccess
|
|
1485
|
+
? getFieldTaint(combined, varName, fieldAccess)
|
|
1486
|
+
: getTaint(combined, varName)
|
|
1487
|
+
if (!varTaint || varTaint.size === 0) continue
|
|
1488
|
+
|
|
1489
|
+
// Find matching kinds: intersection of variable's taint and sink's vulnerable kinds
|
|
1490
|
+
const matchingKinds = new Set<TaintKind>()
|
|
1491
|
+
for (const k of varTaint) {
|
|
1492
|
+
if (sink.vulnerableToKinds.has(k)) {
|
|
1493
|
+
matchingKinds.add(k)
|
|
1494
|
+
}
|
|
1495
|
+
}
|
|
1496
|
+
|
|
1497
|
+
if (matchingKinds.size === 0) continue
|
|
1498
|
+
|
|
1499
|
+
// Find which source(s) contributed this taint
|
|
1500
|
+
for (const { source } of sourceMappings) {
|
|
1501
|
+
// Check if source's taint kinds overlap with matching kinds
|
|
1502
|
+
const sourceOverlap = new Set<TaintKind>()
|
|
1503
|
+
for (const k of matchingKinds) {
|
|
1504
|
+
if (source.taintKinds.has(k)) sourceOverlap.add(k)
|
|
1505
|
+
}
|
|
1506
|
+
if (sourceOverlap.size === 0) continue
|
|
1507
|
+
|
|
1508
|
+
// Reconstruct path
|
|
1509
|
+
const path = reconstructPath(
|
|
1510
|
+
cfg, outStates, source, sink, varName,
|
|
1511
|
+
sourceMappings, cfgNodeId,
|
|
1512
|
+
)
|
|
1513
|
+
|
|
1514
|
+
findings.push({
|
|
1515
|
+
source,
|
|
1516
|
+
sink,
|
|
1517
|
+
path,
|
|
1518
|
+
matchingKinds: sourceOverlap,
|
|
1519
|
+
functionName: cfg.functionName,
|
|
1520
|
+
filePath,
|
|
1521
|
+
})
|
|
1522
|
+
}
|
|
1523
|
+
}
|
|
1524
|
+
}
|
|
1525
|
+
|
|
1526
|
+
return findings
|
|
1527
|
+
}
|
|
1528
|
+
|
|
1529
|
+
/**
|
|
1530
|
+
* Propagate taint through a single function's CFG.
|
|
1531
|
+
*
|
|
1532
|
+
* Returns all taint findings (source→sink pairs with matching kinds).
|
|
1533
|
+
* When callContext is provided, calls to imported/local functions are
|
|
1534
|
+
* resolved inter-procedurally.
|
|
1535
|
+
*/
|
|
1536
|
+
export function propagateFunction(
|
|
1537
|
+
cfg: CFG,
|
|
1538
|
+
sourceMappings: SourceMapping[],
|
|
1539
|
+
sinkMappings: SinkMapping[],
|
|
1540
|
+
sanitizerMap: Map<number, TaintSanitizer[]>,
|
|
1541
|
+
filePath: string,
|
|
1542
|
+
callContext?: CallResolutionContext,
|
|
1543
|
+
): TaintFinding[] {
|
|
1544
|
+
// Collect constants for this function scope
|
|
1545
|
+
const constantMap = cfg.functionNode ? collectConstants(cfg.functionNode) : new Map()
|
|
1546
|
+
const outStates = runFixpointInternal(cfg, sourceMappings, sanitizerMap, filePath, callContext, constantMap)
|
|
1547
|
+
const findings = checkSinksInternal(cfg, outStates, sinkMappings, sourceMappings, filePath)
|
|
1548
|
+
return deduplicateFindings(findings)
|
|
1549
|
+
}
|
|
1550
|
+
|
|
1551
|
+
/**
|
|
1552
|
+
* Extract variables used by a sink expression.
|
|
1553
|
+
* Uses the CFG node's `uses` set plus any identifiers in the sink's AST.
|
|
1554
|
+
*
|
|
1555
|
+
* When `sink.relevantArgNodes` is set (e.g., SSRF sinks), only variables
|
|
1556
|
+
* within those specific argument nodes are collected — taint in other
|
|
1557
|
+
* arguments (like request body) won't trigger the finding.
|
|
1558
|
+
*/
|
|
1559
|
+
function extractSinkUses(sink: TaintSink, cfgNode: CFGNode): Set<string> {
|
|
1560
|
+
// When the sink specifies relevant arg nodes, ONLY collect vars from those nodes.
|
|
1561
|
+
// This prevents e.g. tainted body data from triggering SSRF (which requires tainted URL).
|
|
1562
|
+
if (sink.relevantArgNodes && sink.relevantArgNodes.length > 0) {
|
|
1563
|
+
const uses = new Set<string>()
|
|
1564
|
+
for (const argNode of sink.relevantArgNodes) {
|
|
1565
|
+
walkAST(argNode, (node) => {
|
|
1566
|
+
if (node.type === 'identifier') {
|
|
1567
|
+
uses.add(node.text)
|
|
1568
|
+
}
|
|
1569
|
+
if (node.type === 'arrow_function' || node.type === 'function_expression') return true
|
|
1570
|
+
return false
|
|
1571
|
+
})
|
|
1572
|
+
}
|
|
1573
|
+
return uses
|
|
1574
|
+
}
|
|
1575
|
+
|
|
1576
|
+
// Default: collect from entire sink expression + CFG node uses
|
|
1577
|
+
const uses = new Set<string>(cfgNode.uses)
|
|
1578
|
+
|
|
1579
|
+
walkAST(sink.node, (node) => {
|
|
1580
|
+
if (node.type === 'identifier') {
|
|
1581
|
+
uses.add(node.text)
|
|
1582
|
+
}
|
|
1583
|
+
if (node.type === 'arrow_function' || node.type === 'function_expression') return true
|
|
1584
|
+
return false
|
|
1585
|
+
})
|
|
1586
|
+
|
|
1587
|
+
return uses
|
|
1588
|
+
}
|
|
1589
|
+
|
|
1590
|
+
/**
|
|
1591
|
+
* Extract one-level field accesses from a sink expression.
|
|
1592
|
+
* Returns a map of root variable → field name for `obj.field` patterns.
|
|
1593
|
+
* Used for field-sensitive sink checking.
|
|
1594
|
+
*/
|
|
1595
|
+
function extractSinkFieldAccesses(sink: TaintSink): Map<string, string> {
|
|
1596
|
+
const accesses = new Map<string, string>()
|
|
1597
|
+
|
|
1598
|
+
// Walk the sink's arguments to find member expression patterns
|
|
1599
|
+
walkAST(sink.node, (node) => {
|
|
1600
|
+
if (node.type === 'member_expression') {
|
|
1601
|
+
const obj = node.childForFieldName('object')
|
|
1602
|
+
const prop = node.childForFieldName('property')
|
|
1603
|
+
if (obj?.type === 'identifier' && prop?.type === 'property_identifier') {
|
|
1604
|
+
accesses.set(obj.text, prop.text)
|
|
1605
|
+
}
|
|
1606
|
+
}
|
|
1607
|
+
if (node.type === 'arrow_function' || node.type === 'function_expression') return true
|
|
1608
|
+
return false
|
|
1609
|
+
})
|
|
1610
|
+
|
|
1611
|
+
return accesses
|
|
1612
|
+
}
|
|
1613
|
+
|
|
1614
|
+
// ============================================================================
|
|
1615
|
+
// Path Reconstruction
|
|
1616
|
+
// ============================================================================
|
|
1617
|
+
|
|
1618
|
+
/**
|
|
1619
|
+
* Reconstruct the path from source to sink via backward slice.
|
|
1620
|
+
*/
|
|
1621
|
+
function reconstructPath(
|
|
1622
|
+
cfg: CFG,
|
|
1623
|
+
outStates: Map<number, TaintState>,
|
|
1624
|
+
source: TaintSource,
|
|
1625
|
+
sink: TaintSink,
|
|
1626
|
+
sinkVar: string,
|
|
1627
|
+
sourceMappings: SourceMapping[],
|
|
1628
|
+
sinkNodeId: number,
|
|
1629
|
+
): TaintStep[] {
|
|
1630
|
+
const steps: TaintStep[] = []
|
|
1631
|
+
|
|
1632
|
+
// Step 1: Sink step
|
|
1633
|
+
const sinkNode = cfg.nodes.get(sinkNodeId)
|
|
1634
|
+
if (sinkNode) {
|
|
1635
|
+
steps.unshift({
|
|
1636
|
+
nodeId: sinkNodeId,
|
|
1637
|
+
line: sink.line,
|
|
1638
|
+
variable: sinkVar,
|
|
1639
|
+
taintKinds: new Set(sink.vulnerableToKinds),
|
|
1640
|
+
description: `SINK ${sink.expression.slice(0, 60)}`,
|
|
1641
|
+
stepType: 'sink',
|
|
1642
|
+
})
|
|
1643
|
+
}
|
|
1644
|
+
|
|
1645
|
+
// Step 2: Walk backward from sink to source
|
|
1646
|
+
const visited = new Set<number>()
|
|
1647
|
+
let currentVar = sinkVar
|
|
1648
|
+
let currentNodeId = sinkNodeId
|
|
1649
|
+
|
|
1650
|
+
for (let depth = 0; depth < 50; depth++) {
|
|
1651
|
+
visited.add(currentNodeId)
|
|
1652
|
+
|
|
1653
|
+
// Check if we've reached a source node
|
|
1654
|
+
const sourceMapping = sourceMappings.find(
|
|
1655
|
+
sm => sm.source === source && sm.cfgNodeId === currentNodeId
|
|
1656
|
+
)
|
|
1657
|
+
if (sourceMapping) break
|
|
1658
|
+
|
|
1659
|
+
// Find a predecessor that has taint for the current variable
|
|
1660
|
+
const predIds = getPredecessors(cfg, currentNodeId)
|
|
1661
|
+
let foundPred = false
|
|
1662
|
+
|
|
1663
|
+
for (const predId of predIds) {
|
|
1664
|
+
if (visited.has(predId)) continue
|
|
1665
|
+
|
|
1666
|
+
const predState = outStates.get(predId)
|
|
1667
|
+
if (!predState) continue
|
|
1668
|
+
|
|
1669
|
+
// Check if this predecessor contributes taint for our variable
|
|
1670
|
+
const predTaint = getTaint(predState, currentVar)
|
|
1671
|
+
if (!predTaint || predTaint.size === 0) continue
|
|
1672
|
+
|
|
1673
|
+
const predNode = cfg.nodes.get(predId)
|
|
1674
|
+
if (!predNode) continue
|
|
1675
|
+
|
|
1676
|
+
// Check if this node defines the variable (it's a propagation step)
|
|
1677
|
+
if (predNode.defs.has(currentVar)) {
|
|
1678
|
+
steps.unshift({
|
|
1679
|
+
nodeId: predId,
|
|
1680
|
+
line: predNode.line,
|
|
1681
|
+
variable: currentVar,
|
|
1682
|
+
taintKinds: new Set(predTaint),
|
|
1683
|
+
description: `ASSIGN ${predNode.label}`,
|
|
1684
|
+
stepType: 'propagation',
|
|
1685
|
+
})
|
|
1686
|
+
|
|
1687
|
+
// Check what variable(s) contributed to this def
|
|
1688
|
+
// Look at the node's uses that are tainted
|
|
1689
|
+
for (const use of predNode.uses) {
|
|
1690
|
+
const useTaint = getTaint(predState, use) // check predecessor's state
|
|
1691
|
+
if (!useTaint || useTaint.size === 0) {
|
|
1692
|
+
// Also check in-state (merge of pred's preds)
|
|
1693
|
+
const predPredIds = getPredecessors(cfg, predId)
|
|
1694
|
+
for (const ppId of predPredIds) {
|
|
1695
|
+
const ppState = outStates.get(ppId)
|
|
1696
|
+
const ppTaint = ppState ? getTaint(ppState, use) : undefined
|
|
1697
|
+
if (ppTaint && ppTaint.size > 0) {
|
|
1698
|
+
currentVar = use
|
|
1699
|
+
break
|
|
1700
|
+
}
|
|
1701
|
+
}
|
|
1702
|
+
if (currentVar !== use) continue
|
|
1703
|
+
} else {
|
|
1704
|
+
currentVar = use
|
|
1705
|
+
}
|
|
1706
|
+
break
|
|
1707
|
+
}
|
|
1708
|
+
}
|
|
1709
|
+
|
|
1710
|
+
currentNodeId = predId
|
|
1711
|
+
foundPred = true
|
|
1712
|
+
break
|
|
1713
|
+
}
|
|
1714
|
+
|
|
1715
|
+
if (!foundPred) break
|
|
1716
|
+
}
|
|
1717
|
+
|
|
1718
|
+
// Step 3: Source step
|
|
1719
|
+
steps.unshift({
|
|
1720
|
+
nodeId: sourceMappings.find(sm => sm.source === source)?.cfgNodeId ?? 0,
|
|
1721
|
+
line: source.line,
|
|
1722
|
+
variable: source.variable,
|
|
1723
|
+
taintKinds: new Set(source.taintKinds),
|
|
1724
|
+
description: `SOURCE ${source.expression.slice(0, 60)}`,
|
|
1725
|
+
stepType: 'source',
|
|
1726
|
+
})
|
|
1727
|
+
|
|
1728
|
+
return steps
|
|
1729
|
+
}
|
|
1730
|
+
|
|
1731
|
+
// ============================================================================
|
|
1732
|
+
// Deduplication
|
|
1733
|
+
// ============================================================================
|
|
1734
|
+
|
|
1735
|
+
/**
|
|
1736
|
+
* Deduplicate findings: same source line + same sink line + same kinds = one finding.
|
|
1737
|
+
*/
|
|
1738
|
+
function deduplicateFindings(findings: TaintFinding[]): TaintFinding[] {
|
|
1739
|
+
const seen = new Set<string>()
|
|
1740
|
+
const result: TaintFinding[] = []
|
|
1741
|
+
|
|
1742
|
+
for (const f of findings) {
|
|
1743
|
+
const key = `${f.source.line}:${f.sink.line}:${[...f.matchingKinds].sort().join(',')}`
|
|
1744
|
+
if (seen.has(key)) continue
|
|
1745
|
+
seen.add(key)
|
|
1746
|
+
result.push(f)
|
|
1747
|
+
}
|
|
1748
|
+
|
|
1749
|
+
return result
|
|
1750
|
+
}
|