gitnexus 1.6.8-rc.9 → 1.6.8
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/README.md +3 -3
- package/dist/_shared/graph/types.d.ts +31 -1
- package/dist/_shared/graph/types.d.ts.map +1 -1
- package/dist/_shared/language-detection.d.ts.map +1 -1
- package/dist/_shared/language-detection.js +11 -1
- package/dist/_shared/language-detection.js.map +1 -1
- package/dist/_shared/lbug/schema-constants.d.ts +1 -1
- package/dist/_shared/lbug/schema-constants.d.ts.map +1 -1
- package/dist/_shared/lbug/schema-constants.js +6 -0
- package/dist/_shared/lbug/schema-constants.js.map +1 -1
- package/dist/_shared/scope-resolution/parsed-file.d.ts +21 -0
- package/dist/_shared/scope-resolution/parsed-file.d.ts.map +1 -1
- package/dist/_shared/scope-resolution/symbol-definition.d.ts +6 -0
- package/dist/_shared/scope-resolution/symbol-definition.d.ts.map +1 -1
- package/dist/cli/ai-context.d.ts +31 -1
- package/dist/cli/ai-context.js +18 -16
- package/dist/cli/analyze-config.js +11 -0
- package/dist/cli/analyze.d.ts +14 -0
- package/dist/cli/analyze.js +98 -0
- package/dist/cli/embedding-dims.d.ts +33 -0
- package/dist/cli/embedding-dims.js +39 -0
- package/dist/cli/eval-server.d.ts +8 -0
- package/dist/cli/eval-server.js +242 -2
- package/dist/cli/help-i18n.js +19 -0
- package/dist/cli/i18n/en.d.ts +15 -1
- package/dist/cli/i18n/en.js +15 -1
- package/dist/cli/i18n/resources.d.ts +29 -1
- package/dist/cli/i18n/zh-CN.d.ts +14 -0
- package/dist/cli/i18n/zh-CN.js +15 -1
- package/dist/cli/index.js +89 -1
- package/dist/cli/mcp.d.ts +6 -1
- package/dist/cli/mcp.js +26 -1
- package/dist/cli/optional-grammars.d.ts +6 -8
- package/dist/cli/optional-grammars.js +8 -11
- package/dist/cli/setup.d.ts +3 -1
- package/dist/cli/setup.js +67 -17
- package/dist/cli/skill-gen.js +2 -1
- package/dist/cli/tool.d.ts +19 -1
- package/dist/cli/tool.js +96 -3
- package/dist/core/embeddings/hf-env.d.ts +0 -66
- package/dist/core/embeddings/http-client.d.ts +7 -0
- package/dist/core/embeddings/http-client.js +5 -3
- package/dist/core/embeddings/server-mapping.d.ts +1 -1
- package/dist/core/embeddings/server-mapping.js +7 -3
- package/dist/core/graph/import-cycles.d.ts +10 -0
- package/dist/core/graph/import-cycles.js +103 -0
- package/dist/core/group/cross-impact.d.ts +2 -1
- package/dist/core/group/cross-impact.js +5 -1
- package/dist/core/group/extractors/grpc-patterns/proto.js +10 -6
- package/dist/core/group/extractors/http-patterns/kotlin.js +7 -7
- package/dist/core/group/extractors/http-route-extractor.d.ts +24 -0
- package/dist/core/group/extractors/http-route-extractor.js +22 -4
- package/dist/core/group/extractors/include-extractor.js +22 -13
- package/dist/core/group/sync.d.ts +27 -1
- package/dist/core/group/sync.js +0 -0
- package/dist/core/incremental/subgraph-extract.js +18 -1
- package/dist/core/ingestion/cfg/cfg-builder.d.ts +114 -0
- package/dist/core/ingestion/cfg/cfg-builder.js +196 -0
- package/dist/core/ingestion/cfg/collect.d.ts +51 -0
- package/dist/core/ingestion/cfg/collect.js +77 -0
- package/dist/core/ingestion/cfg/control-dependence.d.ts +58 -0
- package/dist/core/ingestion/cfg/control-dependence.js +182 -0
- package/dist/core/ingestion/cfg/control-flow-context.d.ts +106 -0
- package/dist/core/ingestion/cfg/control-flow-context.js +124 -0
- package/dist/core/ingestion/cfg/emit.d.ts +293 -0
- package/dist/core/ingestion/cfg/emit.js +646 -0
- package/dist/core/ingestion/cfg/post-dominators.d.ts +79 -0
- package/dist/core/ingestion/cfg/post-dominators.js +176 -0
- package/dist/core/ingestion/cfg/reaching-def-reason-codec.d.ts +106 -0
- package/dist/core/ingestion/cfg/reaching-def-reason-codec.js +133 -0
- package/dist/core/ingestion/cfg/reaching-defs-graph.d.ts +18 -0
- package/dist/core/ingestion/cfg/reaching-defs-graph.js +312 -0
- package/dist/core/ingestion/cfg/reaching-defs.d.ts +162 -0
- package/dist/core/ingestion/cfg/reaching-defs.js +785 -0
- package/dist/core/ingestion/cfg/synthetic-escape.d.ts +124 -0
- package/dist/core/ingestion/cfg/synthetic-escape.js +300 -0
- package/dist/core/ingestion/cfg/traversal-result.d.ts +20 -0
- package/dist/core/ingestion/cfg/traversal-result.js +2 -0
- package/dist/core/ingestion/cfg/types.d.ts +274 -0
- package/dist/core/ingestion/cfg/types.js +13 -0
- package/dist/core/ingestion/cfg/visitors/c-cpp-harvest.d.ts +111 -0
- package/dist/core/ingestion/cfg/visitors/c-cpp-harvest.js +541 -0
- package/dist/core/ingestion/cfg/visitors/c-cpp.d.ts +74 -0
- package/dist/core/ingestion/cfg/visitors/c-cpp.js +584 -0
- package/dist/core/ingestion/cfg/visitors/call-site-harvest.d.ts +195 -0
- package/dist/core/ingestion/cfg/visitors/call-site-harvest.js +350 -0
- package/dist/core/ingestion/cfg/visitors/csharp-harvest.d.ts +124 -0
- package/dist/core/ingestion/cfg/visitors/csharp-harvest.js +589 -0
- package/dist/core/ingestion/cfg/visitors/csharp.d.ts +87 -0
- package/dist/core/ingestion/cfg/visitors/csharp.js +867 -0
- package/dist/core/ingestion/cfg/visitors/dart-harvest.d.ts +278 -0
- package/dist/core/ingestion/cfg/visitors/dart-harvest.js +870 -0
- package/dist/core/ingestion/cfg/visitors/dart.d.ts +119 -0
- package/dist/core/ingestion/cfg/visitors/dart.js +836 -0
- package/dist/core/ingestion/cfg/visitors/go-harvest.d.ts +160 -0
- package/dist/core/ingestion/cfg/visitors/go-harvest.js +621 -0
- package/dist/core/ingestion/cfg/visitors/go.d.ts +108 -0
- package/dist/core/ingestion/cfg/visitors/go.js +638 -0
- package/dist/core/ingestion/cfg/visitors/java-harvest.d.ts +108 -0
- package/dist/core/ingestion/cfg/visitors/java-harvest.js +513 -0
- package/dist/core/ingestion/cfg/visitors/java.d.ts +101 -0
- package/dist/core/ingestion/cfg/visitors/java.js +812 -0
- package/dist/core/ingestion/cfg/visitors/kotlin-harvest.d.ts +253 -0
- package/dist/core/ingestion/cfg/visitors/kotlin-harvest.js +719 -0
- package/dist/core/ingestion/cfg/visitors/kotlin.d.ts +121 -0
- package/dist/core/ingestion/cfg/visitors/kotlin.js +809 -0
- package/dist/core/ingestion/cfg/visitors/php-harvest.d.ts +172 -0
- package/dist/core/ingestion/cfg/visitors/php-harvest.js +626 -0
- package/dist/core/ingestion/cfg/visitors/php.d.ts +96 -0
- package/dist/core/ingestion/cfg/visitors/php.js +721 -0
- package/dist/core/ingestion/cfg/visitors/python-harvest.d.ts +212 -0
- package/dist/core/ingestion/cfg/visitors/python-harvest.js +739 -0
- package/dist/core/ingestion/cfg/visitors/python.d.ts +103 -0
- package/dist/core/ingestion/cfg/visitors/python.js +558 -0
- package/dist/core/ingestion/cfg/visitors/ruby-harvest.d.ts +223 -0
- package/dist/core/ingestion/cfg/visitors/ruby-harvest.js +587 -0
- package/dist/core/ingestion/cfg/visitors/ruby.d.ts +112 -0
- package/dist/core/ingestion/cfg/visitors/ruby.js +756 -0
- package/dist/core/ingestion/cfg/visitors/rust-harvest.d.ts +324 -0
- package/dist/core/ingestion/cfg/visitors/rust-harvest.js +873 -0
- package/dist/core/ingestion/cfg/visitors/rust.d.ts +95 -0
- package/dist/core/ingestion/cfg/visitors/rust.js +558 -0
- package/dist/core/ingestion/cfg/visitors/scope-tree-harvest.d.ts +87 -0
- package/dist/core/ingestion/cfg/visitors/scope-tree-harvest.js +116 -0
- package/dist/core/ingestion/cfg/visitors/swift-harvest.d.ts +255 -0
- package/dist/core/ingestion/cfg/visitors/swift-harvest.js +679 -0
- package/dist/core/ingestion/cfg/visitors/swift.d.ts +116 -0
- package/dist/core/ingestion/cfg/visitors/swift.js +787 -0
- package/dist/core/ingestion/cfg/visitors/typescript-harvest.d.ts +149 -0
- package/dist/core/ingestion/cfg/visitors/typescript-harvest.js +1056 -0
- package/dist/core/ingestion/cfg/visitors/typescript.d.ts +66 -0
- package/dist/core/ingestion/cfg/visitors/typescript.js +583 -0
- package/dist/core/ingestion/import-resolvers/utils.js +2 -0
- package/dist/core/ingestion/language-provider.d.ts +11 -0
- package/dist/core/ingestion/languages/c-cpp.js +6 -1
- package/dist/core/ingestion/languages/cobol.js +4 -0
- package/dist/core/ingestion/languages/cpp/arity-metadata.js +41 -2
- package/dist/core/ingestion/languages/cpp/captures.js +50 -1
- package/dist/core/ingestion/languages/cpp/conversion-rank.d.ts +2 -0
- package/dist/core/ingestion/languages/cpp/conversion-rank.js +89 -0
- package/dist/core/ingestion/languages/cpp/header-scan.d.ts +1 -1
- package/dist/core/ingestion/languages/cpp/header-scan.js +2 -2
- package/dist/core/ingestion/languages/cpp/inline-namespaces.js +7 -2
- package/dist/core/ingestion/languages/cpp/member-lookup.js +2 -1
- package/dist/core/ingestion/languages/cpp/query.js +23 -0
- package/dist/core/ingestion/languages/cpp/scope-resolver.js +2 -1
- package/dist/core/ingestion/languages/csharp.js +2 -0
- package/dist/core/ingestion/languages/dart.js +2 -0
- package/dist/core/ingestion/languages/go.js +2 -0
- package/dist/core/ingestion/languages/java.js +3 -0
- package/dist/core/ingestion/languages/kotlin/query.js +3 -2
- package/dist/core/ingestion/languages/kotlin.js +3 -0
- package/dist/core/ingestion/languages/php.js +2 -0
- package/dist/core/ingestion/languages/python.js +2 -0
- package/dist/core/ingestion/languages/ruby.js +2 -0
- package/dist/core/ingestion/languages/rust.js +2 -0
- package/dist/core/ingestion/languages/swift.js +2 -0
- package/dist/core/ingestion/languages/typescript.js +5 -0
- package/dist/core/ingestion/languages/vue.js +6 -0
- package/dist/core/ingestion/method-extractors/configs/c-cpp.js +6 -13
- package/dist/core/ingestion/method-extractors/generic.js +1 -0
- package/dist/core/ingestion/method-types.d.ts +2 -0
- package/dist/core/ingestion/model/symbol-table.d.ts +1 -0
- package/dist/core/ingestion/model/symbol-table.js +1 -0
- package/dist/core/ingestion/mro-processor.js +109 -0
- package/dist/core/ingestion/parsing-processor.js +23 -0
- package/dist/core/ingestion/pipeline-phases/call-summaries.d.ts +26 -0
- package/dist/core/ingestion/pipeline-phases/call-summaries.js +46 -0
- package/dist/core/ingestion/pipeline-phases/index.d.ts +2 -0
- package/dist/core/ingestion/pipeline-phases/index.js +2 -0
- package/dist/core/ingestion/pipeline-phases/parse-impl.d.ts +0 -21
- package/dist/core/ingestion/pipeline-phases/parse-impl.js +18 -1
- package/dist/core/ingestion/pipeline-phases/routes.d.ts +10 -0
- package/dist/core/ingestion/pipeline-phases/routes.js +30 -1
- package/dist/core/ingestion/pipeline-phases/taint-summaries.d.ts +30 -0
- package/dist/core/ingestion/pipeline-phases/taint-summaries.js +81 -0
- package/dist/core/ingestion/pipeline.d.ts +92 -10
- package/dist/core/ingestion/pipeline.js +11 -2
- package/dist/core/ingestion/scope-extractor.js +12 -2
- package/dist/core/ingestion/scope-resolution/contract/scope-resolver.d.ts +9 -0
- package/dist/core/ingestion/scope-resolution/graph-bridge/callee-id-sink.d.ts +75 -0
- package/dist/core/ingestion/scope-resolution/graph-bridge/callee-id-sink.js +67 -0
- package/dist/core/ingestion/scope-resolution/graph-bridge/edges.d.ts +12 -2
- package/dist/core/ingestion/scope-resolution/graph-bridge/edges.js +14 -2
- package/dist/core/ingestion/scope-resolution/graph-bridge/references-to-edges.d.ts +6 -1
- package/dist/core/ingestion/scope-resolution/graph-bridge/references-to-edges.js +13 -1
- package/dist/core/ingestion/scope-resolution/passes/free-call-fallback.d.ts +9 -1
- package/dist/core/ingestion/scope-resolution/passes/free-call-fallback.js +28 -2
- package/dist/core/ingestion/scope-resolution/passes/overload-narrowing.d.ts +4 -0
- package/dist/core/ingestion/scope-resolution/passes/overload-narrowing.js +9 -0
- package/dist/core/ingestion/scope-resolution/passes/receiver-bound-calls.d.ts +6 -1
- package/dist/core/ingestion/scope-resolution/passes/receiver-bound-calls.js +174 -40
- package/dist/core/ingestion/scope-resolution/pipeline/phase.d.ts +24 -0
- package/dist/core/ingestion/scope-resolution/pipeline/phase.js +276 -190
- package/dist/core/ingestion/scope-resolution/pipeline/reconcile-ownership.js +43 -3
- package/dist/core/ingestion/scope-resolution/pipeline/run.d.ts +75 -0
- package/dist/core/ingestion/scope-resolution/pipeline/run.js +349 -1
- package/dist/core/ingestion/scope-resolution/resolution-outcome.d.ts +1 -1
- package/dist/core/ingestion/taint/call-summary-codec.d.ts +75 -0
- package/dist/core/ingestion/taint/call-summary-codec.js +128 -0
- package/dist/core/ingestion/taint/call-summary-emit.d.ts +43 -0
- package/dist/core/ingestion/taint/call-summary-emit.js +70 -0
- package/dist/core/ingestion/taint/call-summary-harvest.d.ts +77 -0
- package/dist/core/ingestion/taint/call-summary-harvest.js +179 -0
- package/dist/core/ingestion/taint/call-summary-model.d.ts +47 -0
- package/dist/core/ingestion/taint/call-summary-model.js +25 -0
- package/dist/core/ingestion/taint/emit.d.ts +125 -0
- package/dist/core/ingestion/taint/emit.js +208 -0
- package/dist/core/ingestion/taint/interproc-emit.d.ts +50 -0
- package/dist/core/ingestion/taint/interproc-emit.js +88 -0
- package/dist/core/ingestion/taint/interproc-solver.d.ts +110 -0
- package/dist/core/ingestion/taint/interproc-solver.js +312 -0
- package/dist/core/ingestion/taint/match.d.ts +153 -0
- package/dist/core/ingestion/taint/match.js +278 -0
- package/dist/core/ingestion/taint/path-codec.d.ts +134 -0
- package/dist/core/ingestion/taint/path-codec.js +190 -0
- package/dist/core/ingestion/taint/propagate.d.ts +216 -0
- package/dist/core/ingestion/taint/propagate.js +659 -0
- package/dist/core/ingestion/taint/site-safety.d.ts +29 -0
- package/dist/core/ingestion/taint/site-safety.js +98 -0
- package/dist/core/ingestion/taint/source-sink-config.d.ts +107 -23
- package/dist/core/ingestion/taint/source-sink-config.js +35 -12
- package/dist/core/ingestion/taint/source-sink-registry.d.ts +6 -4
- package/dist/core/ingestion/taint/source-sink-registry.js +6 -4
- package/dist/core/ingestion/taint/summary-harvest-driver.d.ts +80 -0
- package/dist/core/ingestion/taint/summary-harvest-driver.js +179 -0
- package/dist/core/ingestion/taint/summary-harvest.d.ts +89 -0
- package/dist/core/ingestion/taint/summary-harvest.js +537 -0
- package/dist/core/ingestion/taint/summary-model.d.ts +200 -0
- package/dist/core/ingestion/taint/summary-model.js +86 -0
- package/dist/core/ingestion/taint/typescript-model.d.ts +38 -0
- package/dist/core/ingestion/taint/typescript-model.js +102 -0
- package/dist/core/ingestion/type-extractors/c-cpp.js +21 -0
- package/dist/core/ingestion/utils/env.d.ts +7 -0
- package/dist/core/ingestion/utils/env.js +12 -0
- package/dist/core/ingestion/utils/method-props.js +1 -0
- package/dist/core/ingestion/workers/parse-worker.d.ts +13 -0
- package/dist/core/ingestion/workers/parse-worker.js +76 -17
- package/dist/core/ingestion/workers/worker-pool.d.ts +9 -40
- package/dist/core/ingestion/workers/worker-pool.js +15 -2
- package/dist/core/lbug/csv-generator.d.ts +51 -3
- package/dist/core/lbug/csv-generator.js +369 -255
- package/dist/core/lbug/lbug-adapter.d.ts +58 -18
- package/dist/core/lbug/lbug-adapter.js +346 -84
- package/dist/core/lbug/lbug-config.d.ts +19 -0
- package/dist/core/lbug/lbug-config.js +29 -0
- package/dist/core/lbug/pdg-emit-sink.d.ts +127 -0
- package/dist/core/lbug/pdg-emit-sink.js +358 -0
- package/dist/core/lbug/pool-adapter.d.ts +35 -0
- package/dist/core/lbug/pool-adapter.js +90 -1
- package/dist/core/lbug/rel-pair-routing.d.ts +54 -0
- package/dist/core/lbug/rel-pair-routing.js +140 -0
- package/dist/core/lbug/schema.d.ts +2 -2
- package/dist/core/lbug/schema.js +3 -0
- package/dist/core/logger.d.ts +0 -5
- package/dist/core/run-analyze.d.ts +96 -0
- package/dist/core/run-analyze.js +207 -3
- package/dist/core/tree-sitter/parser-loader.js +5 -4
- package/dist/core/tree-sitter/safe-parse.d.ts +0 -5
- package/dist/core/tree-sitter/vendored-grammars.d.ts +39 -0
- package/dist/core/tree-sitter/vendored-grammars.js +57 -0
- package/dist/core/wiki/generator.js +3 -1
- package/dist/core/wiki/graph-queries.d.ts +6 -0
- package/dist/core/wiki/graph-queries.js +9 -1
- package/dist/mcp/http-transport.d.ts +134 -0
- package/dist/mcp/http-transport.js +503 -0
- package/dist/mcp/local/local-backend.d.ts +110 -24
- package/dist/mcp/local/local-backend.js +1225 -19
- package/dist/mcp/local/pdg-impact.d.ts +384 -0
- package/dist/mcp/local/pdg-impact.js +1972 -0
- package/dist/mcp/resources.js +13 -1
- package/dist/mcp/tools.d.ts +12 -0
- package/dist/mcp/tools.js +214 -7
- package/dist/server/analyze-launch.d.ts +29 -0
- package/dist/server/analyze-launch.js +137 -0
- package/dist/server/analyze-upload.d.ts +33 -0
- package/dist/server/analyze-upload.js +123 -0
- package/dist/server/analyze-worker.d.ts +17 -1
- package/dist/server/api.d.ts +17 -0
- package/dist/server/api.js +132 -168
- package/dist/server/git-clone.d.ts +43 -2
- package/dist/server/git-clone.js +211 -16
- package/dist/server/mcp-http.d.ts +5 -6
- package/dist/server/mcp-http.js +9 -82
- package/dist/server/middleware.d.ts +40 -0
- package/dist/server/middleware.js +92 -0
- package/dist/server/private-ip.d.ts +1 -0
- package/dist/server/private-ip.js +15 -0
- package/dist/server/upload-ingest.d.ts +56 -0
- package/dist/server/upload-ingest.js +276 -0
- package/dist/server/upload-paths.d.ts +37 -0
- package/dist/server/upload-paths.js +57 -0
- package/dist/server/upload-sweep.d.ts +20 -0
- package/dist/server/upload-sweep.js +57 -0
- package/dist/storage/git.js +5 -8
- package/dist/storage/parse-cache.d.ts +22 -1
- package/dist/storage/parse-cache.js +41 -10
- package/dist/storage/repo-manager.d.ts +121 -10
- package/dist/storage/repo-manager.js +43 -5
- package/dist/types/pipeline.d.ts +9 -0
- package/hooks/antigravity/gitnexus-antigravity-hook.cjs +110 -10
- package/hooks/claude/gitnexus-hook.cjs +110 -13
- package/hooks/claude/hook-db-lock-probe.cjs +513 -26
- package/package.json +8 -2
- package/scripts/assert-publish-grammar-coverage.cjs +31 -0
- package/scripts/build-tree-sitter-grammars.cjs +16 -10
- package/scripts/cross-platform-tests.ts +1 -0
- package/skills/gitnexus-debugging.md +16 -4
- package/skills/gitnexus-exploring.md +3 -3
- package/skills/gitnexus-guide.md +33 -0
- package/skills/gitnexus-pdg-query.md +89 -0
- package/skills/gitnexus-refactoring.md +1 -1
- package/skills/gitnexus-taint-analysis.md +178 -0
- package/vendor/tree-sitter-c/package.json +1 -1
- package/vendor/tree-sitter-dart/package.json +1 -1
- package/vendor/tree-sitter-kotlin/package.json +1 -1
- package/vendor/tree-sitter-proto/package.json +1 -1
- package/vendor/tree-sitter-swift/package.json +1 -1
- package/web/assets/{agent-CKPMqImC.js → agent-D3Xd7zZE.js} +123 -123
- package/web/assets/architecture-7EHR7CIX-6QZW5X65-DxJw65fT.js +1 -0
- package/web/assets/architectureDiagram-UL44E2DR-ChH9xW-N.js +36 -0
- package/web/assets/{blockDiagram-7IZFK4PR-B4niSLvR.js → blockDiagram-7IZFK4PR-BkWhpzWB.js} +1 -1
- package/web/assets/{c4Diagram-Y2BXMSZH-DIzEXalR.js → c4Diagram-Y2BXMSZH-DtBvpzWD.js} +1 -1
- package/web/assets/{chunk-3SSMPTDK-BzGeeheI.js → chunk-3SSMPTDK-B4_etUhr.js} +2 -2
- package/web/assets/{chunk-5IMINLNL-CnTLsXIV.js → chunk-5IMINLNL-DOdTeQri.js} +1 -1
- package/web/assets/{chunk-5VCL7Z4A-w1BCx98Z.js → chunk-5VCL7Z4A-CQ-2dYWj.js} +1 -1
- package/web/assets/chunk-6764PJDD-DpIS7hxD.js +1 -0
- package/web/assets/{chunk-67TQ5CYL-CChC2cAv.js → chunk-67TQ5CYL-4sNPJU1q.js} +3 -3
- package/web/assets/{chunk-7W6UQGC5-CgFyfZ6k.js → chunk-7W6UQGC5-KyEG0HQg.js} +1 -1
- package/web/assets/{chunk-AZZRMDJM-5E9ARdDQ.js → chunk-AZZRMDJM-y-xJBuMz.js} +1 -1
- package/web/assets/{chunk-INKRHTLW-ELpfuk9h.js → chunk-INKRHTLW-Nu4ri9P-.js} +1 -1
- package/web/assets/{chunk-JQRUD6KW-Dcq_Qnyy.js → chunk-JQRUD6KW-Dbva2Z17.js} +1 -1
- package/web/assets/{chunk-KGFNY3KK-W-VR57xb.js → chunk-KGFNY3KK-JGMJvKCK.js} +1 -1
- package/web/assets/chunk-KGYTTC2M-CWC_c3H9.js +161 -0
- package/web/assets/{chunk-KNLZD3CH-BuZBOcso.js → chunk-KNLZD3CH-BGlP6X9R.js} +1 -1
- package/web/assets/{chunk-KRXBNO2N-DH2NPM7I.js → chunk-KRXBNO2N-BW5o0KUN.js} +1 -1
- package/web/assets/{chunk-LCXTWHL2-Dfmux4m1.js → chunk-LCXTWHL2-zoomRtWX.js} +1 -1
- package/web/assets/{chunk-LII3EMHJ-3X33tCpU.js → chunk-LII3EMHJ-D-jm-dLa.js} +1 -1
- package/web/assets/{chunk-QA3QBVWF-D5Ha3GgB.js → chunk-QA3QBVWF-BgRW3SXd.js} +1 -1
- package/web/assets/{chunk-RG4AUYOV-CAkzcoRj.js → chunk-RG4AUYOV-DD0B2N2B.js} +1 -1
- package/web/assets/{chunk-T2UQINTJ-kGYG7ppP.js → chunk-T2UQINTJ-DSBqkzH7.js} +1 -1
- package/web/assets/chunk-T5OCTHI4-DdZvN-9i.js +1 -0
- package/web/assets/{chunk-UY5QBCOK-Du-c56gs.js → chunk-UY5QBCOK-DyF0vyGd.js} +1 -1
- package/web/assets/{chunk-W44A43WB-lrCpuupA.js → chunk-W44A43WB-JqMLIpR0.js} +1 -1
- package/web/assets/{chunk-ZXARS5L4-CSZRiyOf.js → chunk-ZXARS5L4-CxN8oiwI.js} +1 -1
- package/web/assets/classDiagram-KGZ6W3CR-DJm9BgF-.js +1 -0
- package/web/assets/classDiagram-v2-72OJOZXJ-DJm9BgF-.js +1 -0
- package/web/assets/{context-builder-_HS0v2ma.js → context-builder-BHiFUA8O.js} +10 -2
- package/web/assets/{cose-bilkent-UX7MHV2Q-BHv2bbtl.js → cose-bilkent-UX7MHV2Q-D71wNYRJ.js} +1 -1
- package/web/assets/dagre-ND4H6XIP-BIkxmhcu.js +4 -0
- package/web/assets/diagram-3NCE3AQN-BTa6iH0T.js +43 -0
- package/web/assets/diagram-GF46GFSD-DZDV0vBj.js +24 -0
- package/web/assets/{diagram-HNR7UZ2L-fUWreZzx.js → diagram-HNR7UZ2L-DFCfd5LI.js} +2 -2
- package/web/assets/diagram-QXG6HAR7-BzM--Jj0.js +24 -0
- package/web/assets/diagram-WEQXMOUZ-DFFwz0k4.js +10 -0
- package/web/assets/{erDiagram-L5TCEMPS-QN2eEP1e.js → erDiagram-L5TCEMPS-CfHMJJqo.js} +1 -1
- package/web/assets/eventmodeling-FCH6USID-MREXMVOE-BQm9QzEa.js +1 -0
- package/web/assets/{flowDiagram-H6V6AXG4-CXbXImlN.js → flowDiagram-H6V6AXG4-Be805QiJ.js} +3 -3
- package/web/assets/{ganttDiagram-JCBTUEKG-BHQssTz6.js → ganttDiagram-JCBTUEKG-D3iL2Aet.js} +1 -1
- package/web/assets/gitGraph-WXDBUCRP-R675I2BI-Cr_Bm2Nb.js +1 -0
- package/web/assets/gitGraphDiagram-S2ZK5IYY-CPIDgxGm.js +106 -0
- package/web/assets/index-BvYev1UJ.js +635 -0
- package/web/assets/index-_lgn7hs5.css +2 -0
- package/web/assets/info-J43DQDTF-KCYPFFUO-Cs4p2oyk.js +1 -0
- package/web/assets/infoDiagram-3YFTVSEB-Y7BKSoke.js +2 -0
- package/web/assets/{ishikawaDiagram-BNXS4ZKH-6LKOvBfp.js → ishikawaDiagram-BNXS4ZKH-CT4SHFuI.js} +1 -1
- package/web/assets/{journeyDiagram-M6C3CM3L-DNPYCpvq.js → journeyDiagram-M6C3CM3L-CvArszLo.js} +1 -1
- package/web/assets/{kanban-definition-75IXJCU3-DgDi9oJT.js → kanban-definition-75IXJCU3-C6yDxr7N.js} +3 -3
- package/web/assets/{katex-K3KEBU37-CrVLz8l7.js → katex-K3KEBU37-CTc5BslQ.js} +1 -1
- package/web/assets/{mindmap-definition-2TDM6QVE-CwR5sBB-.js → mindmap-definition-2TDM6QVE-BOc2WEne.js} +1 -1
- package/web/assets/packet-YPE3B663-LP52Z2RK-CKCrztD2.js +1 -0
- package/web/assets/pie-LRSECV5Y-TCRJHUBD-BB7pHqoD.js +1 -0
- package/web/assets/{pieDiagram-CU6KROY3-By8g6f6B.js → pieDiagram-CU6KROY3-Cvc0f_Pd.js} +1 -1
- package/web/assets/{quadrantDiagram-VICAPDV7-bAm6Hi_5.js → quadrantDiagram-VICAPDV7-yXSZ2lLz.js} +1 -1
- package/web/assets/radar-GUYGQ44K-RDLRG3WG-DhoTOcuK.js +1 -0
- package/web/assets/{requirementDiagram-JXO7QTGE-7oDcJ1_J.js → requirementDiagram-JXO7QTGE-BRcZvHs4.js} +1 -1
- package/web/assets/{sankeyDiagram-URQDO5SZ-LFJz53u5.js → sankeyDiagram-URQDO5SZ-1qEjlFIB.js} +1 -1
- package/web/assets/{sequenceDiagram-VS2MUI6T-FySeKCUy.js → sequenceDiagram-VS2MUI6T-PcSCuvrB.js} +3 -3
- package/web/assets/stateDiagram-7D4R322I-D6qYSV1E.js +1 -0
- package/web/assets/stateDiagram-v2-36443NZ5-Bg-ImOSX.js +1 -0
- package/web/assets/{timeline-definition-O6YCAMPW-Ds2CnVZK.js → timeline-definition-O6YCAMPW-QWkh01xw.js} +1 -1
- package/web/assets/treeView-BLDUP644-QA4HXRO3-ODehyGKL.js +1 -0
- package/web/assets/treemap-LRROVOQU-LLAWBHMP-BfpgC-7o.js +1 -0
- package/web/assets/{vennDiagram-MWXL3ELB-DUIEwXWp.js → vennDiagram-MWXL3ELB-ak4DfwuM.js} +3 -3
- package/web/assets/wardley-L42UT6IY-5TKZOOLJ-CNeL6VHE.js +1 -0
- package/web/assets/{wardleyDiagram-CUQ6CDDI-DEiFPQih.js → wardleyDiagram-CUQ6CDDI-CTzR4Loq.js} +1 -1
- package/web/assets/{xychartDiagram-N2JHSOCM-BUbayhST.js → xychartDiagram-N2JHSOCM-DA1BEaQz.js} +1 -1
- package/web/index.html +13 -15
- package/scripts/materialize-vendor-grammars.cjs +0 -97
- package/web/assets/architecture-7EHR7CIX-6QZW5X65-BETj2x4g.js +0 -1
- package/web/assets/architectureDiagram-UL44E2DR-D1EXI0zA.js +0 -36
- package/web/assets/chunk-2T2R6R2M-n6s9JZqv.js +0 -4
- package/web/assets/chunk-2UTLFMKG-CMBB1TMN.js +0 -1
- package/web/assets/chunk-4R4BOZG6-mKhL59ul.js +0 -159
- package/web/assets/chunk-6764PJDD-ChwMM2z4.js +0 -1
- package/web/assets/chunk-7J6CGLKN-DT-b53FT.js +0 -10
- package/web/assets/chunk-C62D2QBJ-CDAWj26E.js +0 -1
- package/web/assets/chunk-CEXFNPSA-D68Tk6ls.js +0 -1
- package/web/assets/chunk-CilyBKbf.js +0 -1
- package/web/assets/chunk-J5EP6P6S-DxWW0yvu.js +0 -1
- package/web/assets/chunk-KGYTTC2M-C6PHeuay.js +0 -1
- package/web/assets/chunk-RERM46MO-DQNbXtfw.js +0 -1
- package/web/assets/chunk-RKZBBQEN-BmTPLSyv.js +0 -1
- package/web/assets/chunk-RLI5ZMPA-DExu2DOK.js +0 -1
- package/web/assets/chunk-T5OCTHI4--9wWpVws.js +0 -1
- package/web/assets/chunk-UP6H54XL-DsKdC6jC.js +0 -1
- package/web/assets/chunk-UXSXWOXI-DR81EqLr.js +0 -1
- package/web/assets/classDiagram-KGZ6W3CR-Bv93af_b.js +0 -1
- package/web/assets/classDiagram-v2-72OJOZXJ-CLfEqHUa.js +0 -1
- package/web/assets/dagre-ND4H6XIP-DsIr9MOv.js +0 -4
- package/web/assets/diagram-3NCE3AQN-BqAtKUpW.js +0 -43
- package/web/assets/diagram-GF46GFSD-CaBG6n6o.js +0 -24
- package/web/assets/diagram-QXG6HAR7-CZ-O3rcV.js +0 -24
- package/web/assets/diagram-WEQXMOUZ-P4lSL4GH.js +0 -10
- package/web/assets/eventmodeling-FCH6USID-MREXMVOE-B3myHUst.js +0 -1
- package/web/assets/gitGraph-WXDBUCRP-R675I2BI-DF3rtf4A.js +0 -1
- package/web/assets/gitGraphDiagram-S2ZK5IYY-CCwVLT__.js +0 -106
- package/web/assets/index-BKWA-m7o.css +0 -2
- package/web/assets/index-CG6q8eTs.js +0 -626
- package/web/assets/info-J43DQDTF-KCYPFFUO-R4XV_akJ.js +0 -1
- package/web/assets/infoDiagram-3YFTVSEB-DmLICZx1.js +0 -2
- package/web/assets/packet-YPE3B663-LP52Z2RK-CgQPdNZa.js +0 -1
- package/web/assets/pie-LRSECV5Y-TCRJHUBD-BzWOwc_c.js +0 -1
- package/web/assets/radar-GUYGQ44K-RDLRG3WG-B7KG0FN7.js +0 -1
- package/web/assets/stateDiagram-7D4R322I-CB2nABwH.js +0 -1
- package/web/assets/stateDiagram-v2-36443NZ5-COBGd2RL.js +0 -1
- package/web/assets/treeView-BLDUP644-QA4HXRO3-CRSxEwLO.js +0 -1
- package/web/assets/treemap-LRROVOQU-LLAWBHMP-a0PfKoVn.js +0 -1
- package/web/assets/wardley-L42UT6IY-5TKZOOLJ-BKQ6e5mC.js +0 -1
- /package/web/assets/{chunk-AQ6EADP3-CCFje6lL.js → chunk-AQ6EADP3-CZhslHi-.js} +0 -0
|
@@ -0,0 +1,659 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure intra-procedural taint propagation engine (#2083 M3 U3).
|
|
3
|
+
*
|
|
4
|
+
* Forward taint reachability over one function's reaching-definition facts
|
|
5
|
+
* (M2 `computeReachingDefs`) and matched taint sites (U2 `matchFunctionSites`)
|
|
6
|
+
* — sources in, findings + sanitizer kills + coverage status out. PURE AND
|
|
7
|
+
* DETERMINISTIC, mirroring the reaching-defs contract: no graph, no I/O, no
|
|
8
|
+
* logger; insertion-ordered worklist; explicitly sorted outputs; snapshot
|
|
9
|
+
* tests and content-derived edge ids (U4) rely on it.
|
|
10
|
+
*
|
|
11
|
+
* PRECONDITIONS: the caller gates the CFG through `hasTaintSafeSites`
|
|
12
|
+
* (taint/site-safety.ts) and the emit-safety checks before calling — this
|
|
13
|
+
* module dereferences binding/site/statement indices without re-validating.
|
|
14
|
+
*
|
|
15
|
+
* ## The two-rule model (plan HTD)
|
|
16
|
+
*
|
|
17
|
+
* - **Rule (b), statement-local:** a matched SOURCE occurrence (member read)
|
|
18
|
+
* whose intra-statement occurrence path — the member-read's `parent` chain —
|
|
19
|
+
* reaches a matched SINK argument position produces an immediate single-hop
|
|
20
|
+
* finding (`exec(req.body)`). The same statement SEEDS taint: every binding
|
|
21
|
+
* the statement defines becomes tainted (see the precision floor below).
|
|
22
|
+
* - **Rule (a), worklist:** for each tainted `(binding, defPoint)`, every
|
|
23
|
+
* def→use fact delivers the taint to a use statement, where occurrences of
|
|
24
|
+
* the binding in matched sink argument positions produce findings and the
|
|
25
|
+
* statement's own defs are tainted onward. The fact graph contains genuine
|
|
26
|
+
* cycles (loop back-edges, same-statement self-facts) — the visited-set
|
|
27
|
+
* discipline below is load-bearing, not defensive.
|
|
28
|
+
*
|
|
29
|
+
* ## Sanitizer semantics — the KIND-SET exclusion model (KTD4, sharpened)
|
|
30
|
+
*
|
|
31
|
+
* The plan sketches a binary kill; this module implements the strictly more
|
|
32
|
+
* precise SOUND refinement: a taint carries a set of *excluded* (neutralized)
|
|
33
|
+
* `SinkKind`s accumulated through sanitizer hops, and a sink fires unless its
|
|
34
|
+
* kind is in the taint's exclusion set. A binary kill is the special case
|
|
35
|
+
* where the sanitizer neutralizes the sink's kind; the kind-set model
|
|
36
|
+
* additionally keeps `const b = escape(req.body); db.query(b)` a FINDING
|
|
37
|
+
* (an HTML escaper does not neutralize SQL — un-tainting `b` outright would
|
|
38
|
+
* be a suppressed live injection, the forbidden false-negative direction)
|
|
39
|
+
* while still suppressing `res.send(b)` (xss IS neutralized).
|
|
40
|
+
*
|
|
41
|
+
* - **Occurrence interposition (KTD4a):** evaluated over the U1 site
|
|
42
|
+
* structure. An occurrence reaching a sink arg / def-feeding position
|
|
43
|
+
* through a matched sanitizer site accumulates that sanitizer's
|
|
44
|
+
* `neutralizes` kinds on that PATH; a direct occurrence contributes the
|
|
45
|
+
* empty set. Per-position narrowing (`entry.args`) is respected; receiver
|
|
46
|
+
* flow through a sanitizer is NOT neutralized (the receiver is not the
|
|
47
|
+
* sanitized payload), and spread/template positions are never neutralized
|
|
48
|
+
* (position unprovable) — both sound-direction choices (under-kill).
|
|
49
|
+
* - **Intersection over paths:** a def fed by several occurrence paths
|
|
50
|
+
* excludes a kind only when EVERY path neutralizes it
|
|
51
|
+
* (`const c = cond ? escape(b) : b` taints `c` with NO exclusions — the
|
|
52
|
+
* direct arm's ∅ intersects everything away). Equally, a taint re-derived
|
|
53
|
+
* along a second route keeps the INTERSECTION of the exclusion sets and is
|
|
54
|
+
* re-processed whenever the set SHRINKS — a less-neutralized taint is
|
|
55
|
+
* strictly more dangerous. Exclusion sets only shrink over a finite
|
|
56
|
+
* lattice, so the worklist terminates.
|
|
57
|
+
* - **Kill locality (KTD4b):** a kill applies to the def the sanitizer
|
|
58
|
+
* produces (`SiteRecord.resultDefs`) only; the flowing binding's own taint
|
|
59
|
+
* is untouched (`const c = escape(b); exec(b)` still finds `b`'s flow).
|
|
60
|
+
* `x = escape(x)` works because taint keys on the DEF POINT: the
|
|
61
|
+
* sanitizer statement's def enters the set with the sanitizer's kinds
|
|
62
|
+
* excluded, while the seed def keeps flowing wherever the CFG still
|
|
63
|
+
* carries it (zero-iteration loops, conditional sanitizers — may-path
|
|
64
|
+
* mechanics need no special handling here, kills are absent from facts).
|
|
65
|
+
*
|
|
66
|
+
* ## Statement-coalescing precision floor (documented FP)
|
|
67
|
+
*
|
|
68
|
+
* Statement facts conflate multi-declarator statements: a statement that
|
|
69
|
+
* uses tainted `b` and defines `c` taints `c` with NO exclusions even when
|
|
70
|
+
* the two are textually unrelated (`const a = clean(z), b = g(t)` floor-
|
|
71
|
+
* taints `a` from `t` — pinned by a test). The per-declarator `resultDefs`
|
|
72
|
+
* precision narrows the EXCLUSION computation (and powers kills) only — a
|
|
73
|
+
* def in a call's `resultDefs` is fed exactly through that call, so its
|
|
74
|
+
* exclusions come from the paths into it; when the tainted input provably
|
|
75
|
+
* never flows into that call, the floor still taints the def (sound) but
|
|
76
|
+
* records no kill (a kill requires evidence of flow through the sanitizer).
|
|
77
|
+
*
|
|
78
|
+
* ## Propagate-through (KTD5)
|
|
79
|
+
*
|
|
80
|
+
* Taint in any argument or in the receiver of an UNMODELED call flows to
|
|
81
|
+
* the call's result defs, marked `viaCall` on the hop so `explain` can
|
|
82
|
+
* express lower confidence. An occurrence that reaches the unmodeled call
|
|
83
|
+
* only through a sanitizer carries the neutralization through
|
|
84
|
+
* (`const y = unknownFn(escape(b))` excludes the sanitizer's kinds — the
|
|
85
|
+
* plan's deliberate precision choice over flat-conservative).
|
|
86
|
+
*
|
|
87
|
+
* ## Kills output
|
|
88
|
+
*
|
|
89
|
+
* `kills` records every sanitizer that ACTUALLY neutralized kinds on a
|
|
90
|
+
* flowing taint — U4 emits `SANITIZES` edges from them. Two shapes share the
|
|
91
|
+
* record: result-def kills (`killedDef` = the def the sanitizer produces;
|
|
92
|
+
* `bindingIdx` = that def's binding) and value-position interposition kills
|
|
93
|
+
* (`exec(escape(x))` — no def exists; `killedDef` = the sink statement's own
|
|
94
|
+
* point, `bindingIdx` = the interposed binding). Interposition kills are
|
|
95
|
+
* recorded only when the (input, sink, position) produced no finding — a
|
|
96
|
+
* bypassed sanitizer (`exec(x + escape(x))`) killed nothing.
|
|
97
|
+
*/
|
|
98
|
+
import { pointKey } from '../cfg/reaching-defs.js';
|
|
99
|
+
import { SINK_KIND_ORDER as KIND_ORDER, sortSinkKinds as sortKinds, } from './source-sink-config.js';
|
|
100
|
+
/**
|
|
101
|
+
* Default per-function findings cap (U5 config resolution; cfg/emit.ts
|
|
102
|
+
* DEFAULT_* pattern). Resolved into the RepoMeta `pdg` stamp by
|
|
103
|
+
* `resolvePdgConfig` so a cap change trips full writeback; `0` = unlimited
|
|
104
|
+
* is preserved like the other pdg caps. 200 is generous — a real function
|
|
105
|
+
* with more deduped source→sink findings is a fixture or a disaster, and
|
|
106
|
+
* the truncation is deterministic + counted (`droppedFindings`).
|
|
107
|
+
*/
|
|
108
|
+
export const DEFAULT_PDG_MAX_TAINT_FINDINGS_PER_FUNCTION = 200;
|
|
109
|
+
/**
|
|
110
|
+
* Default per-finding hop cap (U5; joins the RepoMeta `pdg` stamp like the
|
|
111
|
+
* findings cap). Bounds the persisted `reason` hop encoding (KTD6 pins the
|
|
112
|
+
* hop cap in config); 32 intra-procedural def→use hops is far beyond any
|
|
113
|
+
* legible path — overflow keeps the source-side prefix and sets
|
|
114
|
+
* `hopsTruncated`, parsed downstream as "path incomplete", never an error.
|
|
115
|
+
*/
|
|
116
|
+
export const DEFAULT_PDG_MAX_TAINT_HOPS = 32;
|
|
117
|
+
// Canonical SinkKind order + sort live in source-sink-config.ts (shared with
|
|
118
|
+
// the M4 summary harvest so the deterministic order never drifts); imported
|
|
119
|
+
// above as KIND_ORDER / sortKinds. `kindRank` is the local comparator index.
|
|
120
|
+
const kindRank = new Map(KIND_ORDER.map((k, i) => [k, i]));
|
|
121
|
+
const EMPTY_KINDS = new Set();
|
|
122
|
+
const DIRECT_PATH = { kinds: EMPTY_KINDS, viaCall: false, sanitizers: [] };
|
|
123
|
+
/**
|
|
124
|
+
* Compute taint flows for one function. See the module doc for the two-rule
|
|
125
|
+
* model, the kind-set exclusion semantics, and the precision floor.
|
|
126
|
+
*/
|
|
127
|
+
export function computeTaintFlows(cfg, defUse, matches, limits) {
|
|
128
|
+
if (defUse.status !== 'computed') {
|
|
129
|
+
return {
|
|
130
|
+
status: 'coverage-gap',
|
|
131
|
+
gapReason: defUse.status,
|
|
132
|
+
findings: [],
|
|
133
|
+
kills: [],
|
|
134
|
+
droppedFindings: 0,
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
const bindings = defUse.bindings;
|
|
138
|
+
// ── per-statement context (built lazily; statements revisit often) ────────
|
|
139
|
+
const matchByPoint = new Map();
|
|
140
|
+
for (const sm of matches.statements) {
|
|
141
|
+
matchByPoint.set(`${sm.blockIndex}:${sm.statementIndex}`, sm);
|
|
142
|
+
}
|
|
143
|
+
const ctxCache = new Map();
|
|
144
|
+
const contextAt = (blockIndex, stmtIndex) => {
|
|
145
|
+
const key = `${blockIndex}:${stmtIndex}`;
|
|
146
|
+
if (ctxCache.has(key))
|
|
147
|
+
return ctxCache.get(key);
|
|
148
|
+
const facts = cfg.blocks[blockIndex]?.statements?.[stmtIndex];
|
|
149
|
+
let ctx;
|
|
150
|
+
if (facts) {
|
|
151
|
+
const sm = matchByPoint.get(key);
|
|
152
|
+
const sinksBySite = new Map();
|
|
153
|
+
const sanitizersBySite = new Map();
|
|
154
|
+
for (const s of sm?.sinks ?? []) {
|
|
155
|
+
const list = sinksBySite.get(s.siteIndex);
|
|
156
|
+
if (list)
|
|
157
|
+
list.push(s);
|
|
158
|
+
else
|
|
159
|
+
sinksBySite.set(s.siteIndex, [s]);
|
|
160
|
+
}
|
|
161
|
+
for (const s of sm?.sanitizers ?? []) {
|
|
162
|
+
const list = sanitizersBySite.get(s.siteIndex);
|
|
163
|
+
if (list)
|
|
164
|
+
list.push(s);
|
|
165
|
+
else
|
|
166
|
+
sanitizersBySite.set(s.siteIndex, [s]);
|
|
167
|
+
}
|
|
168
|
+
const resultDefSites = new Map();
|
|
169
|
+
facts.sites?.forEach((site, siteIndex) => {
|
|
170
|
+
for (const d of site.resultDefs ?? []) {
|
|
171
|
+
const list = resultDefSites.get(d);
|
|
172
|
+
if (list)
|
|
173
|
+
list.push(siteIndex);
|
|
174
|
+
else
|
|
175
|
+
resultDefSites.set(d, [siteIndex]);
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
ctx = {
|
|
179
|
+
point: { blockIndex, stmtIndex, line: facts.line },
|
|
180
|
+
facts,
|
|
181
|
+
sites: facts.sites ?? [],
|
|
182
|
+
sinksBySite,
|
|
183
|
+
sanitizersBySite,
|
|
184
|
+
resultDefSites,
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
ctxCache.set(key, ctx);
|
|
188
|
+
return ctx;
|
|
189
|
+
};
|
|
190
|
+
/** Kinds the matched sanitizers at `siteIndex` neutralize for input position `argPos`. */
|
|
191
|
+
const neutralizedAt = (ctx, siteIndex, argPos) => {
|
|
192
|
+
const sans = ctx.sanitizersBySite.get(siteIndex);
|
|
193
|
+
if (!sans)
|
|
194
|
+
return [];
|
|
195
|
+
const site = ctx.sites[siteIndex];
|
|
196
|
+
// Spread/template positions are never provably the sanitized argument —
|
|
197
|
+
// do not neutralize (sound: under-kill). Exact positions check `args`.
|
|
198
|
+
if (site.template === true || (site.spread !== undefined && argPos >= site.spread))
|
|
199
|
+
return [];
|
|
200
|
+
const kinds = [];
|
|
201
|
+
for (const san of sans) {
|
|
202
|
+
if (san.entry.args === undefined || san.entry.args.includes(argPos)) {
|
|
203
|
+
kinds.push(...san.entry.neutralizes);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
return sortKinds(kinds);
|
|
207
|
+
};
|
|
208
|
+
/** A call/new site the model does not understand (anything but a matched sanitizer). */
|
|
209
|
+
const isUnmodeledCall = (ctx, siteIndex) => {
|
|
210
|
+
const site = ctx.sites[siteIndex];
|
|
211
|
+
return site.kind !== 'member-read' && !ctx.sanitizersBySite.has(siteIndex);
|
|
212
|
+
};
|
|
213
|
+
const emerge = (ctx, siteIndex, argPos, inner) => {
|
|
214
|
+
const added = neutralizedAt(ctx, siteIndex, argPos);
|
|
215
|
+
return {
|
|
216
|
+
kinds: added.length === 0 ? inner.kinds : new Set([...inner.kinds, ...added]),
|
|
217
|
+
viaCall: inner.viaCall || isUnmodeledCall(ctx, siteIndex),
|
|
218
|
+
sanitizers: added.length === 0 ? inner.sanitizers : [...inner.sanitizers, { siteIndex, kinds: added }],
|
|
219
|
+
};
|
|
220
|
+
};
|
|
221
|
+
/**
|
|
222
|
+
* STRICT occurrence paths of binding `b` flowing OUT of site `siteIndex`'s
|
|
223
|
+
* result — found arg entries and the receiver only, each with the site's
|
|
224
|
+
* own neutralization/viaCall applied. Empty when `b` provably never flows
|
|
225
|
+
* in (the caller falls back to the floor and records NO kill). `guard`
|
|
226
|
+
* breaks corrupted-store via cycles (site-safety checks ranges, not
|
|
227
|
+
* acyclicity).
|
|
228
|
+
*/
|
|
229
|
+
const flowsOutOf = (ctx, b, siteIndex, guard) => {
|
|
230
|
+
if (guard.has(siteIndex))
|
|
231
|
+
return [];
|
|
232
|
+
guard.add(siteIndex);
|
|
233
|
+
const site = ctx.sites[siteIndex];
|
|
234
|
+
const out = [];
|
|
235
|
+
site.args?.forEach((entries, argPos) => {
|
|
236
|
+
for (const e of entries) {
|
|
237
|
+
if (typeof e === 'number') {
|
|
238
|
+
if (e === b)
|
|
239
|
+
out.push(emerge(ctx, siteIndex, argPos, DIRECT_PATH));
|
|
240
|
+
}
|
|
241
|
+
else if (e[0] === b) {
|
|
242
|
+
// Via-tagged: the occurrence reaches this position THROUGH the
|
|
243
|
+
// nested site. When the nested site shows no recognized channel
|
|
244
|
+
// for `b` (callee-chain occurrences — dynamic subscript keys), the
|
|
245
|
+
// via-tag is still evidence of flow: fall back to a direct,
|
|
246
|
+
// UN-neutralized path (sound: never a false kill).
|
|
247
|
+
const inner = flowsOutOf(ctx, b, e[1], guard);
|
|
248
|
+
const paths = inner.length > 0
|
|
249
|
+
? inner
|
|
250
|
+
: [{ ...DIRECT_PATH, viaCall: isUnmodeledCall(ctx, e[1]) }];
|
|
251
|
+
for (const p of paths)
|
|
252
|
+
out.push(emerge(ctx, siteIndex, argPos, p));
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
});
|
|
256
|
+
if (site.receiver === b) {
|
|
257
|
+
// Receiver TITO (KTD5): the receiver's value flows through the call
|
|
258
|
+
// into its result — but a sanitizer does not neutralize its receiver.
|
|
259
|
+
out.push({ ...DIRECT_PATH, viaCall: isUnmodeledCall(ctx, siteIndex) });
|
|
260
|
+
}
|
|
261
|
+
guard.delete(siteIndex);
|
|
262
|
+
return out;
|
|
263
|
+
};
|
|
264
|
+
/** Occurrence paths of `b` INTO sink position (s, p) — no emergence from s. */
|
|
265
|
+
const pathsIntoPosition = (ctx, b, siteIndex, argPos) => {
|
|
266
|
+
const entries = ctx.sites[siteIndex].args?.[argPos] ?? [];
|
|
267
|
+
const out = [];
|
|
268
|
+
for (const e of entries) {
|
|
269
|
+
if (typeof e === 'number') {
|
|
270
|
+
if (e === b)
|
|
271
|
+
out.push(DIRECT_PATH);
|
|
272
|
+
}
|
|
273
|
+
else if (e[0] === b) {
|
|
274
|
+
const inner = flowsOutOf(ctx, b, e[1], new Set());
|
|
275
|
+
if (inner.length > 0)
|
|
276
|
+
out.push(...inner);
|
|
277
|
+
else
|
|
278
|
+
out.push({ ...DIRECT_PATH, viaCall: isUnmodeledCall(ctx, e[1]) });
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
return out;
|
|
282
|
+
};
|
|
283
|
+
/**
|
|
284
|
+
* Walk a SOURCE member-read's `parent` chain. Linear (each site has one
|
|
285
|
+
* parent); invokes `onPosition` with the accumulated path BEFORE the
|
|
286
|
+
* ancestor's own emergence (the value flows INTO the ancestor at that
|
|
287
|
+
* position) — sink checks and stop-at-site joins both hang off it.
|
|
288
|
+
*/
|
|
289
|
+
const climbSourceChain = (ctx, srcSiteIndex, onPosition) => {
|
|
290
|
+
const visited = new Set([srcSiteIndex]);
|
|
291
|
+
let cur = ctx.sites[srcSiteIndex];
|
|
292
|
+
let sofar = DIRECT_PATH;
|
|
293
|
+
while (cur.parent) {
|
|
294
|
+
const [siteIndex, argPos] = cur.parent;
|
|
295
|
+
if (visited.has(siteIndex))
|
|
296
|
+
return; // corrupted-store parent cycle
|
|
297
|
+
visited.add(siteIndex);
|
|
298
|
+
if (onPosition(siteIndex, argPos, sofar))
|
|
299
|
+
return;
|
|
300
|
+
sofar = emerge(ctx, siteIndex, argPos, sofar);
|
|
301
|
+
cur = ctx.sites[siteIndex];
|
|
302
|
+
}
|
|
303
|
+
};
|
|
304
|
+
/** Source path INTO site `target` (with target's emergence), or undefined. */
|
|
305
|
+
const sourceFlowsOutOf = (ctx, srcSiteIndex, target) => {
|
|
306
|
+
let found;
|
|
307
|
+
climbSourceChain(ctx, srcSiteIndex, (siteIndex, argPos, sofar) => {
|
|
308
|
+
if (siteIndex !== target)
|
|
309
|
+
return false;
|
|
310
|
+
found = emerge(ctx, target, argPos, sofar);
|
|
311
|
+
return true;
|
|
312
|
+
});
|
|
313
|
+
return found;
|
|
314
|
+
};
|
|
315
|
+
// ── accumulators ──────────────────────────────────────────────────────────
|
|
316
|
+
const findingsByIdentity = new Map();
|
|
317
|
+
const killsByIdentity = new Map();
|
|
318
|
+
const recordKill = (sanitizer, killedDef, bindingIdx, kinds) => {
|
|
319
|
+
if (kinds.length === 0)
|
|
320
|
+
return;
|
|
321
|
+
const key = `${pointKey(sanitizer)}|${pointKey(killedDef)}|${bindingIdx}`;
|
|
322
|
+
const existing = killsByIdentity.get(key);
|
|
323
|
+
if (existing)
|
|
324
|
+
for (const k of kinds)
|
|
325
|
+
existing.kinds.add(k);
|
|
326
|
+
else
|
|
327
|
+
killsByIdentity.set(key, {
|
|
328
|
+
kill: { sanitizer, killedDef, bindingIdx },
|
|
329
|
+
kinds: new Set(kinds),
|
|
330
|
+
});
|
|
331
|
+
};
|
|
332
|
+
// KTD6 statement-level finding identity: source occurrence + sink occurrence
|
|
333
|
+
// + kind (NOT entryName). Computed standalone so the worklist can dedup-check
|
|
334
|
+
// BEFORE the cost of chainHops (first write wins; dedup-before-budget).
|
|
335
|
+
const findingKey = (sinkKind, source, sink) => [
|
|
336
|
+
sinkKind,
|
|
337
|
+
pointKey(source.point),
|
|
338
|
+
source.siteIndex,
|
|
339
|
+
source.objectBindingIdx,
|
|
340
|
+
source.property,
|
|
341
|
+
pointKey(sink.point),
|
|
342
|
+
sink.siteIndex,
|
|
343
|
+
sink.argIndex,
|
|
344
|
+
sink.bindingIdx,
|
|
345
|
+
].join('|');
|
|
346
|
+
const recordFinding = (sinkKind, source, sink, hops, hopsTruncated) => {
|
|
347
|
+
const key = findingKey(sinkKind, source, sink);
|
|
348
|
+
if (findingsByIdentity.has(key))
|
|
349
|
+
return;
|
|
350
|
+
const maxHops = limits?.maxHops && limits.maxHops > 0 ? limits.maxHops : Infinity;
|
|
351
|
+
let truncated = hopsTruncated;
|
|
352
|
+
let kept = hops;
|
|
353
|
+
if (hops.length > maxHops) {
|
|
354
|
+
kept = hops.slice(0, maxHops);
|
|
355
|
+
truncated = true;
|
|
356
|
+
}
|
|
357
|
+
findingsByIdentity.set(key, {
|
|
358
|
+
sinkKind,
|
|
359
|
+
source,
|
|
360
|
+
sink,
|
|
361
|
+
hops: kept,
|
|
362
|
+
...(truncated ? { hopsTruncated: true } : {}),
|
|
363
|
+
});
|
|
364
|
+
};
|
|
365
|
+
// ── taint state ───────────────────────────────────────────────────────────
|
|
366
|
+
const taints = new Map();
|
|
367
|
+
const queue = [];
|
|
368
|
+
/** The (binding, def-point) portion of a state key — the def→use fact-table
|
|
369
|
+
* lookup key, source-independent. */
|
|
370
|
+
const defKey = (bindingIdx, point) => `${bindingIdx}:${pointKey(point)}`;
|
|
371
|
+
/** Full taint-state key: the def-point portion plus a ROOT source-occurrence
|
|
372
|
+
* discriminator ({point, siteIndex} — the same source fields recordFinding's
|
|
373
|
+
* identity uses, deliberately excluding `kind`). Distinct sources reaching
|
|
374
|
+
* one def get distinct states, so a second source is no longer dropped
|
|
375
|
+
* (KTD6); same-source multi-path derivations still share a key so their
|
|
376
|
+
* exclusion sets intersect (the raw arm soundly wins). */
|
|
377
|
+
const stateKey = (bindingIdx, point, source) => `${defKey(bindingIdx, point)}#${pointKey(source.point)}:${source.siteIndex}`;
|
|
378
|
+
const deriveTaint = (bindingIdx, point, exclusions, parentKey, source, viaCall) => {
|
|
379
|
+
const key = stateKey(bindingIdx, point, source);
|
|
380
|
+
const existing = taints.get(key);
|
|
381
|
+
if (!existing) {
|
|
382
|
+
taints.set(key, {
|
|
383
|
+
bindingIdx,
|
|
384
|
+
point,
|
|
385
|
+
exclusions,
|
|
386
|
+
parentKey,
|
|
387
|
+
source,
|
|
388
|
+
viaCall,
|
|
389
|
+
processedSize: -1,
|
|
390
|
+
});
|
|
391
|
+
queue.push(key);
|
|
392
|
+
return;
|
|
393
|
+
}
|
|
394
|
+
// Monotone shrink: keep the intersection; re-process only when it got
|
|
395
|
+
// strictly smaller (a less-neutralized derivation is more dangerous).
|
|
396
|
+
const inter = new Set();
|
|
397
|
+
for (const k of existing.exclusions)
|
|
398
|
+
if (exclusions.has(k))
|
|
399
|
+
inter.add(k);
|
|
400
|
+
if (inter.size < existing.exclusions.size) {
|
|
401
|
+
existing.exclusions = inter;
|
|
402
|
+
existing.parentKey = parentKey;
|
|
403
|
+
existing.source = source;
|
|
404
|
+
existing.viaCall = viaCall;
|
|
405
|
+
queue.push(key);
|
|
406
|
+
}
|
|
407
|
+
};
|
|
408
|
+
/** Taint-chain hops from seed to `key`, with a cycle guard (re-derivation
|
|
409
|
+
* can rewire parents into a loop — truncate instead of spinning). */
|
|
410
|
+
const chainHops = (key) => {
|
|
411
|
+
const reversed = [];
|
|
412
|
+
const seen = new Set();
|
|
413
|
+
let cur = key;
|
|
414
|
+
let truncated = false;
|
|
415
|
+
while (cur !== undefined) {
|
|
416
|
+
if (seen.has(cur)) {
|
|
417
|
+
truncated = true;
|
|
418
|
+
break;
|
|
419
|
+
}
|
|
420
|
+
seen.add(cur);
|
|
421
|
+
const t = taints.get(cur);
|
|
422
|
+
if (!t)
|
|
423
|
+
break;
|
|
424
|
+
reversed.push({
|
|
425
|
+
bindingIdx: t.bindingIdx,
|
|
426
|
+
name: bindings[t.bindingIdx]?.name ?? `#${t.bindingIdx}`,
|
|
427
|
+
point: t.point,
|
|
428
|
+
...(t.viaCall ? { viaCall: true } : {}),
|
|
429
|
+
});
|
|
430
|
+
cur = t.parentKey;
|
|
431
|
+
}
|
|
432
|
+
return { hops: reversed.reverse(), truncated };
|
|
433
|
+
};
|
|
434
|
+
/** Intersection of path kind-sets; viaCall = any path through a call. */
|
|
435
|
+
const summarizePaths = (paths) => {
|
|
436
|
+
let kinds;
|
|
437
|
+
let viaCall = false;
|
|
438
|
+
for (const p of paths) {
|
|
439
|
+
viaCall ||= p.viaCall;
|
|
440
|
+
if (kinds === undefined) {
|
|
441
|
+
kinds = p.kinds;
|
|
442
|
+
}
|
|
443
|
+
else {
|
|
444
|
+
const inter = new Set();
|
|
445
|
+
for (const k of kinds)
|
|
446
|
+
if (p.kinds.has(k))
|
|
447
|
+
inter.add(k);
|
|
448
|
+
kinds = inter;
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
return { kinds: kinds ?? EMPTY_KINDS, viaCall };
|
|
452
|
+
};
|
|
453
|
+
/**
|
|
454
|
+
* Taint every def of `ctx`'s statement from one input. `pathsInto(c)`
|
|
455
|
+
* supplies the input's strict occurrence paths into call site `c` —
|
|
456
|
+
* defining the resultDefs precision and the kill evidence; an empty list
|
|
457
|
+
* means "no provable flow" and the floor applies (taint with the input's
|
|
458
|
+
* own exclusions, no kill).
|
|
459
|
+
*/
|
|
460
|
+
const feedDefs = (ctx, inputExclusions, parentKey, source, pathsInto) => {
|
|
461
|
+
const defs = [...ctx.facts.defs, ...(ctx.facts.mayDefs ?? [])];
|
|
462
|
+
if (defs.length === 0)
|
|
463
|
+
return;
|
|
464
|
+
const seen = new Set();
|
|
465
|
+
for (const d of defs) {
|
|
466
|
+
if (seen.has(d))
|
|
467
|
+
continue;
|
|
468
|
+
seen.add(d);
|
|
469
|
+
const rdSites = ctx.resultDefSites.get(d);
|
|
470
|
+
let addKinds = EMPTY_KINDS;
|
|
471
|
+
let viaCall = false;
|
|
472
|
+
if (rdSites) {
|
|
473
|
+
const paths = [];
|
|
474
|
+
for (const c of rdSites)
|
|
475
|
+
paths.push(...pathsInto(c));
|
|
476
|
+
if (paths.length > 0) {
|
|
477
|
+
const summary = summarizePaths(paths);
|
|
478
|
+
addKinds = summary.kinds;
|
|
479
|
+
viaCall = summary.viaCall;
|
|
480
|
+
for (const p of paths) {
|
|
481
|
+
for (const san of p.sanitizers) {
|
|
482
|
+
recordKill(ctx.point, ctx.point, d, san.kinds);
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
// else: floor — tainted with no exclusions added, no kill (the input
|
|
487
|
+
// provably never flows into the producing call; conflation FP pinned).
|
|
488
|
+
}
|
|
489
|
+
const exclusions = addKinds.size === 0 ? inputExclusions : new Set([...inputExclusions, ...addKinds]);
|
|
490
|
+
deriveTaint(d, ctx.point, exclusions, parentKey, source, viaCall);
|
|
491
|
+
}
|
|
492
|
+
};
|
|
493
|
+
// ── rule (b) + seeding: statements with matched sources ───────────────────
|
|
494
|
+
for (const sm of matches.statements) {
|
|
495
|
+
if (sm.sources.length === 0)
|
|
496
|
+
continue;
|
|
497
|
+
const ctx = contextAt(sm.blockIndex, sm.statementIndex);
|
|
498
|
+
if (!ctx)
|
|
499
|
+
continue;
|
|
500
|
+
for (const src of sm.sources) {
|
|
501
|
+
const srcSite = ctx.sites[src.siteIndex];
|
|
502
|
+
if (srcSite?.object === undefined || srcSite.property === undefined)
|
|
503
|
+
continue;
|
|
504
|
+
const sourceOcc = {
|
|
505
|
+
point: ctx.point,
|
|
506
|
+
siteIndex: src.siteIndex,
|
|
507
|
+
objectBindingIdx: srcSite.object,
|
|
508
|
+
property: srcSite.property,
|
|
509
|
+
kind: src.entry.kind,
|
|
510
|
+
};
|
|
511
|
+
// Statement-local sink checks along the member-read's parent chain.
|
|
512
|
+
climbSourceChain(ctx, src.siteIndex, (siteIndex, argPos, sofar) => {
|
|
513
|
+
for (const sink of ctx.sinksBySite.get(siteIndex) ?? []) {
|
|
514
|
+
if (!sink.argPositions.includes(argPos))
|
|
515
|
+
continue;
|
|
516
|
+
const kind = sink.entry.kind;
|
|
517
|
+
if (!sofar.kinds.has(kind)) {
|
|
518
|
+
recordFinding(kind, sourceOcc, {
|
|
519
|
+
point: ctx.point,
|
|
520
|
+
siteIndex,
|
|
521
|
+
argIndex: argPos,
|
|
522
|
+
bindingIdx: srcSite.object,
|
|
523
|
+
entryName: sink.entry.name,
|
|
524
|
+
}, [
|
|
525
|
+
{
|
|
526
|
+
bindingIdx: srcSite.object,
|
|
527
|
+
name: bindings[srcSite.object]?.name ?? `#${srcSite.object}`,
|
|
528
|
+
point: ctx.point,
|
|
529
|
+
...(sofar.viaCall ? { viaCall: true } : {}),
|
|
530
|
+
},
|
|
531
|
+
], false);
|
|
532
|
+
}
|
|
533
|
+
else {
|
|
534
|
+
for (const san of sofar.sanitizers) {
|
|
535
|
+
if (san.kinds.includes(kind)) {
|
|
536
|
+
recordKill(ctx.point, ctx.point, srcSite.object, san.kinds);
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
return false;
|
|
542
|
+
});
|
|
543
|
+
// Seed every def of the statement (precision floor + resultDefs kills).
|
|
544
|
+
feedDefs(ctx, EMPTY_KINDS, undefined, sourceOcc, (c) => {
|
|
545
|
+
const p = sourceFlowsOutOf(ctx, src.siteIndex, c);
|
|
546
|
+
return p ? [p] : [];
|
|
547
|
+
});
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
// ── rule (a): worklist over def→use facts ─────────────────────────────────
|
|
551
|
+
const factsByDef = new Map();
|
|
552
|
+
for (const f of defUse.facts) {
|
|
553
|
+
const key = defKey(f.bindingIdx, f.def);
|
|
554
|
+
const list = factsByDef.get(key);
|
|
555
|
+
if (list)
|
|
556
|
+
list.push(f);
|
|
557
|
+
else
|
|
558
|
+
factsByDef.set(key, [f]);
|
|
559
|
+
}
|
|
560
|
+
// Strict-FIFO worklist via a head cursor (not Array.shift, which is O(N) per
|
|
561
|
+
// dequeue). FIFO order is load-bearing beyond perf: chainHops reconstructs
|
|
562
|
+
// hops from the live `taints` map, whose parentKey/source/viaCall are
|
|
563
|
+
// rewritten order-sensitively on monotone shrink — so hop-content
|
|
564
|
+
// determinism is contingent on dequeue order matching enqueue order. Do NOT
|
|
565
|
+
// sort or reprioritize the worklist.
|
|
566
|
+
let head = 0;
|
|
567
|
+
while (head < queue.length) {
|
|
568
|
+
const key = queue[head++];
|
|
569
|
+
// Reclaim the consumed prefix periodically so the array doesn't grow
|
|
570
|
+
// unbounded across a long run (order-preserving — pure memory hygiene).
|
|
571
|
+
if (head > 1024 && head * 2 > queue.length) {
|
|
572
|
+
queue.splice(0, head);
|
|
573
|
+
head = 0;
|
|
574
|
+
}
|
|
575
|
+
const t = taints.get(key);
|
|
576
|
+
if (t.processedSize === t.exclusions.size)
|
|
577
|
+
continue; // no-op requeue
|
|
578
|
+
t.processedSize = t.exclusions.size;
|
|
579
|
+
const b = t.bindingIdx;
|
|
580
|
+
const E = t.exclusions;
|
|
581
|
+
// Facts are keyed by (binding, def-point) only — look up by the def portion
|
|
582
|
+
// of this state, not the source-discriminated state key.
|
|
583
|
+
for (const fact of factsByDef.get(defKey(b, t.point)) ?? []) {
|
|
584
|
+
const ctx = contextAt(fact.use.blockIndex, fact.use.stmtIndex);
|
|
585
|
+
if (!ctx)
|
|
586
|
+
continue;
|
|
587
|
+
// Sink check: occurrences of `b` at matched sink argument positions.
|
|
588
|
+
for (const [siteIndex, sinks] of ctx.sinksBySite) {
|
|
589
|
+
for (const sink of sinks) {
|
|
590
|
+
const kind = sink.entry.kind;
|
|
591
|
+
for (const argPos of sink.argPositions) {
|
|
592
|
+
const paths = pathsIntoPosition(ctx, b, siteIndex, argPos);
|
|
593
|
+
if (paths.length === 0)
|
|
594
|
+
continue;
|
|
595
|
+
if (E.has(kind))
|
|
596
|
+
continue; // suppressed at def time; kill already recorded
|
|
597
|
+
const justify = paths.find((p) => !p.kinds.has(kind));
|
|
598
|
+
if (justify) {
|
|
599
|
+
const sinkOcc = {
|
|
600
|
+
point: ctx.point,
|
|
601
|
+
siteIndex,
|
|
602
|
+
argIndex: argPos,
|
|
603
|
+
bindingIdx: b,
|
|
604
|
+
entryName: sink.entry.name,
|
|
605
|
+
};
|
|
606
|
+
// Dedup BEFORE chainHops: already-recorded identities discard
|
|
607
|
+
// their hop chain anyway (first write wins), so skip the walk.
|
|
608
|
+
if (findingsByIdentity.has(findingKey(kind, t.source, sinkOcc)))
|
|
609
|
+
continue;
|
|
610
|
+
const chain = chainHops(key);
|
|
611
|
+
chain.hops.push({
|
|
612
|
+
bindingIdx: b,
|
|
613
|
+
name: bindings[b]?.name ?? `#${b}`,
|
|
614
|
+
point: ctx.point,
|
|
615
|
+
...(justify.viaCall ? { viaCall: true } : {}),
|
|
616
|
+
});
|
|
617
|
+
recordFinding(kind, t.source, sinkOcc, chain.hops, chain.truncated);
|
|
618
|
+
}
|
|
619
|
+
else {
|
|
620
|
+
// EVERY path interposed — value-position kill(s) held.
|
|
621
|
+
for (const p of paths) {
|
|
622
|
+
for (const san of p.sanitizers) {
|
|
623
|
+
if (san.kinds.includes(kind))
|
|
624
|
+
recordKill(ctx.point, ctx.point, b, san.kinds);
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
// Def-feed: the use statement's own defs become tainted.
|
|
632
|
+
feedDefs(ctx, E, key, t.source, (c) => flowsOutOf(ctx, b, c, new Set()));
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
// ── deterministic assembly ────────────────────────────────────────────────
|
|
636
|
+
const comparePoints = (a, b) => a.blockIndex - b.blockIndex || a.stmtIndex - b.stmtIndex;
|
|
637
|
+
const findings = [...findingsByIdentity.values()].sort((a, b) => comparePoints(a.source.point, b.source.point) ||
|
|
638
|
+
a.source.siteIndex - b.source.siteIndex ||
|
|
639
|
+
comparePoints(a.sink.point, b.sink.point) ||
|
|
640
|
+
a.sink.siteIndex - b.sink.siteIndex ||
|
|
641
|
+
a.sink.argIndex - b.sink.argIndex ||
|
|
642
|
+
a.sink.bindingIdx - b.sink.bindingIdx ||
|
|
643
|
+
(kindRank.get(a.sinkKind) ?? 99) - (kindRank.get(b.sinkKind) ?? 99));
|
|
644
|
+
const maxFindings = limits?.maxFindingsPerFunction && limits.maxFindingsPerFunction > 0
|
|
645
|
+
? limits.maxFindingsPerFunction
|
|
646
|
+
: Infinity;
|
|
647
|
+
const kept = findings.length > maxFindings ? findings.slice(0, maxFindings) : findings;
|
|
648
|
+
const kills = [...killsByIdentity.values()]
|
|
649
|
+
.map(({ kill, kinds }) => ({ ...kill, neutralized: sortKinds(kinds) }))
|
|
650
|
+
.sort((a, b) => comparePoints(a.sanitizer, b.sanitizer) ||
|
|
651
|
+
comparePoints(a.killedDef, b.killedDef) ||
|
|
652
|
+
a.bindingIdx - b.bindingIdx);
|
|
653
|
+
return {
|
|
654
|
+
status: 'computed',
|
|
655
|
+
findings: kept,
|
|
656
|
+
kills,
|
|
657
|
+
droppedFindings: findings.length - kept.length,
|
|
658
|
+
};
|
|
659
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Taint-site safety validation (#2083 M3 U1, plan KTD2).
|
|
3
|
+
*
|
|
4
|
+
* Mirrors `hasEmitSafeFacts` (cfg/emit.ts): an untrusted `cfgSideChannel`
|
|
5
|
+
* element — possibly from a corrupted durable parsedfile store — must never
|
|
6
|
+
* crash the taint pass or fabricate matches from out-of-range indices. The
|
|
7
|
+
* degradation contract is per-FUNCTION and one-directional: a CFG whose sites
|
|
8
|
+
* fail this check is SKIPPED FOR TAINT ONLY — the BasicBlock/CFG layer and
|
|
9
|
+
* the REACHING_DEF projection (guarded by their own checks) are unaffected.
|
|
10
|
+
*
|
|
11
|
+
* Checked: exactly the indices the taint matcher dereferences — binding
|
|
12
|
+
* indices (`receiver`/`object`/`resultDefs`/arg occurrences) against the
|
|
13
|
+
* function's binding table, and intra-statement site references (`parent`
|
|
14
|
+
* site / via-tags) against the OWNING statement's `sites` array. Site
|
|
15
|
+
* references are statement-local by construction (each statement's
|
|
16
|
+
* FactAccumulator starts at index 0); a cross-statement reference is
|
|
17
|
+
* corruption, not a feature.
|
|
18
|
+
*
|
|
19
|
+
* Lives in `taint/` (not cfg/emit.ts): U4's taint emit path is the only
|
|
20
|
+
* consumer, and the guard must evolve with the matcher that dereferences
|
|
21
|
+
* these fields.
|
|
22
|
+
*/
|
|
23
|
+
import type { FunctionCfg } from '../cfg/types.js';
|
|
24
|
+
/**
|
|
25
|
+
* Whether a structurally-valid CFG's M3 `sites` annotations are safe to feed
|
|
26
|
+
* to the taint matcher/propagator. `true` when no statement carries sites
|
|
27
|
+
* (pre-M3 channel, or no calls) — absence is the well-formed empty case.
|
|
28
|
+
*/
|
|
29
|
+
export declare const hasTaintSafeSites: (cfg: FunctionCfg) => boolean;
|