gitnexus 1.6.11-rc.8 → 1.6.11
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 +95 -26
- package/dist/_shared/graph/types.d.ts +81 -2
- package/dist/_shared/graph/types.d.ts.map +1 -1
- package/dist/_shared/integrations/circuit-breaker.d.ts.map +1 -1
- package/dist/_shared/integrations/circuit-breaker.js.map +1 -1
- package/dist/_shared/integrations/resilient-fetch.d.ts.map +1 -1
- package/dist/_shared/integrations/resilient-fetch.js.map +1 -1
- package/dist/_shared/language-detection.d.ts.map +1 -1
- package/dist/_shared/language-detection.js +2 -0
- package/dist/_shared/language-detection.js.map +1 -1
- package/dist/_shared/languages.d.ts +2 -1
- package/dist/_shared/languages.d.ts.map +1 -1
- package/dist/_shared/languages.js +1 -0
- package/dist/_shared/languages.js.map +1 -1
- package/dist/_shared/lbug/schema-constants.d.ts +2 -2
- package/dist/_shared/lbug/schema-constants.d.ts.map +1 -1
- package/dist/_shared/lbug/schema-constants.js +4 -0
- package/dist/_shared/lbug/schema-constants.js.map +1 -1
- package/dist/_shared/scope-resolution/evidence-weights.d.ts.map +1 -1
- package/dist/_shared/scope-resolution/finalize-algorithm.js +1 -0
- package/dist/_shared/scope-resolution/finalize-algorithm.js.map +1 -1
- package/dist/_shared/scope-resolution/language-classification.d.ts +3 -0
- package/dist/_shared/scope-resolution/language-classification.d.ts.map +1 -1
- package/dist/_shared/scope-resolution/language-classification.js +4 -0
- package/dist/_shared/scope-resolution/language-classification.js.map +1 -1
- package/dist/_shared/scope-resolution/reference-site.d.ts +10 -0
- package/dist/_shared/scope-resolution/reference-site.d.ts.map +1 -1
- package/dist/_shared/scope-resolution/registries/field-registry.js.map +1 -1
- package/dist/_shared/scope-resolution/registries/method-registry.js.map +1 -1
- package/dist/_shared/scope-resolution/scope-tree.d.ts.map +1 -1
- package/dist/_shared/scope-resolution/scope-tree.js.map +1 -1
- package/dist/_shared/scope-resolution/types.d.ts +15 -0
- package/dist/_shared/scope-resolution/types.d.ts.map +1 -1
- package/dist/cli/ai-context.d.ts +4 -0
- package/dist/cli/ai-context.js +90 -14
- package/dist/cli/analyze-config.js +14 -0
- package/dist/cli/analyze-options.d.ts +14 -0
- package/dist/cli/analyze-watch.d.ts +20 -0
- package/dist/cli/analyze-watch.js +434 -0
- package/dist/cli/analyze.js +27 -9
- package/dist/cli/auto-sync.d.ts +1 -0
- package/dist/cli/auto-sync.js +107 -0
- package/dist/cli/command-banner.d.ts +15 -0
- package/dist/cli/command-banner.js +42 -0
- package/dist/cli/detect-changes-format.js +21 -0
- package/dist/cli/doctor.d.ts +7 -0
- package/dist/cli/doctor.js +14 -0
- package/dist/cli/group.js +7 -1
- package/dist/cli/help-i18n.js +4 -0
- package/dist/cli/i18n/en.d.ts +18 -3
- package/dist/cli/i18n/en.js +18 -3
- package/dist/cli/i18n/resources.d.ts +33 -3
- package/dist/cli/i18n/zh-CN.d.ts +15 -0
- package/dist/cli/i18n/zh-CN.js +18 -3
- package/dist/cli/index.js +34 -7
- package/dist/cli/mcp.d.ts +15 -0
- package/dist/cli/mcp.js +44 -0
- package/dist/cli/optional-grammars.d.ts +3 -2
- package/dist/cli/optional-grammars.js +19 -3
- package/dist/cli/setup.js +41 -15
- package/dist/cli/update-notice.d.ts +34 -0
- package/dist/cli/update-notice.js +91 -0
- package/dist/cli/update.d.ts +21 -0
- package/dist/cli/update.js +93 -0
- package/dist/cli/watch.d.ts +1 -20
- package/dist/cli/watch.js +5 -371
- package/dist/core/analysis-feature-registry.d.ts +2 -0
- package/dist/core/analysis-feature-registry.js +15 -0
- package/dist/core/analyzer-identity.js +21 -3
- package/dist/core/auto-sync/analysis-worker-launch.d.ts +30 -0
- package/dist/core/auto-sync/analysis-worker-launch.js +166 -0
- package/dist/core/auto-sync/config.d.ts +33 -0
- package/dist/core/auto-sync/config.js +320 -0
- package/dist/core/auto-sync/index.d.ts +6 -0
- package/dist/core/auto-sync/index.js +6 -0
- package/dist/core/auto-sync/path-security.d.ts +9 -0
- package/dist/core/auto-sync/path-security.js +251 -0
- package/dist/core/auto-sync/repo.d.ts +1 -0
- package/dist/core/auto-sync/repo.js +6 -0
- package/dist/core/auto-sync/runner.d.ts +44 -0
- package/dist/core/auto-sync/runner.js +428 -0
- package/dist/core/auto-sync/starter.d.ts +50 -0
- package/dist/core/auto-sync/starter.js +497 -0
- package/dist/core/auto-sync/state.d.ts +37 -0
- package/dist/core/auto-sync/state.js +119 -0
- package/dist/core/group/config-parser.js +8 -1
- package/dist/core/group/cross-impact.d.ts +3 -0
- package/dist/core/group/cross-impact.js +36 -4
- package/dist/core/group/extractors/http-patterns/java.js +22 -8
- package/dist/core/group/extractors/http-patterns/kotlin.js +86 -31
- package/dist/core/group/extractors/http-patterns/node.js +145 -8
- package/dist/core/group/extractors/http-patterns/python.js +11 -7
- package/dist/core/group/extractors/http-route-extractor.js +63 -17
- package/dist/core/group/extractors/java-workspace-extractor.js +244 -31
- package/dist/core/group/group-lock.js +5 -6
- package/dist/core/group/normalization.d.ts +38 -1
- package/dist/core/group/normalization.js +59 -3
- package/dist/core/group/service.d.ts +11 -0
- package/dist/core/group/service.js +13 -1
- package/dist/core/group/storage.js +2 -2
- package/dist/core/group/sync.d.ts +24 -1
- package/dist/core/group/sync.js +46 -8
- package/dist/core/group/types.d.ts +13 -0
- package/dist/core/incremental/derived-writeback.d.ts +36 -0
- package/dist/core/incremental/derived-writeback.js +68 -0
- package/dist/core/incremental/spring-config-drift.d.ts +12 -0
- package/dist/core/incremental/spring-config-drift.js +46 -0
- package/dist/core/incremental/subgraph-extract.d.ts +7 -4
- package/dist/core/incremental/subgraph-extract.js +30 -5
- package/dist/core/ingestion/asyncapi/document.d.ts +226 -0
- package/dist/core/ingestion/asyncapi/document.js +758 -0
- package/dist/core/ingestion/asyncapi/protocol.d.ts +79 -0
- package/dist/core/ingestion/asyncapi/protocol.js +203 -0
- package/dist/core/ingestion/call-extractors/configs/zig.d.ts +2 -0
- package/dist/core/ingestion/call-extractors/configs/zig.js +4 -0
- package/dist/core/ingestion/call-extractors/zig-static-gating.d.ts +100 -0
- package/dist/core/ingestion/call-extractors/zig-static-gating.js +319 -0
- package/dist/core/ingestion/call-processor.js +3 -1
- package/dist/core/ingestion/class-extractors/configs/zig.d.ts +2 -0
- package/dist/core/ingestion/class-extractors/configs/zig.js +32 -0
- package/dist/core/ingestion/class-extractors/generic.js +4 -1
- package/dist/core/ingestion/class-types.d.ts +6 -2
- package/dist/core/ingestion/destination-key.d.ts +59 -0
- package/dist/core/ingestion/destination-key.js +61 -0
- package/dist/core/ingestion/entry-point-scoring.d.ts +6 -2
- package/dist/core/ingestion/entry-point-scoring.js +8 -44
- package/dist/core/ingestion/export-detection.d.ts +37 -0
- package/dist/core/ingestion/export-detection.js +60 -0
- package/dist/core/ingestion/field-extractors/configs/zig.d.ts +8 -0
- package/dist/core/ingestion/field-extractors/configs/zig.js +49 -0
- package/dist/core/ingestion/field-extractors/generic.d.ts +1 -1
- package/dist/core/ingestion/field-extractors/generic.js +12 -1
- package/dist/core/ingestion/frameworks/spring/actuator-runtime.d.ts +26 -0
- package/dist/core/ingestion/frameworks/spring/actuator-runtime.js +843 -0
- package/dist/core/ingestion/frameworks/spring/analysis-features.d.ts +5 -0
- package/dist/core/ingestion/frameworks/spring/analysis-features.js +9 -0
- package/dist/core/ingestion/frameworks/spring/annotation-arguments.js +8 -1
- package/dist/core/ingestion/frameworks/spring/argument-facts.d.ts +80 -0
- package/dist/core/ingestion/frameworks/spring/argument-facts.js +85 -0
- package/dist/core/ingestion/frameworks/spring/config-bindings.d.ts +1 -0
- package/dist/core/ingestion/frameworks/spring/config-bindings.js +2 -1
- package/dist/core/ingestion/frameworks/spring/destinations.d.ts +539 -0
- package/dist/core/ingestion/frameworks/spring/destinations.js +840 -0
- package/dist/core/ingestion/frameworks/spring/dynamic-lookups.d.ts +22 -0
- package/dist/core/ingestion/frameworks/spring/dynamic-lookups.js +124 -0
- package/dist/core/ingestion/frameworks/spring/message-producers.d.ts +78 -0
- package/dist/core/ingestion/frameworks/spring/message-producers.js +90 -0
- package/dist/core/ingestion/frameworks/spring/non-http-handlers.d.ts +29 -0
- package/dist/core/ingestion/frameworks/spring/vendor-prefixes.d.ts +7 -0
- package/dist/core/ingestion/frameworks/spring/vendor-prefixes.js +22 -0
- package/dist/core/ingestion/import-resolvers/configs/zig.d.ts +13 -0
- package/dist/core/ingestion/import-resolvers/configs/zig.js +26 -0
- package/dist/core/ingestion/import-resolvers/zig.d.ts +34 -0
- package/dist/core/ingestion/import-resolvers/zig.js +193 -0
- package/dist/core/ingestion/language-config.d.ts +177 -0
- package/dist/core/ingestion/language-config.js +719 -0
- package/dist/core/ingestion/language-provider.d.ts +178 -1
- package/dist/core/ingestion/language-provider.js +22 -0
- package/dist/core/ingestion/languages/csharp/razor-view-components.d.ts +62 -0
- package/dist/core/ingestion/languages/csharp/razor-view-components.js +954 -0
- package/dist/core/ingestion/languages/csharp/resolution-config.d.ts +3 -0
- package/dist/core/ingestion/languages/csharp/resolution-config.js +6 -1
- package/dist/core/ingestion/languages/csharp/scope-resolver.js +8 -0
- package/dist/core/ingestion/languages/index.d.ts +1 -0
- package/dist/core/ingestion/languages/index.js +2 -0
- package/dist/core/ingestion/languages/java/analysis-features.d.ts +1 -1
- package/dist/core/ingestion/languages/java/analysis-features.js +14 -6
- package/dist/core/ingestion/languages/java/capture-side-channel.d.ts +8 -0
- package/dist/core/ingestion/languages/java/capture-side-channel.js +34 -0
- package/dist/core/ingestion/languages/java/captures.js +22 -1
- package/dist/core/ingestion/languages/java/lombok-synthesizer.d.ts +42 -0
- package/dist/core/ingestion/languages/java/lombok-synthesizer.js +439 -0
- package/dist/core/ingestion/languages/java/scope-resolver.js +2 -0
- package/dist/core/ingestion/languages/java/spring-actuator.d.ts +2 -0
- package/dist/core/ingestion/languages/java/spring-actuator.js +62 -0
- package/dist/core/ingestion/languages/java/spring-di.d.ts +17 -1
- package/dist/core/ingestion/languages/java/spring-di.js +44 -2
- package/dist/core/ingestion/languages/java/spring-dynamic-lookup.d.ts +8 -0
- package/dist/core/ingestion/languages/java/spring-dynamic-lookup.js +62 -0
- package/dist/core/ingestion/languages/java/spring-message-producers.d.ts +20 -0
- package/dist/core/ingestion/languages/java/spring-message-producers.js +83 -0
- package/dist/core/ingestion/languages/java/spring-non-http-handlers.d.ts +11 -1
- package/dist/core/ingestion/languages/java/spring-non-http-handlers.js +13 -3
- package/dist/core/ingestion/languages/java.js +18 -8
- package/dist/core/ingestion/languages/jvm/accessor-synthesis.d.ts +99 -0
- package/dist/core/ingestion/languages/jvm/accessor-synthesis.js +173 -0
- package/dist/core/ingestion/languages/jvm/beanspec.d.ts +17 -0
- package/dist/core/ingestion/languages/jvm/beanspec.js +42 -0
- package/dist/core/ingestion/languages/kotlin/capture-side-channel.d.ts +15 -0
- package/dist/core/ingestion/languages/kotlin/capture-side-channel.js +50 -0
- package/dist/core/ingestion/languages/kotlin/captures.js +24 -1
- package/dist/core/ingestion/languages/kotlin/lombok-synthesizer.d.ts +26 -0
- package/dist/core/ingestion/languages/kotlin/lombok-synthesizer.js +417 -0
- package/dist/core/ingestion/languages/kotlin/scope-resolver.js +4 -0
- package/dist/core/ingestion/languages/kotlin/spring-actuator.d.ts +4 -0
- package/dist/core/ingestion/languages/kotlin/spring-actuator.js +172 -0
- package/dist/core/ingestion/languages/kotlin/spring-config-bindings.d.ts +18 -0
- package/dist/core/ingestion/languages/kotlin/spring-config-bindings.js +411 -0
- package/dist/core/ingestion/languages/kotlin/spring-di.d.ts +51 -1
- package/dist/core/ingestion/languages/kotlin/spring-di.js +86 -6
- package/dist/core/ingestion/languages/kotlin/spring-dynamic-lookup.d.ts +8 -0
- package/dist/core/ingestion/languages/kotlin/spring-dynamic-lookup.js +77 -0
- package/dist/core/ingestion/languages/kotlin/spring-message-producers.d.ts +17 -0
- package/dist/core/ingestion/languages/kotlin/spring-message-producers.js +119 -0
- package/dist/core/ingestion/languages/kotlin/spring-non-http-handlers.d.ts +16 -0
- package/dist/core/ingestion/languages/kotlin/spring-non-http-handlers.js +77 -2
- package/dist/core/ingestion/languages/kotlin.js +24 -0
- package/dist/core/ingestion/languages/python.js +2 -0
- package/dist/core/ingestion/languages/rust/query.js +8 -8
- package/dist/core/ingestion/languages/zig/captures.d.ts +260 -0
- package/dist/core/ingestion/languages/zig/captures.js +2070 -0
- package/dist/core/ingestion/languages/zig/index.d.ts +4 -0
- package/dist/core/ingestion/languages/zig/index.js +4 -0
- package/dist/core/ingestion/languages/zig/interpret.d.ts +36 -0
- package/dist/core/ingestion/languages/zig/interpret.js +176 -0
- package/dist/core/ingestion/languages/zig/query.d.ts +3 -0
- package/dist/core/ingestion/languages/zig/query.js +426 -0
- package/dist/core/ingestion/languages/zig/range-binding.d.ts +37 -0
- package/dist/core/ingestion/languages/zig/range-binding.js +373 -0
- package/dist/core/ingestion/languages/zig/scope-resolver.d.ts +12 -0
- package/dist/core/ingestion/languages/zig/scope-resolver.js +58 -0
- package/dist/core/ingestion/languages/zig/simple-hooks.d.ts +25 -0
- package/dist/core/ingestion/languages/zig/simple-hooks.js +143 -0
- package/dist/core/ingestion/languages/zig.d.ts +32 -0
- package/dist/core/ingestion/languages/zig.js +130 -0
- package/dist/core/ingestion/method-extractors/configs/zig.d.ts +2 -0
- package/dist/core/ingestion/method-extractors/configs/zig.js +120 -0
- package/dist/core/ingestion/method-extractors/generic.js +16 -4
- package/dist/core/ingestion/method-types.d.ts +10 -4
- package/dist/core/ingestion/model/field-registry.d.ts +3 -3
- package/dist/core/ingestion/model/field-registry.js +11 -7
- package/dist/core/ingestion/model/method-registry.d.ts +3 -3
- package/dist/core/ingestion/model/method-registry.js +13 -8
- package/dist/core/ingestion/model/registration-table.js +3 -0
- package/dist/core/ingestion/model/type-registry.js +8 -4
- package/dist/core/ingestion/pipeline-phases/di.js +47 -14
- package/dist/core/ingestion/pipeline-phases/index.d.ts +1 -0
- package/dist/core/ingestion/pipeline-phases/index.js +1 -0
- package/dist/core/ingestion/pipeline-phases/parse-impl.d.ts +7 -1
- package/dist/core/ingestion/pipeline-phases/parse-impl.js +79 -91
- package/dist/core/ingestion/pipeline-phases/parse.d.ts +17 -0
- package/dist/core/ingestion/pipeline-phases/processes.d.ts +2 -1
- package/dist/core/ingestion/pipeline-phases/processes.js +39 -4
- package/dist/core/ingestion/pipeline-phases/routes.js +27 -18
- package/dist/core/ingestion/pipeline-phases/runner.d.ts +4 -1
- package/dist/core/ingestion/pipeline-phases/runner.js +34 -14
- package/dist/core/ingestion/pipeline-phases/scan.js +82 -1
- package/dist/core/ingestion/pipeline-phases/spring-auto-configuration.d.ts +2 -0
- package/dist/core/ingestion/pipeline-phases/spring-auto-configuration.js +8 -0
- package/dist/core/ingestion/pipeline-phases/spring-destinations.d.ts +123 -0
- package/dist/core/ingestion/pipeline-phases/spring-destinations.js +688 -0
- package/dist/core/ingestion/pipeline.d.ts +30 -1
- package/dist/core/ingestion/pipeline.js +52 -14
- package/dist/core/ingestion/resolve-references.js +5 -0
- package/dist/core/ingestion/route-extractors/constant-resolver.d.ts +16 -0
- package/dist/core/ingestion/route-extractors/constant-resolver.js +10 -0
- package/dist/core/ingestion/route-extractors/java-const-resolver.d.ts +27 -1
- package/dist/core/ingestion/route-extractors/java-const-resolver.js +161 -13
- package/dist/core/ingestion/route-extractors/kotlin-const-resolver.d.ts +12 -18
- package/dist/core/ingestion/route-extractors/kotlin-const-resolver.js +214 -33
- package/dist/core/ingestion/route-extractors/kotlin-spring.d.ts +13 -0
- package/dist/core/ingestion/route-extractors/kotlin-spring.js +259 -0
- package/dist/core/ingestion/route-extractors/python-decorator-handler.d.ts +6 -0
- package/dist/core/ingestion/route-extractors/python-decorator-handler.js +16 -0
- package/dist/core/ingestion/route-extractors/spring-shared.d.ts +30 -1
- package/dist/core/ingestion/route-extractors/spring-shared.js +77 -9
- package/dist/core/ingestion/route-extractors/spring.js +9 -5
- package/dist/core/ingestion/scope-extractor.js +8 -0
- package/dist/core/ingestion/scope-resolution/contract/scope-resolver.d.ts +62 -0
- package/dist/core/ingestion/scope-resolution/graph-bridge/edges.d.ts +4 -0
- package/dist/core/ingestion/scope-resolution/graph-bridge/edges.js +2 -0
- package/dist/core/ingestion/scope-resolution/graph-bridge/ids.js +28 -0
- package/dist/core/ingestion/scope-resolution/graph-bridge/node-lookup.js +11 -2
- package/dist/core/ingestion/scope-resolution/graph-bridge/references-to-edges.d.ts +23 -2
- package/dist/core/ingestion/scope-resolution/graph-bridge/references-to-edges.js +24 -3
- package/dist/core/ingestion/scope-resolution/passes/callable-value-flow.d.ts +11 -1
- package/dist/core/ingestion/scope-resolution/passes/callable-value-flow.js +26 -6
- package/dist/core/ingestion/scope-resolution/passes/compound-receiver.d.ts +11 -0
- package/dist/core/ingestion/scope-resolution/passes/compound-receiver.js +29 -3
- package/dist/core/ingestion/scope-resolution/passes/free-call-fallback.d.ts +8 -1
- package/dist/core/ingestion/scope-resolution/passes/free-call-fallback.js +35 -9
- package/dist/core/ingestion/scope-resolution/passes/receiver-bound-calls.d.ts +1 -4
- package/dist/core/ingestion/scope-resolution/passes/receiver-bound-calls.js +277 -9
- package/dist/core/ingestion/scope-resolution/pipeline/registry.js +2 -0
- package/dist/core/ingestion/scope-resolution/pipeline/run.js +7 -3
- package/dist/core/ingestion/scope-resolution/scope/walkers.d.ts +43 -9
- package/dist/core/ingestion/scope-resolution/scope/walkers.js +71 -10
- package/dist/core/ingestion/tree-sitter-queries.d.ts +1 -0
- package/dist/core/ingestion/tree-sitter-queries.js +161 -0
- package/dist/core/ingestion/type-extractors/zig.d.ts +2 -0
- package/dist/core/ingestion/type-extractors/zig.js +17 -0
- package/dist/core/ingestion/utils/ast-helpers.d.ts +40 -1
- package/dist/core/ingestion/utils/ast-helpers.js +124 -6
- package/dist/core/ingestion/utils/callable-flow-captures.d.ts +9 -0
- package/dist/core/ingestion/utils/callable-flow-captures.js +75 -14
- package/dist/core/ingestion/utils/test-file-path.d.ts +28 -0
- package/dist/core/ingestion/utils/test-file-path.js +87 -0
- package/dist/core/ingestion/variable-extractors/configs/zig.d.ts +2 -0
- package/dist/core/ingestion/variable-extractors/configs/zig.js +71 -0
- package/dist/core/ingestion/workers/parse-worker.js +85 -14
- package/dist/core/install-context.d.ts +4 -0
- package/dist/core/install-context.js +152 -0
- package/dist/core/lbug/csv-generator.d.ts +10 -1
- package/dist/core/lbug/csv-generator.js +92 -22
- package/dist/core/lbug/graph-emit-sink.d.ts +3 -2
- package/dist/core/lbug/graph-emit-sink.js +4 -2
- package/dist/core/lbug/lbug-adapter.d.ts +100 -5
- package/dist/core/lbug/lbug-adapter.js +236 -24
- package/dist/core/lbug/schema.d.ts +6 -5
- package/dist/core/lbug/schema.js +84 -22
- package/dist/core/net/url-guard.d.ts +6 -0
- package/dist/core/net/url-guard.js +147 -0
- package/dist/core/package-version.d.ts +5 -0
- package/dist/core/package-version.js +10 -0
- package/dist/core/run-analyze.d.ts +21 -4
- package/dist/core/run-analyze.js +426 -117
- package/dist/core/search/fts-indexes.d.ts +19 -1
- package/dist/core/search/fts-indexes.js +28 -1
- package/dist/core/tree-sitter/parser-loader.js +16 -0
- package/dist/core/update-cache.d.ts +39 -0
- package/dist/core/update-cache.js +141 -0
- package/dist/core/update-check.d.ts +31 -0
- package/dist/core/update-check.js +274 -0
- package/dist/mcp/local/local-backend.d.ts +27 -13
- package/dist/mcp/local/local-backend.js +148 -60
- package/dist/mcp/repository-policy.d.ts +5 -1
- package/dist/mcp/repository-policy.js +48 -4
- package/dist/mcp/resources.js +10 -1
- package/dist/mcp/server.js +8 -9
- package/dist/mcp/tools.js +32 -22
- package/dist/server/analyze-launch.d.ts +2 -0
- package/dist/server/analyze-launch.js +2 -0
- package/dist/server/analyze-worker-protocol.d.ts +6 -1
- package/dist/server/analyze-worker.d.ts +1 -0
- package/dist/server/analyze-worker.js +38 -20
- package/dist/server/api.d.ts +5 -1
- package/dist/server/api.js +105 -88
- package/dist/server/git-clone.d.ts +40 -16
- package/dist/server/git-clone.js +253 -199
- package/dist/server/grep-params.d.ts +18 -0
- package/dist/server/grep-params.js +83 -0
- package/dist/server/grep-scan.d.ts +23 -0
- package/dist/server/grep-scan.js +107 -0
- package/dist/server/grep-worker.d.ts +1 -0
- package/dist/server/grep-worker.js +12 -0
- package/dist/server/mcp-http.d.ts +8 -0
- package/dist/server/mcp-http.js +16 -1
- package/dist/server/update-controller.d.ts +29 -0
- package/dist/server/update-controller.js +82 -0
- package/dist/storage/file-lock.d.ts +15 -0
- package/dist/storage/file-lock.js +155 -0
- package/dist/storage/fs-atomic.d.ts +24 -0
- package/dist/storage/fs-atomic.js +86 -2
- package/dist/storage/global-dir.d.ts +2 -0
- package/dist/storage/global-dir.js +6 -0
- package/dist/storage/parse-cache.d.ts +45 -7
- package/dist/storage/parse-cache.js +189 -42
- package/dist/storage/parsedfile-store.d.ts +41 -60
- package/dist/storage/parsedfile-store.js +247 -171
- package/dist/storage/repo-manager.d.ts +10 -4
- package/dist/storage/repo-manager.js +35 -25
- package/dist/storage/repo-meta.d.ts +34 -0
- package/dist/storage/v8-sidecar.d.ts +48 -0
- package/dist/storage/v8-sidecar.js +347 -0
- package/dist/types/pipeline.d.ts +14 -0
- package/dist/utils/process-identity.d.ts +2 -0
- package/dist/utils/process-identity.js +35 -0
- package/package.json +10 -4
- package/scripts/assert-web-assets.mjs +35 -0
- package/scripts/build-web.js +72 -0
- package/scripts/build.js +31 -28
- package/scripts/cross-platform-tests.ts +5 -0
- package/skills/gitnexus-cli.md +5 -1
- package/web/assets/ProcessFlowModal-DAeX2BHW.js +24 -0
- package/web/assets/{abnfDiagram-QNN2D4NM-Cq2djWWe.js → abnfDiagram-PAR5IXJI-J3OdeHLl.js} +1 -1
- package/web/assets/{agent-BWAXPjC5.js → agent-BMRqXorA.js} +6 -4
- package/web/assets/architecture-7GRP2DOG-M7G3SWCO-G3sWyX6M.js +1 -0
- package/web/assets/architectureDiagram-2PNWDENW-ysKcRlGT.js +36 -0
- package/web/assets/blockDiagram-PUWGUBOA-oLXTa1lY.js +129 -0
- package/web/assets/c4Diagram-3NLNHZP5-D3iu4eC0.js +38 -0
- package/web/assets/{chunk-2SREHG4O-Dip42xOq.js → chunk-2NMSUPJH-B24w-wng.js} +1 -1
- package/web/assets/{chunk-VJFVEZTB-CX66F2ep.js → chunk-32MCUW2P-ByUyt0Sa.js} +3 -3
- package/web/assets/{chunk-4RFN2BYJ-DHHWuC-n.js → chunk-3NPNU5GP-DocZ07Sg.js} +1 -1
- package/web/assets/{chunk-XNMVGMAZ-Cg_LnPtj.js → chunk-3SMGFRGC-CAu4JRLv.js} +1 -1
- package/web/assets/{chunk-W44A43WB-BN8EQc_u.js → chunk-4CAJECRU-DRYMwG9z.js} +1 -1
- package/web/assets/chunk-4OADUZ5Q-CxrSvOwZ.js +10 -0
- package/web/assets/{chunk-DMV4VAQV-Bn6WXDSX.js → chunk-5IQCK5RY-CCMhl4so.js} +2 -2
- package/web/assets/{chunk-RRFMTAIC-CMN0wcYA.js → chunk-5MVFYWFU-Db9KvrYO.js} +1 -1
- package/web/assets/chunk-CLS4B6BI-Uvx5QNsu.js +168 -0
- package/web/assets/chunk-DZP67EKU-B3Mt380j.js +70 -0
- package/web/assets/{chunk-6L755F7B-CMhLBxi2.js → chunk-EICP7K5X-CqwE9hui.js} +1 -1
- package/web/assets/{chunk-BI6VK774-D0oaWk8V.js → chunk-GDY2UAB4-CtQQR0a8.js} +1 -1
- package/web/assets/chunk-H7VHZCWX-CE1nAmt6.js +1 -0
- package/web/assets/chunk-IJBDOHL6-BUKtY6oF.js +1 -0
- package/web/assets/{chunk-OIYT25JQ-CQ6KOZGX.js → chunk-JFFVMJIA-0fJenqia.js} +1 -1
- package/web/assets/{chunk-HNC4WDU7-DoU3KnA7.js → chunk-JZGDI5JK-BeTMSSkJ.js} +1 -1
- package/web/assets/{chunk-EQFTRU2I-1ix7TzW9.js → chunk-KD6SPIPN-Qp1Pflah.js} +1 -1
- package/web/assets/chunk-NF366YI6-CpSpya3n.js +231 -0
- package/web/assets/chunk-NUDPKBEF-CU1-BQIW.js +34 -0
- package/web/assets/{chunk-SATU7PGQ-B1zhgOvc.js → chunk-OJINMHTS-zOCQe4eI.js} +1 -1
- package/web/assets/{chunk-E5QJAATJ-dh-5CtIV.js → chunk-Q6NGPIAH-yyBomb63.js} +1 -1
- package/web/assets/chunk-QJSWEUOL-D_rf9gPD.js +128 -0
- package/web/assets/chunk-R7JDMUKV-CRoqKpD6.js +1 -0
- package/web/assets/{chunk-VE5CLXGZ-C1vB7wpY.js → chunk-RCXAM2GK-CUqrWQDV.js} +1 -1
- package/web/assets/{chunk-66DQ2XMT-CM0xjFAN.js → chunk-RT4MAWR3-zXy9-Lvt.js} +1 -1
- package/web/assets/{chunk-TZUXEDM2-B7p9AcTw.js → chunk-SFMINX4W-J2UrvMpD.js} +1 -1
- package/web/assets/{chunk-LORZLZWG-B57uGw2N.js → chunk-SLDQ463B-CHY6e_bQ.js} +2 -2
- package/web/assets/{chunk-FWYVLQTC-BgDItRzJ.js → chunk-TCEHX7R3-CXPDEVbm.js} +1 -1
- package/web/assets/{chunk-224SPVON-B8gxtXpy.js → chunk-VQC2EELJ-Dc8fIy4R.js} +1 -1
- package/web/assets/chunk-VRZKL3EH-DB3KixTQ.js +1 -0
- package/web/assets/{chunk-A7VWPJGB-B4O3oQdQ.js → chunk-VTHDQ7BV-b7FsPQZO.js} +1 -1
- package/web/assets/chunk-VY2OSTJR-Dkgc3Brg.js +2 -0
- package/web/assets/chunk-WUBWJARI-7FwILJ3k.js +2 -0
- package/web/assets/{chunk-D6VWDJW2-BxcBdZ9c.js → chunk-YBMQ5JGJ-BSnyWZmd.js} +1 -1
- package/web/assets/chunk-YDMLL4PJ-IHhMngTz.js +2 -0
- package/web/assets/chunk-Z6V44ARX-BjCEsF0Q.js +62 -0
- package/web/assets/chunk-ZJJ7VQJP-Dz089b-K.js +321 -0
- package/web/assets/chunk-ZNLK7HRR-B0iFpmW4.js +1 -0
- package/web/assets/chunk-ZQN7UHIL-Cy7KAjQt.js +1 -0
- package/web/assets/{chunk-L5GCOVLC-CE2w8Nm_.js → chunk-ZS6VBONO-DBjJO0w9.js} +2 -2
- package/web/assets/{chunk-NV3KIAZN-5yvcpMiu.js → chunk-ZUNWM646-DNRJyNPe.js} +1 -1
- package/web/assets/classDiagram-FGAMPOII-C5Ibyode.js +1 -0
- package/web/assets/classDiagram-v2-AI5RJEK4-C5Ibyode.js +1 -0
- package/web/assets/{cose-bilkent-RZA4XIPR-BHpK4Crp.js → cose-bilkent-Q72C2OJ3-DMeFXnK8.js} +1 -1
- package/web/assets/cynefin-OW5HDTMX-6OCTWOGG-vlqakYcO.js +1 -0
- package/web/assets/{cynefinDiagram-TR7LZUCG-C3Iar9lZ.js → cynefinDiagram-2OOWUY4M-BY6DZkTt.js} +1 -1
- package/web/assets/dagre-MPVFI544-DWO70fy_.js +4 -0
- package/web/assets/diagram-AE4NWXFW-f7JRVRUL.js +24 -0
- package/web/assets/{diagram-L7DVE3PN-GIRyVUtt.js → diagram-BUJID5UG-BXZv1rCS.js} +2 -2
- package/web/assets/{diagram-JCD7PQ7C-DC5-9FRr.js → diagram-C44CZ5NI-CRNfvjCq.js} +1 -1
- package/web/assets/diagram-EYOLEXXS-DZOjKQVE.js +30 -0
- package/web/assets/diagram-P7B6PWOJ-D3SQmw3_.js +3 -0
- package/web/assets/{ebnfDiagram-DRHRQJWO-D8_CI4ZF.js → ebnfDiagram-FTPZ3PV6-jKWpIDN2.js} +1 -1
- package/web/assets/erDiagram-H3ERR26V-BqJntEuJ.js +99 -0
- package/web/assets/eventmodeling-NTZA5JFV-WKSWQHFS-Bq4FqtBL.js +1 -0
- package/web/assets/flowDiagram-YHGXBVSY-Dnlk2bEu.js +1 -0
- package/web/assets/ganttDiagram-RWFTB7ED-Bl6Y1K5T.js +292 -0
- package/web/assets/gitGraph-4MIJSDKK-JH2HKYUO-B3yc4GkH.js +1 -0
- package/web/assets/gitGraphDiagram-MAR6GKOP-CkNvsrdw.js +106 -0
- package/web/assets/{index-CJDpyH-n.css → index-C2_aej58.css} +1 -1
- package/web/assets/{index-DDRie6_8.js → index-Dj3vQOHK.js} +51 -51
- package/web/assets/info-A6RAGUB7-3TX3OFWW-BM3N5_Dv.js +1 -0
- package/web/assets/infoDiagram-LV547AFH-COkFutAf.js +2 -0
- package/web/assets/{ishikawaDiagram-4JDSWWDM-DmqWtjkl.js → ishikawaDiagram-PUJQGNDS-Bb8FylSR.js} +2 -2
- package/web/assets/{journeyDiagram-QUS3FAKT-XAzVzrQW.js → journeyDiagram-EW5QJT4W-BzoHUBhM.js} +4 -4
- package/web/assets/{kanban-definition-D4L7SQKK-YurRnmYn.js → kanban-definition-JSQERAZB-Uy2Pi0yQ.js} +4 -4
- package/web/assets/katex-CZ4GXH2S-C0Fcou7U.js +257 -0
- package/web/assets/{mindmap-definition-66QP5TD6-ChrWWnil.js → mindmap-definition-VBJCLQLM-B-CnOnt_.js} +3 -3
- package/web/assets/packet-AYTQ26CC-MKII6P3Y-Bd52ZE3V.js +1 -0
- package/web/assets/{pegDiagram-YKIO5C6U-QGeQLEq2.js → pegDiagram-7WILOQTJ-D9sneKXg.js} +1 -1
- package/web/assets/pie-WAS4IAKB-6B4OPMJ5-DW5Xy69Z.js +1 -0
- package/web/assets/pieDiagram-EFA7CBSB-Dx2NQofL.js +39 -0
- package/web/assets/{quadrantDiagram-VO5QLCXA-B7098wSc.js → quadrantDiagram-WARVYA7P-DPKL9_0A.js} +3 -3
- package/web/assets/radar-RG4KPBEZ-UPTR3XUL-CkzibT2h.js +1 -0
- package/web/assets/railroad-74A4TZTK-MEFUPFMS-D9zF-mDN.js +1 -0
- package/web/assets/railroad-abnf-HS5TGJTU-R5YG2S5D-ruacjUbX.js +1 -0
- package/web/assets/railroad-ebnf-LZEXJU2U-G5QEX2L6-S1zc5OiO.js +1 -0
- package/web/assets/railroad-peg-WCYAUIDC-H5I2ZRY2-Cbcr0XzY.js +1 -0
- package/web/assets/{railroadDiagram-M77LTMSB-AXu4vxk_.js → railroadDiagram-5NIQVNLQ-DsweTo14.js} +1 -1
- package/web/assets/{requirementDiagram-RASO5HTO-B-l8InpK.js → requirementDiagram-SXREGOKZ-CXwdNSVR.js} +4 -4
- package/web/assets/{sankeyDiagram-F32LOJ6L-Kd7zjJgX.js → sankeyDiagram-VV74M2VP-CA6jtP9P.js} +3 -3
- package/web/assets/{sequenceDiagram-URATNSBD-BlcdY9DS.js → sequenceDiagram-52ZFIFDL-k_5rEbzm.js} +15 -15
- package/web/assets/src-Df5C1Nz4.js +1 -0
- package/web/assets/stateDiagram-3QAKPIVI-BejHTf31.js +1 -0
- package/web/assets/stateDiagram-v2-HN2P4RDZ-BrfoRuOc.js +1 -0
- package/web/assets/swimlanes-QLVP42QD-CQSVO5D6.js +1 -0
- package/web/assets/swimlanesDiagram-JKPDBFCN-DrWB9kcD.js +8 -0
- package/web/assets/{timeline-definition-WJXWRH4I-DUfp1R3E.js → timeline-definition-GS45XAXW-C0Ks8rPP.js} +4 -4
- package/web/assets/treeView-Q6P3EWNA-ZRJGANNQ-DmKOCo6L.js +1 -0
- package/web/assets/treemap-WGGIJYW6-K64VN5AS-DO3xY92U.js +1 -0
- package/web/assets/{vennDiagram-K3VCMQLI-Bj-FN-XS.js → vennDiagram-QIQU2ZRJ-rgPiS837.js} +3 -3
- package/web/assets/wardley-WFR3VGLG-D67OWCCH-DiDPBNft.js +1 -0
- package/web/assets/wardleyDiagram-U6YNM66N-DRVr7SPi.js +78 -0
- package/web/assets/{xychartDiagram-5VRUVLCW-CK_rbSWE.js → xychartDiagram-XJAZEDNM-COtlZzYb.js} +3 -3
- package/web/index.html +19 -17
- package/web/assets/ProcessFlowModal-Badh2Pbg.js +0 -24
- package/web/assets/architecture-TIHT7OUA-V6N4J3SY-fTePvLoe.js +0 -1
- package/web/assets/architectureDiagram-OT7VKSJ7-mKEXxkeF.js +0 -36
- package/web/assets/blockDiagram-54WLDVWM-Dg_hrRWE.js +0 -132
- package/web/assets/c4Diagram-X2LASM7V-CnhoT4aV.js +0 -10
- package/web/assets/chunk-2AEHWXPW-CV4r6-1I.js +0 -127
- package/web/assets/chunk-5VCL7Z4A-CQ-2dYWj.js +0 -1
- package/web/assets/chunk-6BELYETK-ChsYtfHW.js +0 -1
- package/web/assets/chunk-7CWYLC5S-DRgGkkpF.js +0 -2
- package/web/assets/chunk-7FYTHRHK-CsmYIswB.js +0 -32
- package/web/assets/chunk-KRXBNO2N-C-UNuKKf.js +0 -1
- package/web/assets/chunk-LAHSG3N2-Bz4hcn0N.js +0 -321
- package/web/assets/chunk-LIEV3EAG-pyKGssV3.js +0 -1
- package/web/assets/chunk-NLANEA3F-XpH_NPA5.js +0 -62
- package/web/assets/chunk-PE7DX7ZZ-CCvRVj43.js +0 -10
- package/web/assets/chunk-Q67WD55A-BWCH2SVz.js +0 -156
- package/web/assets/chunk-SZD42YQK-ijyKKVr1.js +0 -70
- package/web/assets/chunk-TFPGFQPA-Dj6a-0pC.js +0 -231
- package/web/assets/chunk-TGVD4F4B-BMc31Gj2.js +0 -2
- package/web/assets/chunk-UAT7B5JY-DW-A_fpI.js +0 -1
- package/web/assets/chunk-VY5UBI4V-yxPQAWxw.js +0 -1
- package/web/assets/chunk-WJBAP47W-BckNkuEN.js +0 -1
- package/web/assets/classDiagram-VPOSVPXH-BjOiXA_C.js +0 -1
- package/web/assets/classDiagram-v2-5VFNUXQA-BjOiXA_C.js +0 -1
- package/web/assets/cynefin-VYW2F7L2-2VAHYKR6-DAsF6GZv.js +0 -1
- package/web/assets/dagre-K64A6Z3X-CUwi79mA.js +0 -4
- package/web/assets/diagram-2TLGJV2N-C2F5Nfym.js +0 -30
- package/web/assets/diagram-KFBZAVDW-Dfmf_4ti.js +0 -3
- package/web/assets/diagram-YP3FGXHI-CyNFYtkd.js +0 -24
- package/web/assets/erDiagram-SZLJLQMB-CohQjznC.js +0 -85
- package/web/assets/eventmodeling-45OFAUF4-AOUJWSLX-BvTc6iQi.js +0 -1
- package/web/assets/flowDiagram-BWE6NHOH-B6RnEvD9.js +0 -1
- package/web/assets/ganttDiagram-2QTCJSNI-Cks_mEcF.js +0 -292
- package/web/assets/gitGraph-TEB2WS4Q-U5VWYIBC-Dy7CCSZ5.js +0 -1
- package/web/assets/gitGraphDiagram-VVZAVV5G-CG41cIVc.js +0 -106
- package/web/assets/info-DKCQHKI2-DX6KST54-BLJg1ikX.js +0 -1
- package/web/assets/infoDiagram-FY4PZKRE-BJ3vPVwW.js +0 -2
- package/web/assets/katex-C5OPUE3Q-Bnbavtmu.js +0 -257
- package/web/assets/packet-7NZHBO7P-5WG45QPS-KUxcuvaA.js +0 -1
- package/web/assets/pie-RZYD4A2V-2OCZ2VTD-DPs08kJY.js +0 -1
- package/web/assets/pieDiagram-YPY3TKG5-BuT3MYzm.js +0 -39
- package/web/assets/radar-I7S5WNFK-EPLSA35B-w5F0K3a7.js +0 -1
- package/web/assets/railroad-3IZDKUUU-PWFQV6Y6-Bu668yqY.js +0 -1
- package/web/assets/railroad-abnf-AHOZXSZD-JYYJX3BS-B6ghpETS.js +0 -1
- package/web/assets/railroad-ebnf-EBAXGLYW-RBZ4ONAP-D-XmHh3M.js +0 -1
- package/web/assets/railroad-peg-LSFZ7HO6-KHNPXGPG-mhkrhzEn.js +0 -1
- package/web/assets/src-C5sx_gMp.js +0 -1
- package/web/assets/stateDiagram-DODFXNG5-DTdHc-fF.js +0 -1
- package/web/assets/stateDiagram-v2-JNMM5JRA-DioNLDDo.js +0 -1
- package/web/assets/swimlanes-OSWOYLSK-CA0ofdwY.js +0 -2
- package/web/assets/swimlanesDiagram-RHGX2RDE-PkwUdckC.js +0 -8
- package/web/assets/treeView-QDETBFTQ-EHYTSQNO-C5lpBgPm.js +0 -1
- package/web/assets/treemap-6X3UGDF4-CCWCF7EK-BB0ZyO-B.js +0 -1
- package/web/assets/wardley-OPB4EBWU-6OWERDVT-CEA3Nq0j.js +0 -1
- package/web/assets/wardleyDiagram-XK6FUAWV-CVV0dHFr.js +0 -78
- /package/web/assets/{sizeCapture-5GORAGP4-BEqQzsxs.js → sizeCapture-FGA5AGYP-BEqQzsxs.js} +0 -0
package/dist/server/git-clone.js
CHANGED
|
@@ -7,10 +7,14 @@
|
|
|
7
7
|
import { spawn } from 'child_process';
|
|
8
8
|
import path from 'path';
|
|
9
9
|
import fs from 'fs/promises';
|
|
10
|
-
import
|
|
10
|
+
import os from 'node:os';
|
|
11
11
|
import { logger } from '../core/logger.js';
|
|
12
|
-
import { parseRepoNameFromUrl, stripUrlCredentials } from '../storage/git.js';
|
|
13
12
|
import { getGlobalDir } from '../storage/repo-manager.js';
|
|
13
|
+
import { sanitizeRepoName, stripUrlCredentials } from '../storage/git.js';
|
|
14
|
+
import { validateGitUrl } from '../core/net/url-guard.js';
|
|
15
|
+
import { assertDirectoryOwnerAndPermissions, quarantineAutoSyncPartial, } from '../core/auto-sync/path-security.js';
|
|
16
|
+
import { validateAutoSyncRemoteUrl } from '../core/auto-sync/config.js';
|
|
17
|
+
export { validateGitUrl };
|
|
14
18
|
/**
|
|
15
19
|
* Root directory for all cloned repositories. Targets must resolve inside this.
|
|
16
20
|
*
|
|
@@ -35,16 +39,41 @@ export const REPO_NAME_PATTERN = /^[a-zA-Z0-9._-]+$/;
|
|
|
35
39
|
* clone root via path traversal.
|
|
36
40
|
*/
|
|
37
41
|
export function extractRepoName(url) {
|
|
38
|
-
|
|
42
|
+
let trimmed = url.trim();
|
|
43
|
+
while (trimmed.endsWith('/'))
|
|
44
|
+
trimmed = trimmed.slice(0, -1);
|
|
45
|
+
const withoutGit = trimmed.toLowerCase().endsWith('.git') ? trimmed.slice(0, -4) : trimmed;
|
|
46
|
+
const name = withoutGit.split(/[/:]/).filter(Boolean).pop() ?? '';
|
|
39
47
|
if (!name ||
|
|
40
48
|
name === '.' ||
|
|
41
49
|
name === '..' ||
|
|
42
50
|
name === 'unknown' ||
|
|
51
|
+
name.startsWith('-') ||
|
|
43
52
|
!REPO_NAME_PATTERN.test(name)) {
|
|
44
53
|
throw new Error('Could not extract a valid repository name from URL');
|
|
45
54
|
}
|
|
46
55
|
return name;
|
|
47
56
|
}
|
|
57
|
+
/**
|
|
58
|
+
* Derive a clone directory name for the web `/api/analyze` boundary.
|
|
59
|
+
*
|
|
60
|
+
* The API historically accepted Azure DevOps and similar URLs whose repo
|
|
61
|
+
* segment contains spaces or other directory-unsafe characters by sanitizing
|
|
62
|
+
* the final segment. Keep that compatibility at the web boundary while leaving
|
|
63
|
+
* `extractRepoName()` strict for internal/security-sensitive callers.
|
|
64
|
+
*/
|
|
65
|
+
export function extractWebRepoName(url) {
|
|
66
|
+
let trimmed = url.trim();
|
|
67
|
+
while (trimmed.endsWith('/'))
|
|
68
|
+
trimmed = trimmed.slice(0, -1);
|
|
69
|
+
const withoutGit = trimmed.toLowerCase().endsWith('.git') ? trimmed.slice(0, -4) : trimmed;
|
|
70
|
+
const rawName = withoutGit.split(/[/:]/).filter(Boolean).pop() ?? '';
|
|
71
|
+
const safeName = sanitizeRepoName(rawName);
|
|
72
|
+
if (!rawName || safeName === 'unknown') {
|
|
73
|
+
throw new Error('Could not extract a valid repository name from URL');
|
|
74
|
+
}
|
|
75
|
+
return safeName;
|
|
76
|
+
}
|
|
48
77
|
/** Get the clone target directory for a repo name. */
|
|
49
78
|
export function getCloneDir(repoName) {
|
|
50
79
|
// Re-validate at the boundary even though extractRepoName already checked —
|
|
@@ -54,148 +83,6 @@ export function getCloneDir(repoName) {
|
|
|
54
83
|
}
|
|
55
84
|
return path.join(CLONE_ROOT, repoName);
|
|
56
85
|
}
|
|
57
|
-
// Cloud metadata hostnames that must never be reachable via user-supplied URLs
|
|
58
|
-
const BLOCKED_HOSTNAMES = new Set([
|
|
59
|
-
'localhost',
|
|
60
|
-
'metadata.google.internal',
|
|
61
|
-
'metadata.azure.com',
|
|
62
|
-
'metadata.internal',
|
|
63
|
-
]);
|
|
64
|
-
/**
|
|
65
|
-
* Validate a git URL to prevent SSRF attacks.
|
|
66
|
-
* Only allows https:// and http:// schemes. Blocks private/internal addresses,
|
|
67
|
-
* IPv6 private ranges, cloud metadata hostnames, and numeric IP encodings.
|
|
68
|
-
*/
|
|
69
|
-
export function validateGitUrl(url) {
|
|
70
|
-
let parsed;
|
|
71
|
-
try {
|
|
72
|
-
parsed = new URL(url);
|
|
73
|
-
}
|
|
74
|
-
catch {
|
|
75
|
-
throw new Error('Invalid URL');
|
|
76
|
-
}
|
|
77
|
-
if (!['https:', 'http:'].includes(parsed.protocol)) {
|
|
78
|
-
throw new Error('Only https:// and http:// git URLs are allowed');
|
|
79
|
-
}
|
|
80
|
-
const host = parsed.hostname.toLowerCase();
|
|
81
|
-
// Block known dangerous hostnames (cloud metadata services)
|
|
82
|
-
if (BLOCKED_HOSTNAMES.has(host)) {
|
|
83
|
-
throw new Error('Cloning from private/internal addresses is not allowed');
|
|
84
|
-
}
|
|
85
|
-
// Strip IPv6 brackets if present (URL parser behavior varies across Node versions)
|
|
86
|
-
let normalizedHost = host;
|
|
87
|
-
if (host.startsWith('[') && host.endsWith(']')) {
|
|
88
|
-
normalizedHost = host.slice(1, -1);
|
|
89
|
-
}
|
|
90
|
-
// Check if this is an IPv6 address
|
|
91
|
-
// Use manual colon detection as fallback since isIP may return 0 for some
|
|
92
|
-
// normalized IPv6 forms (e.g. ::ffff:7f00:1)
|
|
93
|
-
const isIPv6 = isIP(normalizedHost) === 6 || normalizedHost.includes(':');
|
|
94
|
-
if (isIPv6) {
|
|
95
|
-
assertNotPrivateIPv6(normalizedHost);
|
|
96
|
-
return;
|
|
97
|
-
}
|
|
98
|
-
// Check if this is an IPv4 address (including numeric encodings)
|
|
99
|
-
if (isIP(normalizedHost) === 4) {
|
|
100
|
-
assertNotPrivateIPv4(normalizedHost);
|
|
101
|
-
return;
|
|
102
|
-
}
|
|
103
|
-
// For non-IP hostnames, check for numeric IP tricks
|
|
104
|
-
// Decimal encoding: 2130706433 = 127.0.0.1
|
|
105
|
-
// Hex encoding: 0x7f000001 = 127.0.0.1
|
|
106
|
-
if (/^\d+$/.test(host) || /^0x[0-9a-f]+$/i.test(host)) {
|
|
107
|
-
throw new Error('Cloning from private/internal addresses is not allowed');
|
|
108
|
-
}
|
|
109
|
-
// Standard IPv4 regex checks for dotted notation
|
|
110
|
-
if (/^127\./.test(host) ||
|
|
111
|
-
/^10\./.test(host) ||
|
|
112
|
-
/^172\.(1[6-9]|2\d|3[01])\./.test(host) ||
|
|
113
|
-
/^192\.168\./.test(host) ||
|
|
114
|
-
/^169\.254\./.test(host) ||
|
|
115
|
-
/^0\./.test(host) ||
|
|
116
|
-
host === '0.0.0.0' ||
|
|
117
|
-
/^100\.(6[4-9]|[7-9]\d|1[01]\d|12[0-7])\./.test(host) ||
|
|
118
|
-
/^198\.1[89]\./.test(host)) {
|
|
119
|
-
throw new Error('Cloning from private/internal addresses is not allowed');
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
function assertNotPrivateIPv6(ip) {
|
|
123
|
-
// Expand common compressed forms for comparison
|
|
124
|
-
const lower = ip.toLowerCase();
|
|
125
|
-
// IPv6 loopback
|
|
126
|
-
if (lower === '::1' || lower === '0:0:0:0:0:0:0:1') {
|
|
127
|
-
throw new Error('Cloning from private/internal addresses is not allowed');
|
|
128
|
-
}
|
|
129
|
-
// Unspecified address
|
|
130
|
-
if (lower === '::' || lower === '0:0:0:0:0:0:0:0') {
|
|
131
|
-
throw new Error('Cloning from private/internal addresses is not allowed');
|
|
132
|
-
}
|
|
133
|
-
// IPv6 Unique Local Address (fc00::/7 = fc and fd prefixes)
|
|
134
|
-
if (lower.startsWith('fc') || lower.startsWith('fd')) {
|
|
135
|
-
throw new Error('Cloning from private/internal addresses is not allowed');
|
|
136
|
-
}
|
|
137
|
-
// IPv6 link-local (fe80::/10)
|
|
138
|
-
if (lower.startsWith('fe80') ||
|
|
139
|
-
lower.startsWith('fe8') ||
|
|
140
|
-
lower.startsWith('fe9') ||
|
|
141
|
-
lower.startsWith('fea') ||
|
|
142
|
-
lower.startsWith('feb')) {
|
|
143
|
-
throw new Error('Cloning from private/internal addresses is not allowed');
|
|
144
|
-
}
|
|
145
|
-
// IPv4-mapped IPv6 (::ffff:x.x.x.x or ::ffff:hex:hex)
|
|
146
|
-
// Node may normalize ::ffff:127.0.0.1 to ::ffff:7f00:1
|
|
147
|
-
if (lower.startsWith('::ffff:')) {
|
|
148
|
-
throw new Error('Cloning from private/internal addresses is not allowed');
|
|
149
|
-
}
|
|
150
|
-
// Also catch the expanded form: 0:0:0:0:0:ffff:
|
|
151
|
-
if (lower.includes(':ffff:')) {
|
|
152
|
-
throw new Error('Cloning from private/internal addresses is not allowed');
|
|
153
|
-
}
|
|
154
|
-
// IPv4-compatible IPv6 (RFC 4291 § 2.5.5.1, deprecated form: ::w.x.y.z).
|
|
155
|
-
// Node's URL parser collapses http://[::127.0.0.1]/ to "::7f00:1" — the IPv4
|
|
156
|
-
// is hidden in the last 32 bits without the ::ffff: marker, so the check
|
|
157
|
-
// above misses it. The form is still routable to the embedded IPv4 on most
|
|
158
|
-
// network stacks, so any address compressed to ::xxxx[:yyyy] must be blocked.
|
|
159
|
-
if (/^::[0-9a-f]{1,4}(:[0-9a-f]{1,4})?$/.test(lower)) {
|
|
160
|
-
throw new Error('Cloning from private/internal addresses is not allowed');
|
|
161
|
-
}
|
|
162
|
-
// NAT64 well-known prefix (RFC 6052 § 2.1: 64:ff9b::/96, plus the local
|
|
163
|
-
// 64:ff9b:1::/48 from RFC 8215). Maps any IPv4 address — including private
|
|
164
|
-
// ranges — into IPv6, so a host with NAT64 can reach the embedded IPv4 via
|
|
165
|
-
// e.g. 64:ff9b::7f00:1 → 127.0.0.1.
|
|
166
|
-
// The check intentionally covers the full 64:ff9b::/32 block (broader than
|
|
167
|
-
// the two cited ranges): IANA reserves it for IPv4-IPv6 translation, so
|
|
168
|
-
// blocking the whole prefix is defensively sound and prevents a narrower
|
|
169
|
-
// CIDR check from quietly re-opening the bypass for 64:ff9b:1::/48 or any
|
|
170
|
-
// future translation assignment.
|
|
171
|
-
if (lower.startsWith('64:ff9b:')) {
|
|
172
|
-
throw new Error('Cloning from private/internal addresses is not allowed');
|
|
173
|
-
}
|
|
174
|
-
// 6to4 (RFC 3056, 2002::/16). Encodes an IPv4 address in bits 17-48, so
|
|
175
|
-
// 2002:7f00:0001::1 routes to 127.0.0.1 on 6to4-capable stacks. The
|
|
176
|
-
// protocol was deprecated by RFC 7526 and the public relay anycast
|
|
177
|
-
// (192.88.99.1) has been retired, so broad-blocking the prefix has near-
|
|
178
|
-
// zero false-positive cost while closing the IPv4-embedded bypass.
|
|
179
|
-
// Teredo (2001::/32) embeds IPv4 obfuscated by XOR; precise blocking is
|
|
180
|
-
// impractical and is out of scope here.
|
|
181
|
-
if (lower.startsWith('2002:')) {
|
|
182
|
-
throw new Error('Cloning from private/internal addresses is not allowed');
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
function assertNotPrivateIPv4(ip) {
|
|
186
|
-
const parts = ip.split('.').map(Number);
|
|
187
|
-
const [a, b] = parts;
|
|
188
|
-
if (a === 127 ||
|
|
189
|
-
a === 10 ||
|
|
190
|
-
(a === 172 && b >= 16 && b <= 31) ||
|
|
191
|
-
(a === 192 && b === 168) ||
|
|
192
|
-
(a === 169 && b === 254) ||
|
|
193
|
-
a === 0 ||
|
|
194
|
-
(a === 100 && b >= 64 && b <= 127) ||
|
|
195
|
-
(a === 198 && (b === 18 || b === 19))) {
|
|
196
|
-
throw new Error('Cloning from private/internal addresses is not allowed');
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
86
|
/**
|
|
200
87
|
* Build the `git clone` argument list for a given URL and target directory.
|
|
201
88
|
*
|
|
@@ -263,6 +150,9 @@ export function warnIfInsecureAzureConfig() {
|
|
|
263
150
|
export function buildCloneArgs(url, targetDir) {
|
|
264
151
|
return ['clone', '--depth', '1', '--', url, targetDir];
|
|
265
152
|
}
|
|
153
|
+
export function buildBranchCloneArgs(url, targetDir, branch) {
|
|
154
|
+
return ['clone', '--depth', '1', '--branch', branch, '--', url, targetDir];
|
|
155
|
+
}
|
|
266
156
|
/**
|
|
267
157
|
* Normalize a git URL into a comparable form.
|
|
268
158
|
*
|
|
@@ -320,28 +210,16 @@ export function normalizeGitUrlForCompare(url) {
|
|
|
320
210
|
* remote means for its threat model — for cloneOrPull, a missing remote
|
|
321
211
|
* on an existing clone is treated as a refuse-to-pull condition.
|
|
322
212
|
*/
|
|
323
|
-
export function getRemoteOriginUrl(cwd) {
|
|
324
|
-
|
|
325
|
-
const
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
stdout += chunk;
|
|
334
|
-
});
|
|
335
|
-
proc.on('close', (code) => {
|
|
336
|
-
if (code === 0 && stdout.trim()) {
|
|
337
|
-
resolve(stdout.trim());
|
|
338
|
-
}
|
|
339
|
-
else {
|
|
340
|
-
resolve(null);
|
|
341
|
-
}
|
|
342
|
-
});
|
|
343
|
-
proc.on('error', () => resolve(null));
|
|
344
|
-
});
|
|
213
|
+
export async function getRemoteOriginUrl(cwd, timeoutMs) {
|
|
214
|
+
try {
|
|
215
|
+
const stdout = await runGit(['config', '--get', 'remote.origin.url'], cwd, { timeoutMs });
|
|
216
|
+
return stdout.trim() || null;
|
|
217
|
+
}
|
|
218
|
+
catch (error) {
|
|
219
|
+
if (error.message.includes('timed out'))
|
|
220
|
+
throw error;
|
|
221
|
+
return null;
|
|
222
|
+
}
|
|
345
223
|
}
|
|
346
224
|
/**
|
|
347
225
|
* Verify that an existing clone's `remote.origin.url` matches the requested
|
|
@@ -357,8 +235,8 @@ export function getRemoteOriginUrl(cwd) {
|
|
|
357
235
|
* Exported so the comparison logic is testable in isolation against any
|
|
358
236
|
* tmpdir-based fixture, without needing to populate CLONE_ROOT.
|
|
359
237
|
*/
|
|
360
|
-
export async function assertRemoteMatchesRequestedUrl(targetDir, requestedUrl) {
|
|
361
|
-
const remoteUrl = await getRemoteOriginUrl(targetDir);
|
|
238
|
+
export async function assertRemoteMatchesRequestedUrl(targetDir, requestedUrl, timeoutMs) {
|
|
239
|
+
const remoteUrl = await getRemoteOriginUrl(targetDir, timeoutMs);
|
|
362
240
|
if (remoteUrl === null) {
|
|
363
241
|
throw new Error(`Existing clone at ${targetDir} has no remote.origin — refusing to pull`);
|
|
364
242
|
}
|
|
@@ -400,37 +278,179 @@ export async function cloneOrPull(url, targetDir, onProgress, options) {
|
|
|
400
278
|
// subprocess sink. The same `safeTarget` is used for every downstream
|
|
401
279
|
// path operation — no reassignment that the analyzer could lose track of.
|
|
402
280
|
//
|
|
403
|
-
//
|
|
404
|
-
//
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
281
|
+
// The lexical check runs before filesystem creation; realpath and symlink
|
|
282
|
+
// checks below run before pull/clone and again after clone completes.
|
|
283
|
+
const cloneRoot = path.resolve(options?.allowedCloneRoot ?? CLONE_ROOT);
|
|
284
|
+
const expectedRepoName = options?.expectedRepoName;
|
|
285
|
+
if (expectedRepoName !== undefined && expectedRepoName !== extractRepoName(url)) {
|
|
286
|
+
throw new Error(`Clone target repo name ${expectedRepoName} does not match requested URL`);
|
|
287
|
+
}
|
|
409
288
|
const safeTarget = path.resolve(targetDir);
|
|
410
|
-
|
|
289
|
+
if (expectedRepoName !== undefined && path.basename(safeTarget) !== expectedRepoName) {
|
|
290
|
+
throw new Error(`Clone target basename must match repository name ${expectedRepoName}`);
|
|
291
|
+
}
|
|
292
|
+
const rel = path.relative(cloneRoot, safeTarget);
|
|
411
293
|
if (rel === '' || rel.startsWith('..') || path.isAbsolute(rel)) {
|
|
412
|
-
throw new Error(`Clone target must be a subdirectory of ${
|
|
294
|
+
throw new Error(`Clone target must be a subdirectory of ${cloneRoot}`);
|
|
413
295
|
}
|
|
414
296
|
// Always validate the requested URL — the prior shape only ran this in
|
|
415
297
|
// the code path where the repo was cloned. Now it runs unconditionally,
|
|
416
298
|
// preventing SSRF / blocked-host bypasses even when targetDir already exists.
|
|
417
|
-
|
|
299
|
+
if (options?.allowAutoSyncSsh)
|
|
300
|
+
validateAutoSyncRemoteUrl(url);
|
|
301
|
+
else
|
|
302
|
+
validateGitUrl(url);
|
|
303
|
+
await fs.mkdir(cloneRoot, { recursive: true });
|
|
304
|
+
if (options?.allowedCloneRoot) {
|
|
305
|
+
await assertDirectoryOwnerAndPermissions(cloneRoot);
|
|
306
|
+
}
|
|
307
|
+
await assertNoSymlinkPath(cloneRoot, safeTarget, Boolean(options?.allowedCloneRoot));
|
|
308
|
+
await fs.mkdir(path.dirname(safeTarget), { recursive: true });
|
|
309
|
+
await assertNoSymlinkPath(cloneRoot, safeTarget, Boolean(options?.allowedCloneRoot));
|
|
310
|
+
await assertPreRealpathContainment(cloneRoot, safeTarget);
|
|
418
311
|
const exists = await fs.access(path.join(safeTarget, '.git')).then(() => true, () => false);
|
|
312
|
+
const targetExists = await fs.access(safeTarget).then(() => true, () => false);
|
|
419
313
|
if (exists) {
|
|
314
|
+
if (options?.allowedCloneRoot) {
|
|
315
|
+
await assertNoSymlinkPath(cloneRoot, path.join(safeTarget, '.git'), true);
|
|
316
|
+
}
|
|
317
|
+
await assertPostRealpathContainment(cloneRoot, safeTarget);
|
|
420
318
|
// Confirm the existing clone is actually the same repository the caller
|
|
421
319
|
// requested. Without this check, a pull would silently succeed against
|
|
422
320
|
// whatever remote the dir was originally cloned from.
|
|
423
|
-
await assertRemoteMatchesRequestedUrl(safeTarget, url);
|
|
321
|
+
await assertRemoteMatchesRequestedUrl(safeTarget, url, options?.timeoutMs);
|
|
424
322
|
onProgress?.({ phase: 'pulling', message: 'Pulling latest changes...' });
|
|
425
|
-
|
|
323
|
+
const runGitImpl = options?.runGitForTest ?? runGit;
|
|
324
|
+
if (options?.branch) {
|
|
325
|
+
if (!options.overwriteLocalChanges) {
|
|
326
|
+
const status = await runGitImpl(['status', '--porcelain'], safeTarget, {
|
|
327
|
+
token: options?.token,
|
|
328
|
+
url,
|
|
329
|
+
timeoutMs: options?.timeoutMs,
|
|
330
|
+
});
|
|
331
|
+
if (status.trim()) {
|
|
332
|
+
throw new Error(`Refusing to update ${safeTarget}: local changes detected. Set overwrite_local_changes: true to overwrite them.`);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
await runGitImpl([
|
|
336
|
+
'fetch',
|
|
337
|
+
'--depth',
|
|
338
|
+
'1',
|
|
339
|
+
'origin',
|
|
340
|
+
`refs/heads/${options.branch}:refs/remotes/origin/${options.branch}`,
|
|
341
|
+
], safeTarget, {
|
|
342
|
+
token: options?.token,
|
|
343
|
+
url,
|
|
344
|
+
timeoutMs: options?.timeoutMs,
|
|
345
|
+
});
|
|
346
|
+
await runGitImpl([
|
|
347
|
+
'checkout',
|
|
348
|
+
...(options.overwriteLocalChanges ? ['--force'] : []),
|
|
349
|
+
'-B',
|
|
350
|
+
options.branch,
|
|
351
|
+
`origin/${options.branch}`,
|
|
352
|
+
], safeTarget, {
|
|
353
|
+
token: options?.token,
|
|
354
|
+
url,
|
|
355
|
+
timeoutMs: options?.timeoutMs,
|
|
356
|
+
});
|
|
357
|
+
if (options.overwriteLocalChanges) {
|
|
358
|
+
// `checkout --force` rewrites tracked files only, so untracked sources
|
|
359
|
+
// left by an operator or an earlier branch survive and then get indexed
|
|
360
|
+
// as if they were part of the remote commit. Deliberately no `-x`/`-X`:
|
|
361
|
+
// ignored paths must survive, and `-e /.gitnexus` is belt-and-braces
|
|
362
|
+
// because `.git/info/exclude` is skipped on a read-only storage mount
|
|
363
|
+
// and a freshly cloned repo may not have been analyzed yet at all.
|
|
364
|
+
await runGitImpl(['clean', '--force', '-d', '-e', '/.gitnexus'], safeTarget, {
|
|
365
|
+
token: options?.token,
|
|
366
|
+
url,
|
|
367
|
+
timeoutMs: options?.timeoutMs,
|
|
368
|
+
});
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
else {
|
|
372
|
+
await runGitImpl(['pull', '--ff-only'], safeTarget, {
|
|
373
|
+
token: options?.token,
|
|
374
|
+
url,
|
|
375
|
+
timeoutMs: options?.timeoutMs,
|
|
376
|
+
});
|
|
377
|
+
}
|
|
426
378
|
}
|
|
427
379
|
else {
|
|
428
|
-
await fs.
|
|
380
|
+
if (targetExists && (await fs.readdir(safeTarget)).length > 0) {
|
|
381
|
+
throw new Error(`Clone target already exists but is not a git repository: ${safeTarget}`);
|
|
382
|
+
}
|
|
429
383
|
onProgress?.({ phase: 'cloning', message: `Cloning ${url}...` });
|
|
430
|
-
|
|
384
|
+
try {
|
|
385
|
+
const runGitImpl = options?.runGitForTest ?? runGit;
|
|
386
|
+
const cloneArgs = options?.branch
|
|
387
|
+
? buildBranchCloneArgs(url, safeTarget, options.branch)
|
|
388
|
+
: buildCloneArgs(url, safeTarget);
|
|
389
|
+
await runGitImpl(cloneArgs, undefined, {
|
|
390
|
+
token: options?.token,
|
|
391
|
+
url,
|
|
392
|
+
timeoutMs: options?.timeoutMs,
|
|
393
|
+
});
|
|
394
|
+
await assertPostRealpathContainment(cloneRoot, safeTarget);
|
|
395
|
+
}
|
|
396
|
+
catch (err) {
|
|
397
|
+
if (options?.quarantineRoot) {
|
|
398
|
+
const partialExists = await fs.access(safeTarget).then(() => true, () => false);
|
|
399
|
+
if (partialExists) {
|
|
400
|
+
try {
|
|
401
|
+
await quarantineAutoSyncPartial(safeTarget, options.quarantineRoot);
|
|
402
|
+
}
|
|
403
|
+
catch (quarantineError) {
|
|
404
|
+
throw new AggregateError([err, quarantineError], `Clone failed and partial checkout could not be quarantined: ${safeTarget}`);
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
throw err;
|
|
409
|
+
}
|
|
431
410
|
}
|
|
432
411
|
return safeTarget;
|
|
433
412
|
}
|
|
413
|
+
async function assertPreRealpathContainment(root, target) {
|
|
414
|
+
const realRoot = await fs.realpath(root);
|
|
415
|
+
const realParent = await fs.realpath(path.dirname(target));
|
|
416
|
+
const parentRel = path.relative(realRoot, realParent);
|
|
417
|
+
if (parentRel.startsWith('..') || path.isAbsolute(parentRel)) {
|
|
418
|
+
throw new Error(`Clone target parent must resolve inside ${root}`);
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
async function assertPostRealpathContainment(root, target) {
|
|
422
|
+
const realRoot = await fs.realpath(root);
|
|
423
|
+
const realTarget = await fs.realpath(target);
|
|
424
|
+
const rel = path.relative(realRoot, realTarget);
|
|
425
|
+
if (rel === '' || rel.startsWith('..') || path.isAbsolute(rel)) {
|
|
426
|
+
throw new Error(`Clone target must resolve inside ${root}`);
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
async function assertNoSymlinkPath(root, target, verifyOwnership = false) {
|
|
430
|
+
const resolvedRoot = path.resolve(root);
|
|
431
|
+
const resolvedTarget = path.resolve(target);
|
|
432
|
+
const relativeTarget = path.relative(resolvedRoot, resolvedTarget);
|
|
433
|
+
if (relativeTarget.startsWith('..') || path.isAbsolute(relativeTarget))
|
|
434
|
+
return;
|
|
435
|
+
let current = resolvedRoot;
|
|
436
|
+
for (const segment of relativeTarget.split(path.sep).filter(Boolean)) {
|
|
437
|
+
current = path.join(current, segment);
|
|
438
|
+
let stat;
|
|
439
|
+
try {
|
|
440
|
+
stat = await fs.lstat(current);
|
|
441
|
+
}
|
|
442
|
+
catch (err) {
|
|
443
|
+
if (err.code === 'ENOENT')
|
|
444
|
+
break;
|
|
445
|
+
throw err;
|
|
446
|
+
}
|
|
447
|
+
if (stat.isSymbolicLink()) {
|
|
448
|
+
throw new Error(`Refusing symlink in clone target path: ${current}`);
|
|
449
|
+
}
|
|
450
|
+
if (verifyOwnership)
|
|
451
|
+
await assertDirectoryOwnerAndPermissions(current);
|
|
452
|
+
}
|
|
453
|
+
}
|
|
434
454
|
/**
|
|
435
455
|
* Hosts the per-request GitHub PAT may be sent to. Exported so the
|
|
436
456
|
* /api/analyze boundary check and this injection-site check share one
|
|
@@ -527,11 +547,10 @@ function warnIfCleartextCredential(url) {
|
|
|
527
547
|
}
|
|
528
548
|
}
|
|
529
549
|
/**
|
|
530
|
-
* Build the spawn env for `git
|
|
531
|
-
*
|
|
532
|
-
*
|
|
533
|
-
*
|
|
534
|
-
* any existing `GIT_CONFIG_COUNT` rather than overwriting it. Exported for
|
|
550
|
+
* Build the spawn env for managed `git` commands. Suppresses credential
|
|
551
|
+
* prompts, disables repository hooks, and injects at most one host-scoped
|
|
552
|
+
* Authorization header via the `GIT_CONFIG_*` env protocol (git ≥2.31).
|
|
553
|
+
* Managed settings append after any existing GIT_CONFIG_COUNT. Exported for
|
|
535
554
|
* unit tests.
|
|
536
555
|
*/
|
|
537
556
|
export function buildGitEnv(baseEnv, options) {
|
|
@@ -550,18 +569,20 @@ export function buildGitEnv(baseEnv, options) {
|
|
|
550
569
|
GIT_TRACE_PACKET: undefined,
|
|
551
570
|
GIT_CURL_VERBOSE: undefined,
|
|
552
571
|
};
|
|
572
|
+
const existing = Number.parseInt(env.GIT_CONFIG_COUNT ?? '', 10);
|
|
573
|
+
let next = Number.isInteger(existing) && existing > 0 ? existing : 0;
|
|
574
|
+
env[`GIT_CONFIG_KEY_${next}`] = 'core.hooksPath';
|
|
575
|
+
env[`GIT_CONFIG_VALUE_${next}`] = os.devNull;
|
|
576
|
+
next += 1;
|
|
553
577
|
const credential = resolveGitCredential(options);
|
|
554
578
|
const key = options?.url ? buildExtraHeaderKey(options.url) : undefined;
|
|
555
579
|
if (credential && key) {
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
const base = Number.isInteger(existing) && existing > 0 ? existing : 0;
|
|
560
|
-
env.GIT_CONFIG_COUNT = String(base + 1);
|
|
561
|
-
env[`GIT_CONFIG_KEY_${base}`] = key;
|
|
562
|
-
env[`GIT_CONFIG_VALUE_${base}`] = `Authorization: Basic ${credential}`;
|
|
580
|
+
env[`GIT_CONFIG_KEY_${next}`] = key;
|
|
581
|
+
env[`GIT_CONFIG_VALUE_${next}`] = `Authorization: Basic ${credential}`;
|
|
582
|
+
next += 1;
|
|
563
583
|
warnIfCleartextCredential(options?.url);
|
|
564
584
|
}
|
|
585
|
+
env.GIT_CONFIG_COUNT = String(next);
|
|
565
586
|
return env;
|
|
566
587
|
}
|
|
567
588
|
// `options` carries the inputs the credential resolver needs: a per-request
|
|
@@ -569,30 +590,63 @@ export function buildGitEnv(baseEnv, options) {
|
|
|
569
590
|
// host-scoped Authorization header (GitHub PAT for github.com, else the
|
|
570
591
|
// server's AZURE_DEVOPS_PAT for Azure hosts) via the GIT_CONFIG_* protocol —
|
|
571
592
|
// never in argv. See resolveGitCredential / buildExtraHeaderKey.
|
|
572
|
-
function runGit(args, cwd, options) {
|
|
593
|
+
export function runGit(args, cwd, options) {
|
|
573
594
|
return new Promise((resolve, reject) => {
|
|
574
|
-
const
|
|
595
|
+
const spawnGit = options?.spawnForTest ?? spawn;
|
|
596
|
+
const proc = spawnGit('git', args, {
|
|
575
597
|
cwd,
|
|
576
598
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
577
599
|
windowsHide: true,
|
|
578
600
|
env: buildGitEnv(process.env, options),
|
|
579
601
|
});
|
|
602
|
+
let stdout = '';
|
|
580
603
|
let stderr = '';
|
|
604
|
+
let settled = false;
|
|
605
|
+
let timedOut = false;
|
|
606
|
+
let killTimer;
|
|
607
|
+
const finish = (fn) => {
|
|
608
|
+
if (settled)
|
|
609
|
+
return;
|
|
610
|
+
settled = true;
|
|
611
|
+
if (timer)
|
|
612
|
+
clearTimeout(timer);
|
|
613
|
+
if (killTimer)
|
|
614
|
+
clearTimeout(killTimer);
|
|
615
|
+
fn();
|
|
616
|
+
};
|
|
617
|
+
const timer = options?.timeoutMs && options.timeoutMs > 0
|
|
618
|
+
? setTimeout(() => {
|
|
619
|
+
timedOut = true;
|
|
620
|
+
proc.kill('SIGTERM');
|
|
621
|
+
killTimer = setTimeout(() => {
|
|
622
|
+
proc.kill('SIGKILL');
|
|
623
|
+
finish(() => reject(new Error(`git ${args[0]} timed out after ${options.timeoutMs}ms`)));
|
|
624
|
+
}, options.timeoutKillGraceMs ?? 1_000);
|
|
625
|
+
}, options.timeoutMs)
|
|
626
|
+
: undefined;
|
|
627
|
+
proc.stdout?.on('data', (chunk) => {
|
|
628
|
+
stdout += chunk;
|
|
629
|
+
});
|
|
581
630
|
proc.stderr.on('data', (chunk) => {
|
|
582
631
|
stderr += chunk;
|
|
583
632
|
});
|
|
584
633
|
proc.on('close', (code) => {
|
|
634
|
+
if (timedOut) {
|
|
635
|
+
finish(() => reject(new Error(`git ${args[0]} timed out after ${options?.timeoutMs}ms`)));
|
|
636
|
+
return;
|
|
637
|
+
}
|
|
585
638
|
if (code === 0)
|
|
586
|
-
resolve();
|
|
639
|
+
finish(() => resolve(stdout));
|
|
587
640
|
else {
|
|
588
641
|
// Log full stderr internally but don't expose it to API callers (SSRF mitigation)
|
|
589
642
|
if (stderr.trim())
|
|
590
643
|
logger.error(`git ${args[0]} stderr: ${stderr.trim()}`);
|
|
591
|
-
reject(new Error(`git ${args[0]} failed (exit code ${code})`));
|
|
644
|
+
finish(() => reject(new Error(`git ${args[0]} failed (exit code ${code})`)));
|
|
592
645
|
}
|
|
593
646
|
});
|
|
594
647
|
proc.on('error', (err) => {
|
|
595
|
-
reject(new Error(`Failed to spawn git: ${err.message}`));
|
|
648
|
+
finish(() => reject(new Error(`Failed to spawn git: ${err.message}`)));
|
|
596
649
|
});
|
|
597
650
|
});
|
|
598
651
|
}
|
|
652
|
+
export const runGitForTest = runGit;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/** Hard cap on pattern length — unchanged from the literal-only era. */
|
|
2
|
+
export declare const GREP_PATTERN_MAX_LENGTH = 200;
|
|
3
|
+
/** Wall-clock budget for one /api/grep call; parent terminate()s the scan worker. */
|
|
4
|
+
export declare const GREP_TIME_BUDGET_MS = 5000;
|
|
5
|
+
export declare const GREP_DEFAULT_LIMIT = 50;
|
|
6
|
+
export declare const GREP_MAX_LIMIT = 200;
|
|
7
|
+
export interface ParsedGrepQuery {
|
|
8
|
+
regex: RegExp;
|
|
9
|
+
/** Lowercased path substring; '' disables path filtering. */
|
|
10
|
+
fileFilter: string;
|
|
11
|
+
limit: number;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Parse /api/grep query parameters into a ready-to-use regex + filters.
|
|
15
|
+
* Throws BadRequestError (mapped to HTTP 400 by statusFromError) on
|
|
16
|
+
* missing/over-long patterns or invalid regex syntax.
|
|
17
|
+
*/
|
|
18
|
+
export declare function parseGrepQuery(query: Record<string, unknown>): ParsedGrepQuery;
|