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,276 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Secure ingestion of a browser folder upload (multipart/form-data).
|
|
3
|
+
*
|
|
4
|
+
* Replaces the path-injection-prone GET /api/fs/list directory listing. The
|
|
5
|
+
* client streams the selected files plus a JSON `manifest` of their
|
|
6
|
+
* webkitRelativePaths; we write each into an mkdtemp staging dir under
|
|
7
|
+
* UPLOAD_ROOT with PROVABLE containment (resolve-then-contain), hard resource
|
|
8
|
+
* caps, and guaranteed cleanup on every failure/abort path. No client value
|
|
9
|
+
* ever reaches a filesystem READ — the server only writes into a sandbox it
|
|
10
|
+
* created, then hands that sandbox to the analysis pipeline.
|
|
11
|
+
*
|
|
12
|
+
* Security references: CodeQL js/path-injection (resolve + startsWith(root+sep)),
|
|
13
|
+
* OWASP File Upload / Path Traversal.
|
|
14
|
+
*/
|
|
15
|
+
import path from 'path';
|
|
16
|
+
import fs from 'fs';
|
|
17
|
+
import fsp from 'fs/promises';
|
|
18
|
+
import busboy from 'busboy';
|
|
19
|
+
import { UPLOAD_ROOT, STAGING_PREFIX } from './upload-paths.js';
|
|
20
|
+
import { BadRequestError } from './validation.js';
|
|
21
|
+
export const DEFAULT_INGEST_LIMITS = {
|
|
22
|
+
maxTotalBytes: 250 * 1024 * 1024,
|
|
23
|
+
maxFileBytes: 25 * 1024 * 1024,
|
|
24
|
+
maxFiles: 20000,
|
|
25
|
+
maxParts: 20100,
|
|
26
|
+
maxDirs: 50000,
|
|
27
|
+
maxFieldBytes: 2 * 1024 * 1024,
|
|
28
|
+
};
|
|
29
|
+
const MAX_PATH_DEPTH = 64;
|
|
30
|
+
const MAX_PATH_LENGTH = 4096;
|
|
31
|
+
/**
|
|
32
|
+
* Resolve a client-provided relative path to an absolute destination PROVABLY
|
|
33
|
+
* contained within `stageRoot`. Throws BadRequestError on any unsafe input.
|
|
34
|
+
* This is the load-bearing path-traversal-on-write control; keep it pure and
|
|
35
|
+
* unit-tested.
|
|
36
|
+
*/
|
|
37
|
+
export function resolveContainedDest(stageRoot, rel) {
|
|
38
|
+
if (typeof rel !== 'string' || rel.length === 0) {
|
|
39
|
+
throw new BadRequestError('Invalid upload path');
|
|
40
|
+
}
|
|
41
|
+
if (rel.length > MAX_PATH_LENGTH) {
|
|
42
|
+
throw new BadRequestError('Upload path too long');
|
|
43
|
+
}
|
|
44
|
+
// webkitRelativePath is always relative; a leading slash is absolute/hostile.
|
|
45
|
+
if (rel.startsWith('/')) {
|
|
46
|
+
throw new BadRequestError('Invalid upload path');
|
|
47
|
+
}
|
|
48
|
+
// Browsers emit forward slashes only; a NUL byte or backslash is hostile.
|
|
49
|
+
if (rel.includes('\u0000') || rel.includes('\\')) {
|
|
50
|
+
throw new BadRequestError('Invalid upload path');
|
|
51
|
+
}
|
|
52
|
+
const rawSegments = rel.split('/').filter((s) => s.length > 0);
|
|
53
|
+
if (rawSegments.length === 0 || rawSegments.length > MAX_PATH_DEPTH) {
|
|
54
|
+
throw new BadRequestError('Invalid upload path');
|
|
55
|
+
}
|
|
56
|
+
const segments = [];
|
|
57
|
+
for (const seg of rawSegments) {
|
|
58
|
+
// Normalize so NFC/NFD variants don't collide silently on case/unicode
|
|
59
|
+
// -folding filesystems (macOS/Windows).
|
|
60
|
+
const s = seg.normalize('NFC');
|
|
61
|
+
if (s === '.' || s === '..') {
|
|
62
|
+
throw new BadRequestError('Upload path must not contain traversal segments');
|
|
63
|
+
}
|
|
64
|
+
segments.push(s);
|
|
65
|
+
}
|
|
66
|
+
const dest = path.resolve(stageRoot, segments.join(path.sep));
|
|
67
|
+
// Suffix path.sep so a sibling prefix (/sandbox-evil vs /sandbox) can't pass.
|
|
68
|
+
if (dest !== stageRoot && !dest.startsWith(stageRoot + path.sep)) {
|
|
69
|
+
throw new BadRequestError('Upload path escapes the sandbox');
|
|
70
|
+
}
|
|
71
|
+
return dest;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Create the parent directories of `destFile` one segment at a time, asserting
|
|
75
|
+
* after each `mkdir` that the segment is a real directory (not a symlink
|
|
76
|
+
* swapped in mid-stream) still inside `stageRoot`. Counts created dirs against
|
|
77
|
+
* the inode-exhaustion cap.
|
|
78
|
+
*/
|
|
79
|
+
function mkdirContained(stageRoot, destFile, state) {
|
|
80
|
+
const parent = path.dirname(destFile);
|
|
81
|
+
const relParent = path.relative(stageRoot, parent);
|
|
82
|
+
if (relParent === '' || relParent === '.')
|
|
83
|
+
return;
|
|
84
|
+
const segs = relParent.split(path.sep).filter(Boolean);
|
|
85
|
+
let cur = stageRoot;
|
|
86
|
+
for (const seg of segs) {
|
|
87
|
+
cur = path.join(cur, seg);
|
|
88
|
+
let made = false;
|
|
89
|
+
try {
|
|
90
|
+
fs.mkdirSync(cur);
|
|
91
|
+
made = true;
|
|
92
|
+
}
|
|
93
|
+
catch (err) {
|
|
94
|
+
if (err.code !== 'EEXIST')
|
|
95
|
+
throw err;
|
|
96
|
+
}
|
|
97
|
+
const st = fs.lstatSync(cur);
|
|
98
|
+
if (st.isSymbolicLink() || !st.isDirectory()) {
|
|
99
|
+
throw new BadRequestError('Upload path escapes the sandbox');
|
|
100
|
+
}
|
|
101
|
+
if (made) {
|
|
102
|
+
state.dirCount++;
|
|
103
|
+
if (state.dirCount > state.limits.maxDirs) {
|
|
104
|
+
throw new BadRequestError('Too many directories in upload', 413);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Parse and securely write a multipart folder upload into a fresh staging
|
|
111
|
+
* directory under UPLOAD_ROOT. Resolves with the populated staging dir, or
|
|
112
|
+
* rejects with a BadRequestError (status 400/413) after removing the staging
|
|
113
|
+
* dir. The caller owns promotion + cleanup of the returned `stageRoot`.
|
|
114
|
+
*/
|
|
115
|
+
export async function ingestUpload(req, limitsOverride, opts = {}) {
|
|
116
|
+
const limits = { ...DEFAULT_INGEST_LIMITS, ...limitsOverride };
|
|
117
|
+
const uploadRoot = opts.root ?? UPLOAD_ROOT;
|
|
118
|
+
await fsp.mkdir(uploadRoot, { recursive: true });
|
|
119
|
+
// mkdtemp creates the dir mode 0o700 (owner-only); realpath canonicalizes
|
|
120
|
+
// the root so the containment prefix check is exact.
|
|
121
|
+
const stageRoot = await fsp.realpath(await fsp.mkdtemp(path.join(uploadRoot, STAGING_PREFIX)));
|
|
122
|
+
let cleaned = false;
|
|
123
|
+
const cleanup = async () => {
|
|
124
|
+
if (cleaned)
|
|
125
|
+
return;
|
|
126
|
+
cleaned = true;
|
|
127
|
+
await fsp.rm(stageRoot, { recursive: true, force: true }).catch(() => { });
|
|
128
|
+
};
|
|
129
|
+
return new Promise((resolve, reject) => {
|
|
130
|
+
let settled = false;
|
|
131
|
+
let manifest = null;
|
|
132
|
+
let fileIndex = 0;
|
|
133
|
+
let fileCount = 0;
|
|
134
|
+
let totalBytes = 0;
|
|
135
|
+
let topLevelName = '';
|
|
136
|
+
const dirState = { dirCount: 0, limits };
|
|
137
|
+
const writePromises = [];
|
|
138
|
+
const bb = busboy({
|
|
139
|
+
headers: req.headers,
|
|
140
|
+
limits: {
|
|
141
|
+
fileSize: limits.maxFileBytes,
|
|
142
|
+
files: limits.maxFiles,
|
|
143
|
+
parts: limits.maxParts,
|
|
144
|
+
fields: 10,
|
|
145
|
+
fieldNameSize: 200,
|
|
146
|
+
fieldSize: limits.maxFieldBytes,
|
|
147
|
+
headerPairs: 2000,
|
|
148
|
+
},
|
|
149
|
+
});
|
|
150
|
+
const fail = (err) => {
|
|
151
|
+
if (settled)
|
|
152
|
+
return;
|
|
153
|
+
settled = true;
|
|
154
|
+
try {
|
|
155
|
+
req.unpipe(bb);
|
|
156
|
+
}
|
|
157
|
+
catch {
|
|
158
|
+
/* ignore */
|
|
159
|
+
}
|
|
160
|
+
try {
|
|
161
|
+
req.resume(); // drain remaining body so the socket isn't left hanging
|
|
162
|
+
}
|
|
163
|
+
catch {
|
|
164
|
+
/* ignore */
|
|
165
|
+
}
|
|
166
|
+
void cleanup().finally(() => reject(err));
|
|
167
|
+
};
|
|
168
|
+
bb.on('field', (name, val) => {
|
|
169
|
+
if (name !== 'manifest')
|
|
170
|
+
return;
|
|
171
|
+
try {
|
|
172
|
+
const parsed = JSON.parse(val);
|
|
173
|
+
if (!Array.isArray(parsed) || !parsed.every((p) => typeof p === 'string')) {
|
|
174
|
+
return fail(new BadRequestError('Invalid manifest'));
|
|
175
|
+
}
|
|
176
|
+
manifest = parsed;
|
|
177
|
+
}
|
|
178
|
+
catch {
|
|
179
|
+
fail(new BadRequestError('Invalid manifest'));
|
|
180
|
+
}
|
|
181
|
+
});
|
|
182
|
+
bb.on('file', (_name, stream, _info) => {
|
|
183
|
+
if (settled) {
|
|
184
|
+
stream.resume();
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
if (manifest === null) {
|
|
188
|
+
// The manifest field MUST arrive before any file part.
|
|
189
|
+
stream.resume();
|
|
190
|
+
return fail(new BadRequestError('Manifest must precede file parts'));
|
|
191
|
+
}
|
|
192
|
+
const idx = fileIndex++;
|
|
193
|
+
const rel = manifest[idx];
|
|
194
|
+
let dest;
|
|
195
|
+
try {
|
|
196
|
+
dest = resolveContainedDest(stageRoot, rel);
|
|
197
|
+
// A folder upload is exactly one top-level directory: every entry must
|
|
198
|
+
// have ≥2 segments and share the same first segment. This rejects a
|
|
199
|
+
// bare file at the root (which would make the promote target a file)
|
|
200
|
+
// and a multi-top manifest (which would silently drop all but the
|
|
201
|
+
// first folder). Validated here, before any job is created.
|
|
202
|
+
const segs = String(rel)
|
|
203
|
+
.split('/')
|
|
204
|
+
.filter((s) => s.length > 0);
|
|
205
|
+
const firstSeg = (segs[0] ?? '').normalize('NFC');
|
|
206
|
+
if (!topLevelName) {
|
|
207
|
+
topLevelName = firstSeg;
|
|
208
|
+
}
|
|
209
|
+
if (segs.length < 2 || firstSeg !== topLevelName) {
|
|
210
|
+
throw new BadRequestError('Upload must be a single folder of files');
|
|
211
|
+
}
|
|
212
|
+
mkdirContained(stageRoot, dest, dirState);
|
|
213
|
+
}
|
|
214
|
+
catch (err) {
|
|
215
|
+
stream.resume();
|
|
216
|
+
return fail(err);
|
|
217
|
+
}
|
|
218
|
+
fileCount++;
|
|
219
|
+
const ws = fs.createWriteStream(dest, { flags: 'wx' });
|
|
220
|
+
const p = new Promise((res, rej) => {
|
|
221
|
+
stream.on('data', (chunk) => {
|
|
222
|
+
totalBytes += chunk.length;
|
|
223
|
+
if (totalBytes > limits.maxTotalBytes) {
|
|
224
|
+
stream.unpipe(ws);
|
|
225
|
+
ws.destroy();
|
|
226
|
+
rej(new BadRequestError('Upload exceeds total size limit', 413));
|
|
227
|
+
}
|
|
228
|
+
});
|
|
229
|
+
stream.on('limit', () => {
|
|
230
|
+
ws.destroy();
|
|
231
|
+
rej(new BadRequestError('File exceeds size limit', 413));
|
|
232
|
+
});
|
|
233
|
+
stream.on('error', rej);
|
|
234
|
+
ws.on('error', rej);
|
|
235
|
+
ws.on('finish', () => res());
|
|
236
|
+
stream.pipe(ws);
|
|
237
|
+
});
|
|
238
|
+
writePromises.push(p);
|
|
239
|
+
p.catch(fail);
|
|
240
|
+
});
|
|
241
|
+
bb.on('filesLimit', () => fail(new BadRequestError('Too many files in upload', 413)));
|
|
242
|
+
bb.on('partsLimit', () => fail(new BadRequestError('Too many parts in upload', 413)));
|
|
243
|
+
bb.on('fieldsLimit', () => fail(new BadRequestError('Too many fields in upload')));
|
|
244
|
+
bb.on('error', (err) => fail(err instanceof Error ? err : new BadRequestError('Upload parse error')));
|
|
245
|
+
bb.on('close', () => {
|
|
246
|
+
if (settled)
|
|
247
|
+
return;
|
|
248
|
+
Promise.all(writePromises)
|
|
249
|
+
.then(() => {
|
|
250
|
+
if (settled)
|
|
251
|
+
return;
|
|
252
|
+
if (manifest === null)
|
|
253
|
+
return fail(new BadRequestError('Missing manifest'));
|
|
254
|
+
if (fileCount === 0)
|
|
255
|
+
return fail(new BadRequestError('Empty upload'));
|
|
256
|
+
if (fileCount !== manifest.length) {
|
|
257
|
+
return fail(new BadRequestError('Manifest/file count mismatch'));
|
|
258
|
+
}
|
|
259
|
+
if (!topLevelName) {
|
|
260
|
+
return fail(new BadRequestError('Could not determine upload folder name'));
|
|
261
|
+
}
|
|
262
|
+
settled = true;
|
|
263
|
+
resolve({ stageRoot, fileCount, totalBytes, topLevelName });
|
|
264
|
+
})
|
|
265
|
+
.catch(() => {
|
|
266
|
+
/* a write rejected → fail() already invoked via p.catch */
|
|
267
|
+
});
|
|
268
|
+
});
|
|
269
|
+
req.on('aborted', () => {
|
|
270
|
+
if (!settled)
|
|
271
|
+
fail(new BadRequestError('Upload aborted'));
|
|
272
|
+
});
|
|
273
|
+
req.on('error', (err) => fail(err instanceof Error ? err : new BadRequestError('Request error')));
|
|
274
|
+
req.pipe(bb);
|
|
275
|
+
});
|
|
276
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Upload working-directory paths.
|
|
3
|
+
*
|
|
4
|
+
* Browser folder uploads are written into getGlobalDir()/uploads/{name}/ — a
|
|
5
|
+
* sibling of the clone root (git-clone.ts CLONE_ROOT) — so an uploaded repo
|
|
6
|
+
* persists and behaves like a cloned one (the graph UI's /api/file reads its
|
|
7
|
+
* files after analysis, and DELETE /api/repo removes it). Staging happens in
|
|
8
|
+
* an mkdtemp dir *under* UPLOAD_ROOT so the promote rename stays on one
|
|
9
|
+
* filesystem and remains atomic (a rename from os.tmpdir() could trip EXDEV —
|
|
10
|
+
* the exact Docker case this feature targets; see bridge-db.ts for the same
|
|
11
|
+
* anchored-staging pattern).
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* Root directory for all uploaded repositories. Targets must resolve inside this.
|
|
15
|
+
*
|
|
16
|
+
* Sourced from getGlobalDir() so it honors GITNEXUS_HOME and stays a sibling of
|
|
17
|
+
* the clone root on the same (in Docker, persistent) volume. Falls back to
|
|
18
|
+
* ~/.gitnexus when the env var is unset.
|
|
19
|
+
*/
|
|
20
|
+
export declare const UPLOAD_ROOT: string;
|
|
21
|
+
/** Prefix for per-upload staging directories created under UPLOAD_ROOT. */
|
|
22
|
+
export declare const STAGING_PREFIX = ".staging-";
|
|
23
|
+
/**
|
|
24
|
+
* Get the upload target directory for a repo name.
|
|
25
|
+
*
|
|
26
|
+
* Re-validates at the boundary (callers may derive the name from an untrusted
|
|
27
|
+
* manifest). Rejects `.`, `..`, the `'unknown'` sentinel that sanitizeRepoName
|
|
28
|
+
* emits for un-nameable inputs, names beginning with `.` (which would collide
|
|
29
|
+
* with the `.staging-` prefix), and anything outside the safe charset.
|
|
30
|
+
*/
|
|
31
|
+
export declare function getUploadDir(repoName: string): string;
|
|
32
|
+
/**
|
|
33
|
+
* Derive a filesystem-safe upload directory name from the manifest's
|
|
34
|
+
* top-level folder. Returns null when the name is un-nameable (so the caller
|
|
35
|
+
* rejects with 400 rather than colliding everyone on `UPLOAD_ROOT/unknown`).
|
|
36
|
+
*/
|
|
37
|
+
export declare function deriveUploadName(topLevelName: string): string | null;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Upload working-directory paths.
|
|
3
|
+
*
|
|
4
|
+
* Browser folder uploads are written into getGlobalDir()/uploads/{name}/ — a
|
|
5
|
+
* sibling of the clone root (git-clone.ts CLONE_ROOT) — so an uploaded repo
|
|
6
|
+
* persists and behaves like a cloned one (the graph UI's /api/file reads its
|
|
7
|
+
* files after analysis, and DELETE /api/repo removes it). Staging happens in
|
|
8
|
+
* an mkdtemp dir *under* UPLOAD_ROOT so the promote rename stays on one
|
|
9
|
+
* filesystem and remains atomic (a rename from os.tmpdir() could trip EXDEV —
|
|
10
|
+
* the exact Docker case this feature targets; see bridge-db.ts for the same
|
|
11
|
+
* anchored-staging pattern).
|
|
12
|
+
*/
|
|
13
|
+
import path from 'path';
|
|
14
|
+
import { sanitizeRepoName } from '../storage/git.js';
|
|
15
|
+
import { REPO_NAME_PATTERN } from './git-clone.js';
|
|
16
|
+
import { getGlobalDir } from '../storage/repo-manager.js';
|
|
17
|
+
/**
|
|
18
|
+
* Root directory for all uploaded repositories. Targets must resolve inside this.
|
|
19
|
+
*
|
|
20
|
+
* Sourced from getGlobalDir() so it honors GITNEXUS_HOME and stays a sibling of
|
|
21
|
+
* the clone root on the same (in Docker, persistent) volume. Falls back to
|
|
22
|
+
* ~/.gitnexus when the env var is unset.
|
|
23
|
+
*/
|
|
24
|
+
export const UPLOAD_ROOT = path.resolve(path.join(getGlobalDir(), 'uploads'));
|
|
25
|
+
/** Prefix for per-upload staging directories created under UPLOAD_ROOT. */
|
|
26
|
+
export const STAGING_PREFIX = '.staging-';
|
|
27
|
+
/**
|
|
28
|
+
* Get the upload target directory for a repo name.
|
|
29
|
+
*
|
|
30
|
+
* Re-validates at the boundary (callers may derive the name from an untrusted
|
|
31
|
+
* manifest). Rejects `.`, `..`, the `'unknown'` sentinel that sanitizeRepoName
|
|
32
|
+
* emits for un-nameable inputs, names beginning with `.` (which would collide
|
|
33
|
+
* with the `.staging-` prefix), and anything outside the safe charset.
|
|
34
|
+
*/
|
|
35
|
+
export function getUploadDir(repoName) {
|
|
36
|
+
if (!repoName ||
|
|
37
|
+
repoName === '.' ||
|
|
38
|
+
repoName === '..' ||
|
|
39
|
+
repoName === 'unknown' ||
|
|
40
|
+
repoName.startsWith('.') ||
|
|
41
|
+
!REPO_NAME_PATTERN.test(repoName)) {
|
|
42
|
+
throw new Error('Invalid repository name');
|
|
43
|
+
}
|
|
44
|
+
return path.join(UPLOAD_ROOT, repoName);
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Derive a filesystem-safe upload directory name from the manifest's
|
|
48
|
+
* top-level folder. Returns null when the name is un-nameable (so the caller
|
|
49
|
+
* rejects with 400 rather than colliding everyone on `UPLOAD_ROOT/unknown`).
|
|
50
|
+
*/
|
|
51
|
+
export function deriveUploadName(topLevelName) {
|
|
52
|
+
const safe = sanitizeRepoName(topLevelName);
|
|
53
|
+
if (safe === 'unknown' || safe === '.' || safe === '..' || safe.startsWith('.')) {
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
return safe;
|
|
57
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Backstop cleanup for abandoned upload staging directories.
|
|
3
|
+
*
|
|
4
|
+
* A crashed/killed process can leave a `.staging-*` directory under
|
|
5
|
+
* UPLOAD_ROOT (the normal path removes it on success/failure/abort). This
|
|
6
|
+
* sweep, run once at server startup, removes staging dirs older than a
|
|
7
|
+
* threshold. Promoted upload dirs are persistent registered repos (like
|
|
8
|
+
* clones) and are NOT touched here — they are removed via DELETE /api/repo.
|
|
9
|
+
*/
|
|
10
|
+
export interface SweepOptions {
|
|
11
|
+
/** Remove staging dirs older than this (default 6h). */
|
|
12
|
+
maxAgeMs?: number;
|
|
13
|
+
/** Override the root to sweep (defaults to UPLOAD_ROOT; for tests). */
|
|
14
|
+
root?: string;
|
|
15
|
+
/** Clock injection for tests. */
|
|
16
|
+
now?: number;
|
|
17
|
+
}
|
|
18
|
+
export declare function sweepStaleUploads(opts?: SweepOptions): Promise<{
|
|
19
|
+
removed: string[];
|
|
20
|
+
}>;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Backstop cleanup for abandoned upload staging directories.
|
|
3
|
+
*
|
|
4
|
+
* A crashed/killed process can leave a `.staging-*` directory under
|
|
5
|
+
* UPLOAD_ROOT (the normal path removes it on success/failure/abort). This
|
|
6
|
+
* sweep, run once at server startup, removes staging dirs older than a
|
|
7
|
+
* threshold. Promoted upload dirs are persistent registered repos (like
|
|
8
|
+
* clones) and are NOT touched here — they are removed via DELETE /api/repo.
|
|
9
|
+
*/
|
|
10
|
+
import path from 'path';
|
|
11
|
+
import fsp from 'fs/promises';
|
|
12
|
+
import { UPLOAD_ROOT, STAGING_PREFIX } from './upload-paths.js';
|
|
13
|
+
export async function sweepStaleUploads(opts = {}) {
|
|
14
|
+
const maxAgeMs = opts.maxAgeMs ?? 6 * 60 * 60 * 1000;
|
|
15
|
+
const root = opts.root ?? UPLOAD_ROOT;
|
|
16
|
+
const now = opts.now ?? Date.now();
|
|
17
|
+
const removed = [];
|
|
18
|
+
let entries;
|
|
19
|
+
try {
|
|
20
|
+
entries = await fsp.readdir(root, { withFileTypes: true });
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
return { removed }; // root does not exist yet — nothing to sweep
|
|
24
|
+
}
|
|
25
|
+
for (const entry of entries) {
|
|
26
|
+
if (!entry.isDirectory())
|
|
27
|
+
continue;
|
|
28
|
+
const full = path.join(root, entry.name);
|
|
29
|
+
try {
|
|
30
|
+
const st = await fsp.stat(full);
|
|
31
|
+
if (now - st.mtimeMs <= maxAgeMs)
|
|
32
|
+
continue; // recent — keep
|
|
33
|
+
if (entry.name.startsWith(STAGING_PREFIX)) {
|
|
34
|
+
// Transient staging dir orphaned by a crash — always removable.
|
|
35
|
+
await fsp.rm(full, { recursive: true, force: true }).catch(() => { });
|
|
36
|
+
removed.push(full);
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
// Promoted upload dir. A successfully-analyzed (registered) repo always
|
|
40
|
+
// has a `.gitnexus` index inside it; a stale promoted dir WITHOUT one is
|
|
41
|
+
// an orphan from an analysis that failed before registering — remove it.
|
|
42
|
+
const hasIndex = await fsp
|
|
43
|
+
.access(path.join(full, '.gitnexus'))
|
|
44
|
+
.then(() => true)
|
|
45
|
+
.catch(() => false);
|
|
46
|
+
if (!hasIndex) {
|
|
47
|
+
await fsp.rm(full, { recursive: true, force: true }).catch(() => { });
|
|
48
|
+
removed.push(full);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
/* stat race — skip */
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return { removed };
|
|
57
|
+
}
|
package/dist/storage/git.js
CHANGED
|
@@ -2,6 +2,7 @@ import { execSync } from 'child_process';
|
|
|
2
2
|
import { statSync } from 'fs';
|
|
3
3
|
import path from 'path';
|
|
4
4
|
// Git utilities for repository detection, commit tracking, and diff analysis
|
|
5
|
+
const chompGitOutput = (value) => value.toString().replace(/\r?\n$/, '');
|
|
5
6
|
export const isGitRepo = (repoPath) => {
|
|
6
7
|
try {
|
|
7
8
|
execSync('git rev-parse --is-inside-work-tree', {
|
|
@@ -99,14 +100,12 @@ export const getRemoteUrl = (repoPath) => {
|
|
|
99
100
|
*/
|
|
100
101
|
export const getGitRoot = (fromPath) => {
|
|
101
102
|
try {
|
|
102
|
-
const raw = execSync('git rev-parse --show-toplevel', {
|
|
103
|
+
const raw = chompGitOutput(execSync('git rev-parse --show-toplevel', {
|
|
103
104
|
cwd: fromPath,
|
|
104
105
|
// Suppress stderr -- see getCurrentCommit comment and #1172.
|
|
105
106
|
stdio: ['ignore', 'pipe', 'ignore'],
|
|
106
107
|
windowsHide: true,
|
|
107
|
-
})
|
|
108
|
-
.toString()
|
|
109
|
-
.trim();
|
|
108
|
+
}));
|
|
110
109
|
// On Windows, git returns /d/Projects/Foo — path.resolve normalizes to D:\Projects\Foo
|
|
111
110
|
return path.resolve(raw);
|
|
112
111
|
}
|
|
@@ -143,13 +142,11 @@ export const getGitRoot = (fromPath) => {
|
|
|
143
142
|
*/
|
|
144
143
|
export const getCanonicalRepoRoot = (fromPath) => {
|
|
145
144
|
try {
|
|
146
|
-
const commonDir = execSync('git rev-parse --path-format=absolute --git-common-dir', {
|
|
145
|
+
const commonDir = chompGitOutput(execSync('git rev-parse --path-format=absolute --git-common-dir', {
|
|
147
146
|
cwd: fromPath,
|
|
148
147
|
stdio: ['ignore', 'pipe', 'ignore'],
|
|
149
148
|
windowsHide: true,
|
|
150
|
-
})
|
|
151
|
-
.toString()
|
|
152
|
-
.trim();
|
|
149
|
+
}));
|
|
153
150
|
if (!commonDir)
|
|
154
151
|
return null;
|
|
155
152
|
// Common dir is `<repo>/.git` for both the main checkout and all
|
|
@@ -53,10 +53,31 @@ export declare const fileContentHash: (content: Buffer | string) => string;
|
|
|
53
53
|
* in the chunk. We sort by filePath before hashing so chunks composed of
|
|
54
54
|
* the same files in different order produce the same key.
|
|
55
55
|
*/
|
|
56
|
+
/** PDG/CFG cache namespace (#2081 M1) — every input that changes the
|
|
57
|
+
* WORKER-EMITTED `cfgSideChannel` must be folded into the chunk key, and
|
|
58
|
+
* ONLY those. The classification test for a future option: does the worker
|
|
59
|
+
* see it (workerData) and does it change the bytes the worker writes to the
|
|
60
|
+
* shard? `pdgMaxEdgesPerFunction` famously fails that test — it is applied
|
|
61
|
+
* at EMIT time on the main thread (scope-resolution run.ts), the worker
|
|
62
|
+
* never receives it, and the cached output is byte-identical across cap
|
|
63
|
+
* values; folding it in (as a prior review round did) only forced a
|
|
64
|
+
* spurious full re-parse on every cap change (#2099 F3). Options that
|
|
65
|
+
* change the PERSISTED GRAPH but not the shard belong in the RepoMeta pdg
|
|
66
|
+
* stamp (incremental-eligibility), not here. */
|
|
67
|
+
export interface PdgCacheKey {
|
|
68
|
+
readonly pdg?: boolean;
|
|
69
|
+
/** Per-function source-line cap (changes WHICH functions get a CFG —
|
|
70
|
+
* applied in the worker, so it shapes the cached shard). Callers must
|
|
71
|
+
* pass the RESOLVED value (the production call site in parse-impl.ts
|
|
72
|
+
* applies the worker's default before folding) so an explicit-default
|
|
73
|
+
* run shares the default run's keys — this function folds whatever it
|
|
74
|
+
* is given verbatim. */
|
|
75
|
+
readonly maxFunctionLines?: number;
|
|
76
|
+
}
|
|
56
77
|
export declare const computeChunkHash: (entries: Array<{
|
|
57
78
|
filePath: string;
|
|
58
79
|
contentHash: string;
|
|
59
|
-
}
|
|
80
|
+
}>, pdg?: boolean | PdgCacheKey) => string;
|
|
60
81
|
export declare const mapReplacer: (_key: string, value: unknown) => unknown;
|
|
61
82
|
export declare const mapReviver: (_key: string, value: unknown) => unknown;
|
|
62
83
|
/**
|
|
@@ -52,7 +52,7 @@ import { fileURLToPath } from 'url';
|
|
|
52
52
|
// the main thread (the #1983 OOM). Because the two stores share this version,
|
|
53
53
|
// any future change to the `ParsedFile` serialization shape MUST bump
|
|
54
54
|
// SCHEMA_BUMP so both invalidate in lockstep.
|
|
55
|
-
const SCHEMA_BUMP =
|
|
55
|
+
const SCHEMA_BUMP = 6; // #2082 M2: cfgSideChannel gained bindings + per-block statement facts
|
|
56
56
|
const GITNEXUS_PKG_VERSION = (() => {
|
|
57
57
|
try {
|
|
58
58
|
// package.json sits at gitnexus/package.json — two levels up from
|
|
@@ -92,17 +92,48 @@ const sha256Hex = (input) => createHash('sha256')
|
|
|
92
92
|
.digest('hex');
|
|
93
93
|
/** Stable hash of a single file's contents — used by callers to compose a chunk hash. */
|
|
94
94
|
export const fileContentHash = (content) => sha256Hex(content);
|
|
95
|
-
|
|
96
|
-
* Compute the canonical cache key for a chunk's contents.
|
|
97
|
-
*
|
|
98
|
-
* `entries` is the list of (filePath, file content hash) for every file
|
|
99
|
-
* in the chunk. We sort by filePath before hashing so chunks composed of
|
|
100
|
-
* the same files in different order produce the same key.
|
|
101
|
-
*/
|
|
102
|
-
export const computeChunkHash = (entries) => {
|
|
95
|
+
export const computeChunkHash = (entries, pdg = false) => {
|
|
103
96
|
const sorted = [...entries].sort((a, b) => (a.filePath < b.filePath ? -1 : 1));
|
|
104
97
|
const joined = sorted.map((e) => `${e.filePath}:${e.contentHash}`).join('\n');
|
|
105
|
-
|
|
98
|
+
const opts = typeof pdg === 'boolean' ? { pdg } : pdg;
|
|
99
|
+
// pdg-off path keeps its pre-#2081 chunk-KEY format verbatim. Note this does
|
|
100
|
+
// NOT mean caches survive the M1 upgrade: SCHEMA_BUMP 4→5 changed
|
|
101
|
+
// PARSE_CACHE_VERSION, and both loadParseCache (below) and the durable
|
|
102
|
+
// parsedfile-store index hard-invalidate on it — every user pays one full
|
|
103
|
+
// cold re-parse on upgrade regardless of --pdg. Keeping the key format
|
|
104
|
+
// stable only means no SECOND invalidation class is introduced here.
|
|
105
|
+
if (!opts.pdg)
|
|
106
|
+
return sha256Hex(joined);
|
|
107
|
+
// Fold the worker-visible --pdg configuration into the key: the boolean
|
|
108
|
+
// plus `maxFunctionLines` (decides which functions get a CFG at all, in the
|
|
109
|
+
// worker). Without it a warm chunk built under one cap is served to a run
|
|
110
|
+
// with a different cap → a stale/under-built CFG: the #2038-class
|
|
111
|
+
// option-blind-key trap. `def` marks an unset (default) value so two
|
|
112
|
+
// default-cap runs share a key. The emit-time edge cap is deliberately
|
|
113
|
+
// absent — see the PdgCacheKey doc comment.
|
|
114
|
+
//
|
|
115
|
+
// NAMESPACE VERSION (`pdg:5`): bumped when the worker-emitted
|
|
116
|
+
// `cfgSideChannel` SHAPE changes for pdg-mode runs only — pdg:1→2 in #2083
|
|
117
|
+
// M3 U1 (TsHarvester emits taint `sites` on StatementFacts); pdg:2→3 in the
|
|
118
|
+
// #2227 follow-up U1 (every C-family / TS harvester now stamps the call-site
|
|
119
|
+
// anchor `SiteRecord.at`, which the resolved-callee-id join reads); pdg:3→4 in
|
|
120
|
+
// the #2227 tri-review-2 U4 (the Rust harvester now emits a `kind:'new'` site
|
|
121
|
+
// for `struct_expression`, a new worker-output site the join consumes); pdg:4→5
|
|
122
|
+
// in the FU-C call-summary soundness fix (the TS harvester now stamps
|
|
123
|
+
// `BindingEntry.formalIndex` on param bindings so the PDG call-summary keys
|
|
124
|
+
// return-flow on the enclosing FORMAL position, not the flattened binding
|
|
125
|
+
// ordinal — a warm chunk lacking it would route the harvest to its conservative
|
|
126
|
+
// empty-summary fallback). A warm chunk built by a worker predating the relevant
|
|
127
|
+
// change carries a stale site shape, so the join skips it and
|
|
128
|
+
// `BasicBlock.calleeIds` is silently empty (or missing the struct constructor)
|
|
129
|
+
// even though `callees` is populated — exactly the #2225-class shape skew this
|
|
130
|
+
// version token exists to prevent. Invalidates pdg-mode chunks and their durable
|
|
131
|
+
// parsedfile-cache entries; flag-off chunk keys never reach this line and stay
|
|
132
|
+
// byte-identical, so non-pdg users pay nothing. Deliberately NOT a SCHEMA_BUMP —
|
|
133
|
+
// that gates the whole cache version and would force a full cold re-parse on
|
|
134
|
+
// EVERY user (the M1 bump comment above records that cost).
|
|
135
|
+
const ns = `pdg:5;maxFn=${opts.maxFunctionLines ?? 'def'}`;
|
|
136
|
+
return sha256Hex(`${ns}\n${joined}`);
|
|
106
137
|
};
|
|
107
138
|
/**
|
|
108
139
|
* JSON replacer that round-trips Map/Set instances through plain JSON.
|