gitnexus 1.6.8-rc.8 → 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 +23 -2
- package/dist/core/ingestion/languages/c-cpp.js +17 -5
- 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 +8 -1
- 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 +44 -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/clone-safety.d.ts +109 -0
- package/dist/core/ingestion/workers/clone-safety.js +465 -0
- package/dist/core/ingestion/workers/parse-worker.d.ts +23 -0
- package/dist/core/ingestion/workers/parse-worker.js +81 -53
- package/dist/core/ingestion/workers/post-result.d.ts +22 -0
- package/dist/core/ingestion/workers/post-result.js +87 -0
- package/dist/core/ingestion/workers/result-merge.d.ts +20 -0
- package/dist/core/ingestion/workers/result-merge.js +43 -0
- package/dist/core/ingestion/workers/worker-pool.d.ts +15 -40
- package/dist/core/ingestion/workers/worker-pool.js +43 -16
- 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-ipc.d.ts +58 -0
- package/dist/server/analyze-worker-ipc.js +16 -0
- package/dist/server/analyze-worker.d.ts +17 -1
- package/dist/server/analyze-worker.js +7 -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 +44 -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,278 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Import-aware taint-site matcher (#2083 M3 U2, plan KTD7).
|
|
3
|
+
*
|
|
4
|
+
* Classifies a function's harvested {@link SiteRecord}s against a registered
|
|
5
|
+
* {@link SourceSinkSanitizerSpec}: which member reads are SOURCES, which
|
|
6
|
+
* call/new sites are SINKS (and at which argument positions), and which are
|
|
7
|
+
* SANITIZERS. Pure main-thread data work — sites + bindings come from the U1
|
|
8
|
+
* worker harvest, imports from `ParsedFile.parsedImports`; no AST, no I/O.
|
|
9
|
+
*
|
|
10
|
+
* PRECONDITION: the caller must gate the CFG through `hasTaintSafeSites`
|
|
11
|
+
* (taint/site-safety.ts) first — this module dereferences binding/site
|
|
12
|
+
* indices without re-validating them.
|
|
13
|
+
*
|
|
14
|
+
* ## Callee resolution precedence (bare and member-rooted calls)
|
|
15
|
+
*
|
|
16
|
+
* 1. ESM import join — the callee root's local name is resolved through the
|
|
17
|
+
* {@link TaintImportIndex} built from `parsedImports` (`named`/`alias`
|
|
18
|
+
* members, `namespace`/default-import module handles); `import { exec as
|
|
19
|
+
* run } from 'child_process'` makes `run(c)` resolve to
|
|
20
|
+
* `child_process.exec`, and `import * as cp …` makes `cp.exec(c)` resolve
|
|
21
|
+
* the same way.
|
|
22
|
+
* 2. require-literal join — a binding whose in-function defining site carries
|
|
23
|
+
* `requireArg` resolves like a namespace handle (`const cp =
|
|
24
|
+
* require('child_process'); cp.exec(c)`). A BARE call of a require-joined
|
|
25
|
+
* binding is matched under BOTH interpretations, `<module>.default` (the
|
|
26
|
+
* module/default export invoked directly) and `<module>.<localName>`
|
|
27
|
+
* (non-renamed destructured require — the harvest attaches `resultDefs`
|
|
28
|
+
* to destructured bindings without recording the property path, and the
|
|
29
|
+
* binding name IS the member name in the non-renamed case).
|
|
30
|
+
* 3. Bare-name fallback — TRUE GLOBALS only (`global: true` entries: `eval`,
|
|
31
|
+
* `new Function`, `encodeURIComponent`), and only when the name is neither
|
|
32
|
+
* import-bound nor shadowed. Conventional receiver names (`req`/`request`
|
|
33
|
+
* member-read sources, `res.send`, `.query`/`.execute`) are matched
|
|
34
|
+
* name-based by their own mechanisms, never via the global fallback.
|
|
35
|
+
*
|
|
36
|
+
* ## Shadowing rule (exact)
|
|
37
|
+
*
|
|
38
|
+
* A name is treated as function-local — blocking import/global resolution —
|
|
39
|
+
* iff the function's binding table contains a NON-`synthetic` entry with that
|
|
40
|
+
* name (an in-function `function exec(){}` / `const exec = …`). Synthetic
|
|
41
|
+
* bindings (kind `module`, `synthetic: true`) are imports, true globals, or
|
|
42
|
+
* enclosing-scope captures and do not shadow. Member-call roots use the
|
|
43
|
+
* harvested `receiver` binding index directly (no name scan).
|
|
44
|
+
*
|
|
45
|
+
* ## Documented resolution gaps (direction stated, per plan KTD10)
|
|
46
|
+
*
|
|
47
|
+
* - MODULE-LEVEL `const cp = require('child_process')`: the binding is
|
|
48
|
+
* synthetic inside the function, produces no `ParsedImport`, and its
|
|
49
|
+
* defining site lives outside the function's harvested sites — the
|
|
50
|
+
* require join cannot see it. Module-mechanism sinks miss (FN) and
|
|
51
|
+
* sanitizers don't kill (FP noise — never a false kill, the safe
|
|
52
|
+
* direction). Only in-function requires resolve.
|
|
53
|
+
* - RENAMED destructured require (`const { exec: run } = require(…)`):
|
|
54
|
+
* the dual interpretation resolves `run` to `child_process.run` — no
|
|
55
|
+
* match (FN). Non-renamed destructures resolve exactly.
|
|
56
|
+
* - CONSERVATIVE shadow scan for bare calls: ANY non-synthetic binding of
|
|
57
|
+
* the callee name anywhere in the function blocks import/global
|
|
58
|
+
* resolution, even when the shadow is block-scoped elsewhere and the call
|
|
59
|
+
* site actually sees the import (FN; rare; safe for sanitizers).
|
|
60
|
+
* - MODULE-LEVEL user declarations are indistinguishable from imports in
|
|
61
|
+
* the binding table (both synthetic). ESM forbids a module-level
|
|
62
|
+
* declaration colliding with an import name, so the import join is
|
|
63
|
+
* authoritative when an import exists; a module-level user function
|
|
64
|
+
* shadowing a TRUE GLOBAL (e.g. a local `encodeURIComponent`) is not
|
|
65
|
+
* detectable and would still match (pathological; accepted).
|
|
66
|
+
* - Handle COPIES (`const c2 = cp; c2.exec(…)`) are not followed — joins
|
|
67
|
+
* are one level deep (binding → import/require), never through
|
|
68
|
+
* assignments (FN).
|
|
69
|
+
* - `this.`/`super.`-rooted and call-rooted callee chains have no
|
|
70
|
+
* resolvable root: only the syntactic `anyReceiver`/`receivers`
|
|
71
|
+
* mechanisms can match them.
|
|
72
|
+
* - `reexport`/`wildcard`/`dynamic-*`/`side-effect` imports introduce no
|
|
73
|
+
* matcher-visible local binding and are skipped by the index.
|
|
74
|
+
*/
|
|
75
|
+
const stripNodeScheme = (specifier) => specifier.startsWith('node:') ? specifier.slice('node:'.length) : specifier;
|
|
76
|
+
/**
|
|
77
|
+
* Build the local-name → module/member index from a file's `parsedImports`.
|
|
78
|
+
* Only `named`/`alias`/`namespace` kinds bind matcher-visible local names;
|
|
79
|
+
* `importedName === 'default'` collapses to a module handle.
|
|
80
|
+
*/
|
|
81
|
+
export function buildTaintImportIndex(imports) {
|
|
82
|
+
const index = new Map();
|
|
83
|
+
for (const imp of imports) {
|
|
84
|
+
if (imp.kind === 'named' || imp.kind === 'alias') {
|
|
85
|
+
const module = stripNodeScheme(imp.targetRaw);
|
|
86
|
+
index.set(imp.localName, imp.importedName === 'default' ? { module } : { module, member: imp.importedName });
|
|
87
|
+
}
|
|
88
|
+
else if (imp.kind === 'namespace') {
|
|
89
|
+
index.set(imp.localName, { module: stripNodeScheme(imp.targetRaw) });
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return index;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Classify a function's harvested sites against a language spec. See the
|
|
96
|
+
* module doc for resolution precedence, the shadowing rule, and gaps.
|
|
97
|
+
*/
|
|
98
|
+
export function matchFunctionSites(cfg, spec, imports) {
|
|
99
|
+
const bindings = cfg.bindings ?? [];
|
|
100
|
+
// Non-synthetic (in-function-declared) binding indices by name — the
|
|
101
|
+
// shadow scan + bare-call require-join lookup.
|
|
102
|
+
const nonSyntheticByName = new Map();
|
|
103
|
+
bindings.forEach((b, i) => {
|
|
104
|
+
if (b.synthetic === true)
|
|
105
|
+
return;
|
|
106
|
+
const list = nonSyntheticByName.get(b.name);
|
|
107
|
+
if (list)
|
|
108
|
+
list.push(i);
|
|
109
|
+
else
|
|
110
|
+
nonSyntheticByName.set(b.name, [i]);
|
|
111
|
+
});
|
|
112
|
+
// require-literal join: binding index → module specifier. A binding def'd
|
|
113
|
+
// by two DIFFERENT require literals is conflicted → dropped (resolving it
|
|
114
|
+
// either way could fabricate a sanitizer kill).
|
|
115
|
+
const requireByBinding = new Map();
|
|
116
|
+
const conflicted = new Set();
|
|
117
|
+
for (const block of cfg.blocks) {
|
|
118
|
+
for (const stmt of block.statements ?? []) {
|
|
119
|
+
for (const site of stmt.sites ?? []) {
|
|
120
|
+
if (site.requireArg === undefined || site.resultDefs === undefined)
|
|
121
|
+
continue;
|
|
122
|
+
const module = stripNodeScheme(site.requireArg);
|
|
123
|
+
for (const def of site.resultDefs) {
|
|
124
|
+
if (conflicted.has(def))
|
|
125
|
+
continue;
|
|
126
|
+
const prior = requireByBinding.get(def);
|
|
127
|
+
if (prior === undefined)
|
|
128
|
+
requireByBinding.set(def, module);
|
|
129
|
+
else if (prior !== module) {
|
|
130
|
+
requireByBinding.delete(def);
|
|
131
|
+
conflicted.add(def);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
const resolveCallee = (site) => {
|
|
138
|
+
if (site.callee === undefined)
|
|
139
|
+
return undefined;
|
|
140
|
+
const path = site.callee.split('.');
|
|
141
|
+
const root = path[0];
|
|
142
|
+
const rest = path.slice(1);
|
|
143
|
+
const canonical = [];
|
|
144
|
+
let globalRoot = false;
|
|
145
|
+
if (site.receiver !== undefined) {
|
|
146
|
+
// Member chain with an identifier root — origin known by binding index.
|
|
147
|
+
const rb = bindings[site.receiver];
|
|
148
|
+
if (rb.synthetic === true) {
|
|
149
|
+
const imp = imports.get(rb.name);
|
|
150
|
+
if (imp !== undefined) {
|
|
151
|
+
const base = imp.member === undefined ? [imp.module] : [imp.module, imp.member];
|
|
152
|
+
canonical.push([...base, ...rest].join('.'));
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
else {
|
|
156
|
+
const module = requireByBinding.get(site.receiver);
|
|
157
|
+
if (module !== undefined)
|
|
158
|
+
canonical.push([module, ...rest].join('.'));
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
else if (path.length === 1) {
|
|
162
|
+
// Bare call. `this`/`super`/call-rooted chains never get here (those
|
|
163
|
+
// are dotted-without-receiver or callee-less).
|
|
164
|
+
const locals = nonSyntheticByName.get(root);
|
|
165
|
+
if (locals !== undefined) {
|
|
166
|
+
// Shadowed by an in-function declaration — only the require join
|
|
167
|
+
// applies, under the dual interpretation (module doc).
|
|
168
|
+
for (const idx of locals) {
|
|
169
|
+
const module = requireByBinding.get(idx);
|
|
170
|
+
if (module !== undefined)
|
|
171
|
+
canonical.push(`${module}.default`, `${module}.${root}`);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
else {
|
|
175
|
+
const imp = imports.get(root);
|
|
176
|
+
if (imp !== undefined) {
|
|
177
|
+
canonical.push(imp.member === undefined ? `${imp.module}.default` : `${imp.module}.${imp.member}`);
|
|
178
|
+
}
|
|
179
|
+
else {
|
|
180
|
+
globalRoot = true;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
return { path, canonical, globalRoot };
|
|
185
|
+
};
|
|
186
|
+
/** The spread/template/registered-position rule (see MatchedSinkCall doc). */
|
|
187
|
+
const positionMatches = (entry, site, p) => {
|
|
188
|
+
if (site.template === true)
|
|
189
|
+
return true;
|
|
190
|
+
if (entry.args === undefined)
|
|
191
|
+
return true;
|
|
192
|
+
if (site.spread !== undefined && p >= site.spread) {
|
|
193
|
+
const spread = site.spread;
|
|
194
|
+
return entry.args.some((q) => q >= spread);
|
|
195
|
+
}
|
|
196
|
+
return entry.args.includes(p);
|
|
197
|
+
};
|
|
198
|
+
const sinkMechanismHit = (entry, site, r) => {
|
|
199
|
+
if (entry.module !== undefined)
|
|
200
|
+
return r.canonical.includes(`${entry.module}.${entry.name}`);
|
|
201
|
+
if (entry.global === true) {
|
|
202
|
+
return (r.globalRoot &&
|
|
203
|
+
r.path.length === 1 &&
|
|
204
|
+
r.path[0] === entry.name &&
|
|
205
|
+
(entry.newOnly !== true || site.kind === 'new'));
|
|
206
|
+
}
|
|
207
|
+
if (entry.anyReceiver === true) {
|
|
208
|
+
return r.path.length >= 2 && r.path[r.path.length - 1] === entry.name;
|
|
209
|
+
}
|
|
210
|
+
if (entry.receivers !== undefined) {
|
|
211
|
+
return r.path.length === 2 && entry.receivers.includes(r.path[0]) && r.path[1] === entry.name;
|
|
212
|
+
}
|
|
213
|
+
return false;
|
|
214
|
+
};
|
|
215
|
+
// Sanitizers: module + global mechanisms ONLY — never receiver-conventional,
|
|
216
|
+
// never bare-name for non-globals (a false kill is the forbidden direction).
|
|
217
|
+
const sanitizerMechanismHit = (entry, r) => {
|
|
218
|
+
if (entry.module !== undefined)
|
|
219
|
+
return r.canonical.includes(`${entry.module}.${entry.name}`);
|
|
220
|
+
if (entry.global === true) {
|
|
221
|
+
return r.globalRoot && r.path.length === 1 && r.path[0] === entry.name;
|
|
222
|
+
}
|
|
223
|
+
return false;
|
|
224
|
+
};
|
|
225
|
+
const statements = [];
|
|
226
|
+
let hasSource = false;
|
|
227
|
+
let hasSink = false;
|
|
228
|
+
cfg.blocks.forEach((block, blockIndex) => {
|
|
229
|
+
block.statements?.forEach((stmt, statementIndex) => {
|
|
230
|
+
const sites = stmt.sites;
|
|
231
|
+
if (sites === undefined || sites.length === 0)
|
|
232
|
+
return;
|
|
233
|
+
const sources = [];
|
|
234
|
+
const sinks = [];
|
|
235
|
+
const sanitizers = [];
|
|
236
|
+
sites.forEach((site, siteIndex) => {
|
|
237
|
+
if (site.kind === 'member-read') {
|
|
238
|
+
if (site.object === undefined || site.property === undefined)
|
|
239
|
+
return;
|
|
240
|
+
const objectName = bindings[site.object].name;
|
|
241
|
+
const property = site.property;
|
|
242
|
+
for (const entry of spec.sources) {
|
|
243
|
+
if (entry.objects.includes(objectName) && entry.properties.includes(property)) {
|
|
244
|
+
sources.push({ siteIndex, entry });
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
// call / new
|
|
250
|
+
const resolved = resolveCallee(site);
|
|
251
|
+
if (resolved === undefined)
|
|
252
|
+
return;
|
|
253
|
+
for (const entry of spec.sinks) {
|
|
254
|
+
if (!sinkMechanismHit(entry, site, resolved))
|
|
255
|
+
continue;
|
|
256
|
+
const argPositions = [];
|
|
257
|
+
site.args?.forEach((occurrences, p) => {
|
|
258
|
+
if (occurrences.length > 0 && positionMatches(entry, site, p))
|
|
259
|
+
argPositions.push(p);
|
|
260
|
+
});
|
|
261
|
+
if (argPositions.length > 0)
|
|
262
|
+
sinks.push({ siteIndex, entry, argPositions });
|
|
263
|
+
}
|
|
264
|
+
for (const entry of spec.sanitizers) {
|
|
265
|
+
if (!sanitizerMechanismHit(entry, resolved))
|
|
266
|
+
continue;
|
|
267
|
+
sanitizers.push({ siteIndex, entry, resultDefs: site.resultDefs ?? [] });
|
|
268
|
+
}
|
|
269
|
+
});
|
|
270
|
+
if (sources.length === 0 && sinks.length === 0 && sanitizers.length === 0)
|
|
271
|
+
return;
|
|
272
|
+
hasSource ||= sources.length > 0;
|
|
273
|
+
hasSink ||= sinks.length > 0;
|
|
274
|
+
statements.push({ blockIndex, statementIndex, line: stmt.line, sources, sinks, sanitizers });
|
|
275
|
+
});
|
|
276
|
+
});
|
|
277
|
+
return { statements, hasSource, hasSink };
|
|
278
|
+
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Taint-path reason codec (#2083 M3 U4/U6, plan KTD6).
|
|
3
|
+
*
|
|
4
|
+
* THE one shared encoder/decoder for the hop-encoded `reason` carried on
|
|
5
|
+
* persisted `TAINTED` edges: the U4 emit path writes it, the U6 MCP `explain`
|
|
6
|
+
* tool reads it. Two hand-rolled copies of a wire format drift — both sides
|
|
7
|
+
* MUST import from here.
|
|
8
|
+
*
|
|
9
|
+
* ## Wire format (version `1`)
|
|
10
|
+
*
|
|
11
|
+
* ```
|
|
12
|
+
* 1[;<kind>]|<name>:<line>[:<flags>]|<name>:<line>[:<flags>]|…[|~]
|
|
13
|
+
* ```
|
|
14
|
+
*
|
|
15
|
+
* - One-character version prefix (`TAINT_PATH_CODEC_VERSION`), then an
|
|
16
|
+
* OPTIONAL `;<kind>` header segment, then ordered source→sink hops, each
|
|
17
|
+
* `variable:line[:flags]`.
|
|
18
|
+
* - `kind` is the finding's sink category (`SinkKind`, e.g.
|
|
19
|
+
* `command-injection`). It rides the reason because it is the ONLY
|
|
20
|
+
* persisted channel: the CodeRelation columns are
|
|
21
|
+
* `type/confidence/reason/step` — `step` is INT32 and the emit-time edge id
|
|
22
|
+
* (which embeds the kind) is not a stored column. The U6 `explain` tool
|
|
23
|
+
* reads it for finding classification. Charset `[a-z0-9-]` (printable
|
|
24
|
+
* ASCII, disjoint from every structural delimiter); `;` itself is printable
|
|
25
|
+
* ASCII and never appears in hop names (identifier charset) or flags.
|
|
26
|
+
* U6 deviation note: this header was added by U6 WITHIN version `1` —
|
|
27
|
+
* U4 and U6 ship in the same release, so no reason string without the
|
|
28
|
+
* header was ever persisted by a released build; the decoder still accepts
|
|
29
|
+
* header-less strings (`kind` simply decodes as `undefined`).
|
|
30
|
+
* - `flags` is a lowercase-letter set; only `c` (= the hop passed through an
|
|
31
|
+
* unmodeled call, KTD5 `viaCall`) is defined today — the rest of the
|
|
32
|
+
* alphabet is RESERVED, and the decoder accepts unknown flag letters so a
|
|
33
|
+
* future writer's output stays decodable.
|
|
34
|
+
* - A trailing `|~` segment is the TRUNCATION MARKER: the encoded path is a
|
|
35
|
+
* source-side PREFIX of the real one (hop cap, byte cap, or an unencodable
|
|
36
|
+
* hop name). Decoders MUST report it as "path incomplete" — never an error.
|
|
37
|
+
*
|
|
38
|
+
* ## Delimiter / round-trip discipline (KTD6)
|
|
39
|
+
*
|
|
40
|
+
* Every structural character (`|`, `:`, `~`, digits, flag letters) is
|
|
41
|
+
* printable ASCII: `sanitizeUTF8` (csv-generator.ts) strips control
|
|
42
|
+
* characters, lone surrogates, and U+FFFE/FFFF — printable ASCII passes
|
|
43
|
+
* through byte-exact, so the encoding survives `escapeCSVField ∘
|
|
44
|
+
* sanitizeUTF8` and the DB load unchanged (pinned by the round-trip test).
|
|
45
|
+
* None of the delimiters can appear in a JS identifier.
|
|
46
|
+
*
|
|
47
|
+
* Hop names are identifier-charset by U1 construction (the harvest records
|
|
48
|
+
* binding names), but the encoder DEFENDS anyway: a hop whose name falls
|
|
49
|
+
* outside the safe charset (or whose line is not a non-negative integer) is
|
|
50
|
+
* never emitted — encoding stops at the offending hop and sets the truncation
|
|
51
|
+
* marker, preserving the prefix-of-the-true-path invariant rather than
|
|
52
|
+
* corrupting the format. (`#` is in the charset: JS private names are
|
|
53
|
+
* `#field`, and the propagation engine's fallback hop names are `#<idx>`.)
|
|
54
|
+
*
|
|
55
|
+
* The byte cap (`TAINT_REASON_MAX_BYTES`, KTD6's "absolute reason-byte cap")
|
|
56
|
+
* bounds the persisted reason column regardless of hop caps: overflow drops
|
|
57
|
+
* TRAILING hops (keeps the source side) and sets the marker. All structural
|
|
58
|
+
* chars and valid names are single-byte ASCII, so `string.length` IS the
|
|
59
|
+
* byte length.
|
|
60
|
+
*/
|
|
61
|
+
/** One-character format version prefix. Bump on any wire-format change. */
|
|
62
|
+
export declare const TAINT_PATH_CODEC_VERSION = "1";
|
|
63
|
+
/**
|
|
64
|
+
* Absolute cap on the encoded reason's byte length (KTD6). 4096 comfortably
|
|
65
|
+
* holds ~100 hops of realistic identifiers — far beyond the default hop cap
|
|
66
|
+
* (32) — while bounding the persisted column even at `maxHops: 0` (unlimited).
|
|
67
|
+
*/
|
|
68
|
+
export declare const TAINT_REASON_MAX_BYTES = 4096;
|
|
69
|
+
/** The truncation-marker segment content (rides as a trailing `|~`). */
|
|
70
|
+
export declare const TAINT_PATH_TRUNCATION_MARKER = "~";
|
|
71
|
+
/** Encoder input hop — shape-compatible with `TaintHop` (propagate.ts). */
|
|
72
|
+
export interface TaintPathHopInput {
|
|
73
|
+
readonly name: string;
|
|
74
|
+
readonly line: number;
|
|
75
|
+
readonly viaCall?: boolean;
|
|
76
|
+
}
|
|
77
|
+
export interface EncodeTaintPathOptions {
|
|
78
|
+
/**
|
|
79
|
+
* The hop list is already a truncated prefix (e.g. the propagation engine's
|
|
80
|
+
* `hopsTruncated` from its hop cap) — emit the marker even when every hop
|
|
81
|
+
* fits.
|
|
82
|
+
*/
|
|
83
|
+
readonly truncated?: boolean;
|
|
84
|
+
/** Byte cap override (tests). Default {@link TAINT_REASON_MAX_BYTES}. */
|
|
85
|
+
readonly maxBytes?: number;
|
|
86
|
+
/**
|
|
87
|
+
* Finding sink category (`SinkKind`) carried in the `;<kind>` header — the
|
|
88
|
+
* only persisted channel for it (see the module doc). A value outside the
|
|
89
|
+
* `[a-z0-9-]` charset is DROPPED (header omitted), never corrupted into the
|
|
90
|
+
* wire string; `SinkKind` is a closed lowercase-hyphen union so this is
|
|
91
|
+
* purely defensive.
|
|
92
|
+
*/
|
|
93
|
+
readonly kind?: string;
|
|
94
|
+
}
|
|
95
|
+
export interface EncodedTaintPath {
|
|
96
|
+
/** The wire string for the TAINTED edge's `reason` column. */
|
|
97
|
+
readonly reason: string;
|
|
98
|
+
/** True when the marker was emitted (caller-flagged, byte cap, or bad hop). */
|
|
99
|
+
readonly truncated: boolean;
|
|
100
|
+
}
|
|
101
|
+
export interface DecodedTaintHop {
|
|
102
|
+
readonly variable: string;
|
|
103
|
+
readonly line: number;
|
|
104
|
+
/** The hop passed through an unmodeled call (flag `c`, KTD5). */
|
|
105
|
+
readonly viaCall: boolean;
|
|
106
|
+
}
|
|
107
|
+
export interface DecodedTaintPath {
|
|
108
|
+
readonly ok: true;
|
|
109
|
+
readonly version: string;
|
|
110
|
+
/** Finding sink category from the `;<kind>` header; absent when not encoded. */
|
|
111
|
+
readonly kind?: string;
|
|
112
|
+
/** Ordered source→sink hops (a PREFIX when `truncated`). */
|
|
113
|
+
readonly hops: readonly DecodedTaintHop[];
|
|
114
|
+
/** Path incomplete (trailing `|~`) — informational, NOT an error. */
|
|
115
|
+
readonly truncated: boolean;
|
|
116
|
+
}
|
|
117
|
+
/** Typed parse failure — the decoder never throws. */
|
|
118
|
+
export interface TaintPathDecodeFailure {
|
|
119
|
+
readonly ok: false;
|
|
120
|
+
readonly error: string;
|
|
121
|
+
}
|
|
122
|
+
export type TaintPathDecodeResult = DecodedTaintPath | TaintPathDecodeFailure;
|
|
123
|
+
/**
|
|
124
|
+
* Encode an ordered hop list into the versioned `reason` wire string.
|
|
125
|
+
* Deterministic; never throws. See the module doc for the format and the
|
|
126
|
+
* three truncation triggers (caller flag, unencodable hop, byte cap).
|
|
127
|
+
*/
|
|
128
|
+
export declare function encodeTaintPath(hops: readonly TaintPathHopInput[], options?: EncodeTaintPathOptions): EncodedTaintPath;
|
|
129
|
+
/**
|
|
130
|
+
* Decode a `reason` wire string. Returns a typed failure for anything that is
|
|
131
|
+
* not a well-formed version-`1` path — never throws. A truncated path decodes
|
|
132
|
+
* `ok: true` with `truncated: true` ("path incomplete", per KTD6).
|
|
133
|
+
*/
|
|
134
|
+
export declare function decodeTaintPath(reason: unknown): TaintPathDecodeResult;
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Taint-path reason codec (#2083 M3 U4/U6, plan KTD6).
|
|
3
|
+
*
|
|
4
|
+
* THE one shared encoder/decoder for the hop-encoded `reason` carried on
|
|
5
|
+
* persisted `TAINTED` edges: the U4 emit path writes it, the U6 MCP `explain`
|
|
6
|
+
* tool reads it. Two hand-rolled copies of a wire format drift — both sides
|
|
7
|
+
* MUST import from here.
|
|
8
|
+
*
|
|
9
|
+
* ## Wire format (version `1`)
|
|
10
|
+
*
|
|
11
|
+
* ```
|
|
12
|
+
* 1[;<kind>]|<name>:<line>[:<flags>]|<name>:<line>[:<flags>]|…[|~]
|
|
13
|
+
* ```
|
|
14
|
+
*
|
|
15
|
+
* - One-character version prefix (`TAINT_PATH_CODEC_VERSION`), then an
|
|
16
|
+
* OPTIONAL `;<kind>` header segment, then ordered source→sink hops, each
|
|
17
|
+
* `variable:line[:flags]`.
|
|
18
|
+
* - `kind` is the finding's sink category (`SinkKind`, e.g.
|
|
19
|
+
* `command-injection`). It rides the reason because it is the ONLY
|
|
20
|
+
* persisted channel: the CodeRelation columns are
|
|
21
|
+
* `type/confidence/reason/step` — `step` is INT32 and the emit-time edge id
|
|
22
|
+
* (which embeds the kind) is not a stored column. The U6 `explain` tool
|
|
23
|
+
* reads it for finding classification. Charset `[a-z0-9-]` (printable
|
|
24
|
+
* ASCII, disjoint from every structural delimiter); `;` itself is printable
|
|
25
|
+
* ASCII and never appears in hop names (identifier charset) or flags.
|
|
26
|
+
* U6 deviation note: this header was added by U6 WITHIN version `1` —
|
|
27
|
+
* U4 and U6 ship in the same release, so no reason string without the
|
|
28
|
+
* header was ever persisted by a released build; the decoder still accepts
|
|
29
|
+
* header-less strings (`kind` simply decodes as `undefined`).
|
|
30
|
+
* - `flags` is a lowercase-letter set; only `c` (= the hop passed through an
|
|
31
|
+
* unmodeled call, KTD5 `viaCall`) is defined today — the rest of the
|
|
32
|
+
* alphabet is RESERVED, and the decoder accepts unknown flag letters so a
|
|
33
|
+
* future writer's output stays decodable.
|
|
34
|
+
* - A trailing `|~` segment is the TRUNCATION MARKER: the encoded path is a
|
|
35
|
+
* source-side PREFIX of the real one (hop cap, byte cap, or an unencodable
|
|
36
|
+
* hop name). Decoders MUST report it as "path incomplete" — never an error.
|
|
37
|
+
*
|
|
38
|
+
* ## Delimiter / round-trip discipline (KTD6)
|
|
39
|
+
*
|
|
40
|
+
* Every structural character (`|`, `:`, `~`, digits, flag letters) is
|
|
41
|
+
* printable ASCII: `sanitizeUTF8` (csv-generator.ts) strips control
|
|
42
|
+
* characters, lone surrogates, and U+FFFE/FFFF — printable ASCII passes
|
|
43
|
+
* through byte-exact, so the encoding survives `escapeCSVField ∘
|
|
44
|
+
* sanitizeUTF8` and the DB load unchanged (pinned by the round-trip test).
|
|
45
|
+
* None of the delimiters can appear in a JS identifier.
|
|
46
|
+
*
|
|
47
|
+
* Hop names are identifier-charset by U1 construction (the harvest records
|
|
48
|
+
* binding names), but the encoder DEFENDS anyway: a hop whose name falls
|
|
49
|
+
* outside the safe charset (or whose line is not a non-negative integer) is
|
|
50
|
+
* never emitted — encoding stops at the offending hop and sets the truncation
|
|
51
|
+
* marker, preserving the prefix-of-the-true-path invariant rather than
|
|
52
|
+
* corrupting the format. (`#` is in the charset: JS private names are
|
|
53
|
+
* `#field`, and the propagation engine's fallback hop names are `#<idx>`.)
|
|
54
|
+
*
|
|
55
|
+
* The byte cap (`TAINT_REASON_MAX_BYTES`, KTD6's "absolute reason-byte cap")
|
|
56
|
+
* bounds the persisted reason column regardless of hop caps: overflow drops
|
|
57
|
+
* TRAILING hops (keeps the source side) and sets the marker. All structural
|
|
58
|
+
* chars and valid names are single-byte ASCII, so `string.length` IS the
|
|
59
|
+
* byte length.
|
|
60
|
+
*/
|
|
61
|
+
/** One-character format version prefix. Bump on any wire-format change. */
|
|
62
|
+
export const TAINT_PATH_CODEC_VERSION = '1';
|
|
63
|
+
/**
|
|
64
|
+
* Absolute cap on the encoded reason's byte length (KTD6). 4096 comfortably
|
|
65
|
+
* holds ~100 hops of realistic identifiers — far beyond the default hop cap
|
|
66
|
+
* (32) — while bounding the persisted column even at `maxHops: 0` (unlimited).
|
|
67
|
+
*/
|
|
68
|
+
export const TAINT_REASON_MAX_BYTES = 4096;
|
|
69
|
+
/** The truncation-marker segment content (rides as a trailing `|~`). */
|
|
70
|
+
export const TAINT_PATH_TRUNCATION_MARKER = '~';
|
|
71
|
+
/**
|
|
72
|
+
* Safe hop-name charset: ASCII identifier characters plus `#` (JS private
|
|
73
|
+
* names / the propagation engine's `#<idx>` fallback). Deliberately ASCII-only
|
|
74
|
+
* — a Unicode identifier is VALID JS but is skipped (truncation marker) rather
|
|
75
|
+
* than risking a `sanitizeUTF8` byte change breaking decode (defensive
|
|
76
|
+
* simplification; documented FN on path completeness, never on the finding).
|
|
77
|
+
*/
|
|
78
|
+
const SAFE_NAME = /^[A-Za-z0-9_$#]+$/;
|
|
79
|
+
/** Decoder-side flags charset — `c` defined, the rest reserved (see module doc). */
|
|
80
|
+
const FLAGS = /^[a-z]*$/;
|
|
81
|
+
/**
|
|
82
|
+
* Kind-header charset: lowercase + digits + hyphen — covers every `SinkKind`
|
|
83
|
+
* label and stays disjoint from the structural delimiters (`;|:~`).
|
|
84
|
+
*/
|
|
85
|
+
const SAFE_KIND = /^[a-z0-9-]+$/;
|
|
86
|
+
/**
|
|
87
|
+
* Encode an ordered hop list into the versioned `reason` wire string.
|
|
88
|
+
* Deterministic; never throws. See the module doc for the format and the
|
|
89
|
+
* three truncation triggers (caller flag, unencodable hop, byte cap).
|
|
90
|
+
*/
|
|
91
|
+
export function encodeTaintPath(hops, options) {
|
|
92
|
+
// Kind header (defensively validated — see EncodeTaintPathOptions.kind).
|
|
93
|
+
const kindHeader = typeof options?.kind === 'string' && SAFE_KIND.test(options.kind) ? `;${options.kind}` : '';
|
|
94
|
+
// Floor: version char + kind header + room for the marker — a smaller cap
|
|
95
|
+
// could not hold even the empty truncated path. The header is identity
|
|
96
|
+
// material (finding classification), so it is never sacrificed to the byte
|
|
97
|
+
// cap; trailing hops are.
|
|
98
|
+
const maxBytes = Math.max(options?.maxBytes ?? TAINT_REASON_MAX_BYTES, TAINT_PATH_CODEC_VERSION.length + kindHeader.length + 2);
|
|
99
|
+
let truncated = options?.truncated === true;
|
|
100
|
+
const segments = [];
|
|
101
|
+
let total = TAINT_PATH_CODEC_VERSION.length + kindHeader.length;
|
|
102
|
+
for (const hop of hops) {
|
|
103
|
+
if (typeof hop.name !== 'string' ||
|
|
104
|
+
!SAFE_NAME.test(hop.name) ||
|
|
105
|
+
!Number.isInteger(hop.line) ||
|
|
106
|
+
hop.line < 0) {
|
|
107
|
+
// Unencodable hop: drop it AND everything after it so the emitted hops
|
|
108
|
+
// stay a faithful source-side prefix (a silent mid-path gap would lie).
|
|
109
|
+
truncated = true;
|
|
110
|
+
break;
|
|
111
|
+
}
|
|
112
|
+
const segment = `|${hop.name}:${hop.line}${hop.viaCall === true ? ':c' : ''}`;
|
|
113
|
+
if (total + segment.length > maxBytes) {
|
|
114
|
+
truncated = true;
|
|
115
|
+
break;
|
|
116
|
+
}
|
|
117
|
+
segments.push(segment);
|
|
118
|
+
total += segment.length;
|
|
119
|
+
}
|
|
120
|
+
if (truncated) {
|
|
121
|
+
// Make room for the trailing `|~` marker (drop trailing hops as needed).
|
|
122
|
+
while (segments.length > 0 && total + 2 > maxBytes) {
|
|
123
|
+
total -= segments.pop().length;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
const reason = TAINT_PATH_CODEC_VERSION +
|
|
127
|
+
kindHeader +
|
|
128
|
+
segments.join('') +
|
|
129
|
+
(truncated ? `|${TAINT_PATH_TRUNCATION_MARKER}` : '');
|
|
130
|
+
return { reason, truncated };
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Decode a `reason` wire string. Returns a typed failure for anything that is
|
|
134
|
+
* not a well-formed version-`1` path — never throws. A truncated path decodes
|
|
135
|
+
* `ok: true` with `truncated: true` ("path incomplete", per KTD6).
|
|
136
|
+
*/
|
|
137
|
+
export function decodeTaintPath(reason) {
|
|
138
|
+
if (typeof reason !== 'string' || reason.length === 0) {
|
|
139
|
+
return { ok: false, error: 'empty or non-string reason' };
|
|
140
|
+
}
|
|
141
|
+
const version = reason[0];
|
|
142
|
+
if (version !== TAINT_PATH_CODEC_VERSION) {
|
|
143
|
+
return { ok: false, error: `unsupported taint-path version '${version}'` };
|
|
144
|
+
}
|
|
145
|
+
let body = reason.slice(1);
|
|
146
|
+
// Optional `;<kind>` header segment (finding sink category — see module doc).
|
|
147
|
+
let kind;
|
|
148
|
+
if (body.startsWith(';')) {
|
|
149
|
+
const headerEnd = body.indexOf('|');
|
|
150
|
+
kind = headerEnd === -1 ? body.slice(1) : body.slice(1, headerEnd);
|
|
151
|
+
if (!SAFE_KIND.test(kind)) {
|
|
152
|
+
return { ok: false, error: `invalid kind header '${kind}'` };
|
|
153
|
+
}
|
|
154
|
+
body = headerEnd === -1 ? '' : body.slice(headerEnd);
|
|
155
|
+
}
|
|
156
|
+
const hops = [];
|
|
157
|
+
if (body.length === 0)
|
|
158
|
+
return { ok: true, version, ...(kind ? { kind } : {}), hops, truncated: false };
|
|
159
|
+
if (!body.startsWith('|')) {
|
|
160
|
+
return { ok: false, error: 'malformed body: expected a hop separator after the version' };
|
|
161
|
+
}
|
|
162
|
+
const parts = body.slice(1).split('|');
|
|
163
|
+
let truncated = false;
|
|
164
|
+
for (let i = 0; i < parts.length; i++) {
|
|
165
|
+
const part = parts[i];
|
|
166
|
+
if (part === TAINT_PATH_TRUNCATION_MARKER) {
|
|
167
|
+
if (i !== parts.length - 1) {
|
|
168
|
+
return { ok: false, error: 'truncation marker not in trailing position' };
|
|
169
|
+
}
|
|
170
|
+
truncated = true;
|
|
171
|
+
break;
|
|
172
|
+
}
|
|
173
|
+
const fields = part.split(':');
|
|
174
|
+
if (fields.length < 2 || fields.length > 3) {
|
|
175
|
+
return { ok: false, error: `malformed hop segment '${part}'` };
|
|
176
|
+
}
|
|
177
|
+
const [name, lineStr, flags = ''] = fields;
|
|
178
|
+
if (!SAFE_NAME.test(name)) {
|
|
179
|
+
return { ok: false, error: `invalid hop variable '${name}'` };
|
|
180
|
+
}
|
|
181
|
+
if (!/^\d+$/.test(lineStr)) {
|
|
182
|
+
return { ok: false, error: `invalid hop line '${lineStr}'` };
|
|
183
|
+
}
|
|
184
|
+
if (!FLAGS.test(flags)) {
|
|
185
|
+
return { ok: false, error: `invalid hop flags '${flags}'` };
|
|
186
|
+
}
|
|
187
|
+
hops.push({ variable: name, line: Number(lineStr), viaCall: flags.includes('c') });
|
|
188
|
+
}
|
|
189
|
+
return { ok: true, version, ...(kind ? { kind } : {}), hops, truncated };
|
|
190
|
+
}
|