gitnexus 1.6.8-rc.2 → 1.6.8-rc.21
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 +20 -0
- 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 +4 -0
- package/dist/_shared/scope-resolution/symbol-definition.d.ts.map +1 -1
- package/dist/cli/ai-context.js +1 -0
- package/dist/cli/analyze-config.js +40 -0
- package/dist/cli/analyze.d.ts +22 -0
- package/dist/cli/analyze.js +36 -6
- package/dist/cli/clean.d.ts +1 -0
- package/dist/cli/clean.js +43 -1
- package/dist/cli/eval-server.js +43 -0
- package/dist/cli/help-i18n.js +11 -0
- package/dist/cli/i18n/en.d.ts +13 -0
- package/dist/cli/i18n/en.js +13 -0
- package/dist/cli/i18n/resources.d.ts +26 -0
- package/dist/cli/i18n/zh-CN.d.ts +13 -0
- package/dist/cli/i18n/zh-CN.js +13 -0
- package/dist/cli/index.js +19 -0
- package/dist/cli/list.js +12 -0
- package/dist/cli/optional-grammars.d.ts +6 -8
- package/dist/cli/optional-grammars.js +8 -11
- package/dist/cli/skill-gen.js +1 -0
- package/dist/cli/status.js +26 -5
- package/dist/cli/tool.d.ts +12 -1
- package/dist/cli/tool.js +41 -1
- package/dist/core/embeddings/embedder.js +4 -0
- package/dist/core/embeddings/embedding-pipeline.js +27 -16
- package/dist/core/embeddings/onnxruntime-common-resolver.d.ts +6 -0
- package/dist/core/embeddings/onnxruntime-common-resolver.js +130 -0
- package/dist/core/graph/import-cycles.d.ts +10 -0
- package/dist/core/graph/import-cycles.js +103 -0
- package/dist/core/group/extractors/grpc-patterns/proto.js +10 -6
- package/dist/core/group/extractors/http-patterns/java.js +3 -50
- package/dist/core/group/extractors/http-patterns/kotlin.js +7 -7
- package/dist/core/group/extractors/include-extractor.js +7 -7
- package/dist/core/ingestion/cfg/cfg-builder.d.ts +64 -0
- package/dist/core/ingestion/cfg/cfg-builder.js +129 -0
- package/dist/core/ingestion/cfg/collect.d.ts +30 -0
- package/dist/core/ingestion/cfg/collect.js +34 -0
- package/dist/core/ingestion/cfg/control-flow-context.d.ts +97 -0
- package/dist/core/ingestion/cfg/control-flow-context.js +113 -0
- package/dist/core/ingestion/cfg/emit.d.ts +144 -0
- package/dist/core/ingestion/cfg/emit.js +315 -0
- package/dist/core/ingestion/cfg/reaching-defs.d.ts +90 -0
- package/dist/core/ingestion/cfg/reaching-defs.js +364 -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 +231 -0
- package/dist/core/ingestion/cfg/types.js +13 -0
- package/dist/core/ingestion/cfg/visitors/typescript-harvest.d.ts +144 -0
- package/dist/core/ingestion/cfg/visitors/typescript-harvest.js +1032 -0
- package/dist/core/ingestion/cfg/visitors/typescript.d.ts +66 -0
- package/dist/core/ingestion/cfg/visitors/typescript.js +584 -0
- package/dist/core/ingestion/language-provider.d.ts +36 -2
- package/dist/core/ingestion/languages/c-cpp.js +11 -4
- package/dist/core/ingestion/languages/cpp/arity-metadata.js +6 -2
- package/dist/core/ingestion/languages/cpp/captures.js +24 -1
- package/dist/core/ingestion/languages/cpp/query.js +23 -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 +5 -1
- package/dist/core/ingestion/languages/typescript.js +5 -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/parsing-processor.js +22 -0
- package/dist/core/ingestion/pipeline-phases/parse-impl.js +18 -1
- package/dist/core/ingestion/pipeline-phases/routes.js +64 -14
- package/dist/core/ingestion/pipeline.d.ts +57 -0
- package/dist/core/ingestion/route-extractors/spring-shared.d.ts +50 -0
- package/dist/core/ingestion/route-extractors/spring-shared.js +80 -0
- package/dist/core/ingestion/route-extractors/spring.d.ts +35 -0
- package/dist/core/ingestion/route-extractors/spring.js +136 -0
- package/dist/core/ingestion/scope-extractor.js +3 -0
- package/dist/core/ingestion/scope-resolution/passes/free-call-fallback.js +12 -0
- package/dist/core/ingestion/scope-resolution/passes/receiver-bound-calls.js +145 -24
- package/dist/core/ingestion/scope-resolution/pipeline/phase.js +6 -0
- package/dist/core/ingestion/scope-resolution/pipeline/reconcile-ownership.js +43 -3
- package/dist/core/ingestion/scope-resolution/pipeline/run.d.ts +21 -0
- package/dist/core/ingestion/scope-resolution/pipeline/run.js +207 -0
- package/dist/core/ingestion/scope-resolution/resolution-outcome.d.ts +1 -1
- package/dist/core/ingestion/taint/emit.d.ts +124 -0
- package/dist/core/ingestion/taint/emit.js +204 -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 +664 -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 +94 -23
- package/dist/core/ingestion/taint/source-sink-config.js +11 -11
- 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/typescript-model.d.ts +38 -0
- package/dist/core/ingestion/taint/typescript-model.js +102 -0
- package/dist/core/ingestion/utils/method-props.js +1 -0
- package/dist/core/ingestion/workers/clone-safety.d.ts +109 -0
- package/dist/core/ingestion/workers/clone-safety.js +465 -0
- package/dist/core/ingestion/workers/parse-worker.d.ts +11 -0
- package/dist/core/ingestion/workers/parse-worker.js +70 -53
- package/dist/core/ingestion/workers/post-result.d.ts +22 -0
- package/dist/core/ingestion/workers/post-result.js +87 -0
- package/dist/core/ingestion/workers/result-merge.d.ts +20 -0
- package/dist/core/ingestion/workers/result-merge.js +43 -0
- package/dist/core/ingestion/workers/worker-pool.d.ts +15 -0
- package/dist/core/ingestion/workers/worker-pool.js +33 -16
- package/dist/core/lbug/lbug-adapter.d.ts +19 -0
- package/dist/core/lbug/lbug-adapter.js +56 -1
- package/dist/core/run-analyze.d.ts +87 -0
- package/dist/core/run-analyze.js +280 -25
- package/dist/core/tree-sitter/parser-loader.js +5 -4
- package/dist/core/tree-sitter/vendored-grammars.d.ts +39 -0
- package/dist/core/tree-sitter/vendored-grammars.js +57 -0
- package/dist/mcp/core/embedder.js +4 -0
- package/dist/mcp/local/local-backend.d.ts +86 -2
- package/dist/mcp/local/local-backend.js +657 -27
- package/dist/mcp/resources.js +1 -0
- package/dist/mcp/tools.d.ts +9 -0
- package/dist/mcp/tools.js +109 -0
- package/dist/server/analyze-launch.d.ts +29 -0
- package/dist/server/analyze-launch.js +137 -0
- package/dist/server/analyze-upload.d.ts +33 -0
- package/dist/server/analyze-upload.js +123 -0
- package/dist/server/analyze-worker-ipc.d.ts +58 -0
- package/dist/server/analyze-worker-ipc.js +16 -0
- package/dist/server/analyze-worker.d.ts +17 -1
- package/dist/server/analyze-worker.js +7 -1
- package/dist/server/api.js +46 -140
- package/dist/server/git-clone.d.ts +1 -0
- package/dist/server/git-clone.js +1 -1
- package/dist/server/middleware.d.ts +11 -0
- package/dist/server/middleware.js +27 -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 +31 -0
- package/dist/server/upload-paths.js +51 -0
- package/dist/server/upload-sweep.d.ts +20 -0
- package/dist/server/upload-sweep.js +57 -0
- package/dist/storage/branch-index.d.ts +52 -0
- package/dist/storage/branch-index.js +65 -0
- package/dist/storage/git.d.ts +11 -0
- package/dist/storage/git.js +28 -0
- package/dist/storage/parse-cache.d.ts +22 -1
- package/dist/storage/parse-cache.js +32 -10
- package/dist/storage/repo-manager.d.ts +122 -10
- package/dist/storage/repo-manager.js +162 -21
- package/hooks/antigravity/gitnexus-antigravity-hook.cjs +42 -8
- package/hooks/claude/gitnexus-hook.cjs +36 -8
- package/hooks/claude/hook-db-lock-probe.cjs +123 -2
- package/package.json +4 -2
- package/scripts/assert-publish-grammar-coverage.cjs +31 -0
- package/scripts/build-tree-sitter-grammars.cjs +16 -10
- package/skills/gitnexus-guide.md +11 -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-ay4LD70X.js} +1 -1
- package/web/assets/{architectureDiagram-UL44E2DR-D1EXI0zA.js → architectureDiagram-UL44E2DR-Dc-viYhd.js} +1 -1
- package/web/assets/{chunk-LCXTWHL2-Dfmux4m1.js → chunk-LCXTWHL2-4rpojOyj.js} +1 -1
- package/web/assets/{chunk-RG4AUYOV-CAkzcoRj.js → chunk-RG4AUYOV-BnOy944n.js} +1 -1
- package/web/assets/{classDiagram-KGZ6W3CR-Bv93af_b.js → classDiagram-KGZ6W3CR-Bsgpy98Q.js} +1 -1
- package/web/assets/{classDiagram-v2-72OJOZXJ-CLfEqHUa.js → classDiagram-v2-72OJOZXJ-D5atDGjc.js} +1 -1
- package/web/assets/{diagram-3NCE3AQN-BqAtKUpW.js → diagram-3NCE3AQN-Cz1OEMVi.js} +1 -1
- package/web/assets/{diagram-GF46GFSD-CaBG6n6o.js → diagram-GF46GFSD-CWYwzfP8.js} +1 -1
- package/web/assets/{diagram-QXG6HAR7-CZ-O3rcV.js → diagram-QXG6HAR7-DtBObr8L.js} +1 -1
- package/web/assets/{diagram-WEQXMOUZ-P4lSL4GH.js → diagram-WEQXMOUZ-BUV44Ov_.js} +1 -1
- package/web/assets/{erDiagram-L5TCEMPS-QN2eEP1e.js → erDiagram-L5TCEMPS-Ds9s-sRF.js} +1 -1
- package/web/assets/{flowDiagram-H6V6AXG4-CXbXImlN.js → flowDiagram-H6V6AXG4-CupStHQb.js} +1 -1
- package/web/assets/index-COMMmbxW.css +2 -0
- package/web/assets/{index-CG6q8eTs.js → index-Do8AE5yF.js} +85 -85
- package/web/assets/{infoDiagram-3YFTVSEB-DmLICZx1.js → infoDiagram-3YFTVSEB-DPTXSbgu.js} +1 -1
- package/web/assets/{ishikawaDiagram-BNXS4ZKH-6LKOvBfp.js → ishikawaDiagram-BNXS4ZKH-CeK-Qhet.js} +1 -1
- package/web/assets/{kanban-definition-75IXJCU3-DgDi9oJT.js → kanban-definition-75IXJCU3-D4tx6QQ-.js} +1 -1
- package/web/assets/{mindmap-definition-2TDM6QVE-CwR5sBB-.js → mindmap-definition-2TDM6QVE-Bcto_kGa.js} +1 -1
- package/web/assets/{pieDiagram-CU6KROY3-By8g6f6B.js → pieDiagram-CU6KROY3-DPIeBTG6.js} +1 -1
- package/web/assets/{requirementDiagram-JXO7QTGE-7oDcJ1_J.js → requirementDiagram-JXO7QTGE-DDGXjVKO.js} +1 -1
- package/web/assets/{sequenceDiagram-VS2MUI6T-FySeKCUy.js → sequenceDiagram-VS2MUI6T-BzDqpcVW.js} +1 -1
- package/web/assets/{stateDiagram-7D4R322I-CB2nABwH.js → stateDiagram-7D4R322I-Bx__als3.js} +1 -1
- package/web/assets/{stateDiagram-v2-36443NZ5-COBGd2RL.js → stateDiagram-v2-36443NZ5-QqOh2yO-.js} +1 -1
- package/web/assets/{timeline-definition-O6YCAMPW-Ds2CnVZK.js → timeline-definition-O6YCAMPW-C1eogTOG.js} +1 -1
- package/web/assets/{vennDiagram-MWXL3ELB-DUIEwXWp.js → vennDiagram-MWXL3ELB-D20F4rSW.js} +1 -1
- package/web/assets/{wardleyDiagram-CUQ6CDDI-DEiFPQih.js → wardleyDiagram-CUQ6CDDI-BeBHg7ST.js} +1 -1
- package/web/assets/{xychartDiagram-N2JHSOCM-BUbayhST.js → xychartDiagram-N2JHSOCM-CdOWeoNI.js} +1 -1
- package/web/index.html +2 -2
- package/scripts/materialize-vendor-grammars.cjs +0 -97
- package/web/assets/index-BKWA-m7o.css +0 -2
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Taint-site safety validation (#2083 M3 U1, plan KTD2).
|
|
3
|
+
*
|
|
4
|
+
* Mirrors `hasEmitSafeFacts` (cfg/emit.ts): an untrusted `cfgSideChannel`
|
|
5
|
+
* element — possibly from a corrupted durable parsedfile store — must never
|
|
6
|
+
* crash the taint pass or fabricate matches from out-of-range indices. The
|
|
7
|
+
* degradation contract is per-FUNCTION and one-directional: a CFG whose sites
|
|
8
|
+
* fail this check is SKIPPED FOR TAINT ONLY — the BasicBlock/CFG layer and
|
|
9
|
+
* the REACHING_DEF projection (guarded by their own checks) are unaffected.
|
|
10
|
+
*
|
|
11
|
+
* Checked: exactly the indices the taint matcher dereferences — binding
|
|
12
|
+
* indices (`receiver`/`object`/`resultDefs`/arg occurrences) against the
|
|
13
|
+
* function's binding table, and intra-statement site references (`parent`
|
|
14
|
+
* site / via-tags) against the OWNING statement's `sites` array. Site
|
|
15
|
+
* references are statement-local by construction (each statement's
|
|
16
|
+
* FactAccumulator starts at index 0); a cross-statement reference is
|
|
17
|
+
* corruption, not a feature.
|
|
18
|
+
*
|
|
19
|
+
* Lives in `taint/` (not cfg/emit.ts): U4's taint emit path is the only
|
|
20
|
+
* consumer, and the guard must evolve with the matcher that dereferences
|
|
21
|
+
* these fields.
|
|
22
|
+
*/
|
|
23
|
+
import type { FunctionCfg } from '../cfg/types.js';
|
|
24
|
+
/**
|
|
25
|
+
* Whether a structurally-valid CFG's M3 `sites` annotations are safe to feed
|
|
26
|
+
* to the taint matcher/propagator. `true` when no statement carries sites
|
|
27
|
+
* (pre-M3 channel, or no calls) — absence is the well-formed empty case.
|
|
28
|
+
*/
|
|
29
|
+
export declare const hasTaintSafeSites: (cfg: FunctionCfg) => boolean;
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
const SITE_KINDS = new Set(['call', 'new', 'member-read']);
|
|
2
|
+
/**
|
|
3
|
+
* Whether a structurally-valid CFG's M3 `sites` annotations are safe to feed
|
|
4
|
+
* to the taint matcher/propagator. `true` when no statement carries sites
|
|
5
|
+
* (pre-M3 channel, or no calls) — absence is the well-formed empty case.
|
|
6
|
+
*/
|
|
7
|
+
export const hasTaintSafeSites = (cfg) => {
|
|
8
|
+
// Sites carry binding indices — a channel with sites but no binding table
|
|
9
|
+
// has nothing to range-check them against: reject (checked per statement).
|
|
10
|
+
const bindingCount = Array.isArray(cfg.bindings) ? cfg.bindings.length : -1;
|
|
11
|
+
for (const block of cfg.blocks) {
|
|
12
|
+
const stmts = block.statements;
|
|
13
|
+
if (stmts === undefined)
|
|
14
|
+
continue;
|
|
15
|
+
if (!Array.isArray(stmts))
|
|
16
|
+
return false;
|
|
17
|
+
for (const s of stmts) {
|
|
18
|
+
if (s?.sites === undefined)
|
|
19
|
+
continue;
|
|
20
|
+
if (bindingCount < 0)
|
|
21
|
+
return false;
|
|
22
|
+
if (!isSafeSiteList(s.sites, bindingCount))
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return true;
|
|
27
|
+
};
|
|
28
|
+
const isSafeSiteList = (sites, bindingCount) => {
|
|
29
|
+
if (!Array.isArray(sites))
|
|
30
|
+
return false;
|
|
31
|
+
const siteCount = sites.length;
|
|
32
|
+
const bindingInRange = (i) => Number.isInteger(i) && i >= 0 && i < bindingCount;
|
|
33
|
+
const siteInRange = (i) => Number.isInteger(i) && i >= 0 && i < siteCount;
|
|
34
|
+
for (const site of sites) {
|
|
35
|
+
if (site === null || typeof site !== 'object')
|
|
36
|
+
return false;
|
|
37
|
+
if (typeof site.kind !== 'string' || !SITE_KINDS.has(site.kind))
|
|
38
|
+
return false;
|
|
39
|
+
if (site.callee !== undefined && typeof site.callee !== 'string')
|
|
40
|
+
return false;
|
|
41
|
+
if (site.receiver !== undefined && !bindingInRange(site.receiver))
|
|
42
|
+
return false;
|
|
43
|
+
if (site.requireArg !== undefined && typeof site.requireArg !== 'string')
|
|
44
|
+
return false;
|
|
45
|
+
if (site.template !== undefined && typeof site.template !== 'boolean')
|
|
46
|
+
return false;
|
|
47
|
+
if (site.spread !== undefined &&
|
|
48
|
+
(!Number.isInteger(site.spread) || site.spread < 0)) {
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
if (site.parent !== undefined) {
|
|
52
|
+
const p = site.parent;
|
|
53
|
+
if (!Array.isArray(p) || p.length !== 2)
|
|
54
|
+
return false;
|
|
55
|
+
if (!siteInRange(p[0]))
|
|
56
|
+
return false;
|
|
57
|
+
if (!Number.isInteger(p[1]) || p[1] < 0)
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
if (site.resultDefs !== undefined) {
|
|
61
|
+
if (!Array.isArray(site.resultDefs) || !site.resultDefs.every(bindingInRange))
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
64
|
+
if (site.args !== undefined) {
|
|
65
|
+
if (!Array.isArray(site.args))
|
|
66
|
+
return false;
|
|
67
|
+
for (const position of site.args) {
|
|
68
|
+
if (!Array.isArray(position))
|
|
69
|
+
return false;
|
|
70
|
+
for (const entry of position) {
|
|
71
|
+
if (typeof entry === 'number') {
|
|
72
|
+
if (!bindingInRange(entry))
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
else if (Array.isArray(entry) && entry.length === 2) {
|
|
76
|
+
if (!bindingInRange(entry[0]) || !siteInRange(entry[1]))
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
if (site.kind === 'member-read') {
|
|
86
|
+
// The matcher dereferences both unconditionally on member reads.
|
|
87
|
+
if (!bindingInRange(site.object) || typeof site.property !== 'string')
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
if (site.object !== undefined && !bindingInRange(site.object))
|
|
92
|
+
return false;
|
|
93
|
+
if (site.property !== undefined && typeof site.property !== 'string')
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return true;
|
|
98
|
+
};
|
|
@@ -1,36 +1,107 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Source/sink/sanitizer config model (issue #2080,
|
|
2
|
+
* Source/sink/sanitizer config model (issue #2080 M0 seam, extended by #2083
|
|
3
|
+
* M3 U2).
|
|
3
4
|
*
|
|
4
|
-
* The per-language taint configuration *shape*. M0
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
5
|
+
* The per-language taint configuration *shape*. M0 shipped only the bare
|
|
6
|
+
* `{name, args?}` callable matcher and an empty registry seam; M3 U2 extends
|
|
7
|
+
* it with the `kind` taxonomy and the resolution-mechanism fields the
|
|
8
|
+
* import-aware matcher (`taint/match.ts`) needs, and fills the registry with
|
|
9
|
+
* the built-in TS/JS model (`taint/typescript-model.ts`).
|
|
8
10
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* when M3 makes contact with real flows; that is a forward-declared-interface
|
|
14
|
-
* design choice, not a finished contract.
|
|
11
|
+
* Design rule: entries describe WHAT a callable is (category + how its name
|
|
12
|
+
* resolves), never HOW matching works — matching semantics (import joins,
|
|
13
|
+
* shadow checks, spread/template position rules) live in the matcher so the
|
|
14
|
+
* spec stays declarative data that can hash into `taintModelVersion`.
|
|
15
15
|
*/
|
|
16
|
+
/** Categories of taint sources. M3 ships remote HTTP input only. */
|
|
17
|
+
export type SourceKind = 'remote-input';
|
|
16
18
|
/**
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
19
|
+
* Vulnerability categories for sinks. Sanitizers reference the SAME taxonomy
|
|
20
|
+
* via {@link TaintSanitizerEntry.neutralizes}: a sanitizer kill applies only
|
|
21
|
+
* when it neutralizes the matched sink's kind (`path.basename` strips
|
|
22
|
+
* directories, not shell metacharacters — a kind-blind kill is a suppressed
|
|
23
|
+
* live command injection, the forbidden false-negative direction).
|
|
22
24
|
*/
|
|
23
|
-
export
|
|
25
|
+
export type SinkKind = 'command-injection' | 'code-injection' | 'path-traversal' | 'sql-injection' | 'xss';
|
|
26
|
+
/**
|
|
27
|
+
* Identifies a callable that participates in taint flow. `name` is the
|
|
28
|
+
* callable's own (unqualified) name — qualification comes from the
|
|
29
|
+
* resolution-mechanism fields on the extending entry types, not from dotted
|
|
30
|
+
* `name` strings. `args` optionally narrows to specific 0-based argument
|
|
31
|
+
* positions that carry taint into a sink (or are cleared by a sanitizer);
|
|
32
|
+
* omit to mean "all positions".
|
|
33
|
+
*/
|
|
34
|
+
export interface TaintCallableMatcher<K extends string = string> {
|
|
35
|
+
readonly name: string;
|
|
36
|
+
readonly args?: readonly number[];
|
|
37
|
+
/** Category label — drives finding classification and sanitizer kind-compat. */
|
|
38
|
+
readonly kind: K;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* A sink callable. Exactly one resolution mechanism should be set per entry:
|
|
42
|
+
*
|
|
43
|
+
* - `module` — the callable lives in a package/builtin module; the matcher
|
|
44
|
+
* resolves call sites against it import-aware (ESM `parsedImports` aliases,
|
|
45
|
+
* namespace handles, and the CommonJS `require('<literal>')` join). `name`
|
|
46
|
+
* is the exported member (`'exec'` of `'child_process'`); the pseudo-name
|
|
47
|
+
* `'default'` denotes invoking the module's default export / the module
|
|
48
|
+
* handle itself.
|
|
49
|
+
* - `global` — a true ECMAScript global (`eval`, `Function`); matched by bare
|
|
50
|
+
* name only when the name is not shadowed by an in-function declaration and
|
|
51
|
+
* not bound by an import. `newOnly` further restricts to `new` expressions
|
|
52
|
+
* (`new Function(body)`).
|
|
53
|
+
* - `anyReceiver` — a method matched on ANY receiver chain by its final
|
|
54
|
+
* segment (`.query(sql)` / `.execute(sql)` on whatever the DB handle is
|
|
55
|
+
* named) — deliberately name-conventional, like Semgrep's default rules.
|
|
56
|
+
* - `receivers` — a method matched only on the listed conventional receiver
|
|
57
|
+
* names (`res.send` / `res.write`); exactly `<receiver>.<name>`, name-based.
|
|
58
|
+
*/
|
|
59
|
+
export interface TaintSinkEntry extends TaintCallableMatcher<SinkKind> {
|
|
60
|
+
readonly module?: string;
|
|
61
|
+
readonly global?: boolean;
|
|
62
|
+
/** Only meaningful with `global`: match `new <name>(…)` sites only. */
|
|
63
|
+
readonly newOnly?: boolean;
|
|
64
|
+
readonly anyReceiver?: boolean;
|
|
65
|
+
readonly receivers?: readonly string[];
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* A sanitizer callable. Carries the sink kinds it `neutralizes` instead of a
|
|
69
|
+
* `kind` of its own. STRICTER resolution than sinks by design: only the
|
|
70
|
+
* `module` (import-aware) and `global` mechanisms exist — never a bare-name
|
|
71
|
+
* convention — because a sanitizer mis-match is a false KILL (a user's own
|
|
72
|
+
* `escape` helper must not suppress findings), while a sink mis-match is
|
|
73
|
+
* merely noise. `args` narrows which argument positions are cleared (omit =
|
|
74
|
+
* all).
|
|
75
|
+
*/
|
|
76
|
+
export interface TaintSanitizerEntry {
|
|
24
77
|
readonly name: string;
|
|
25
78
|
readonly args?: readonly number[];
|
|
79
|
+
readonly neutralizes: readonly SinkKind[];
|
|
80
|
+
readonly module?: string;
|
|
81
|
+
readonly global?: boolean;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* A member-read taint source: reading `<object>.<property>` where the object
|
|
85
|
+
* is one of the conventional receiver `objects` names (`req`/`request`) and
|
|
86
|
+
* the property is one of `properties` (`body`, `query`, …). Matching is
|
|
87
|
+
* name-based on the harvested `member-read` site (Semgrep-convention, not
|
|
88
|
+
* type-aware — the accepted M3 FP/FN trade recorded in the plan's risk
|
|
89
|
+
* table). One entry fans out over the objects × properties product.
|
|
90
|
+
*/
|
|
91
|
+
export interface TaintMemberSourceEntry {
|
|
92
|
+
readonly kind: SourceKind;
|
|
93
|
+
readonly objects: readonly string[];
|
|
94
|
+
readonly properties: readonly string[];
|
|
26
95
|
}
|
|
27
96
|
/**
|
|
28
|
-
* The taint configuration for a single language: which
|
|
29
|
-
* taint (sources), which are dangerous to reach with tainted input
|
|
30
|
-
* and which clear
|
|
97
|
+
* The taint configuration for a single language: which member reads introduce
|
|
98
|
+
* taint (sources), which callables are dangerous to reach with tainted input
|
|
99
|
+
* (sinks), and which callables clear it (sanitizers). M3 sources are
|
|
100
|
+
* member-read entries only; call-result sources are a forward extension
|
|
101
|
+
* (add a union variant), not a missing case.
|
|
31
102
|
*/
|
|
32
103
|
export interface SourceSinkSanitizerSpec {
|
|
33
|
-
readonly sources: readonly
|
|
34
|
-
readonly sinks: readonly
|
|
35
|
-
readonly sanitizers: readonly
|
|
104
|
+
readonly sources: readonly TaintMemberSourceEntry[];
|
|
105
|
+
readonly sinks: readonly TaintSinkEntry[];
|
|
106
|
+
readonly sanitizers: readonly TaintSanitizerEntry[];
|
|
36
107
|
}
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Source/sink/sanitizer config model (issue #2080,
|
|
2
|
+
* Source/sink/sanitizer config model (issue #2080 M0 seam, extended by #2083
|
|
3
|
+
* M3 U2).
|
|
3
4
|
*
|
|
4
|
-
* The per-language taint configuration *shape*. M0
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
5
|
+
* The per-language taint configuration *shape*. M0 shipped only the bare
|
|
6
|
+
* `{name, args?}` callable matcher and an empty registry seam; M3 U2 extends
|
|
7
|
+
* it with the `kind` taxonomy and the resolution-mechanism fields the
|
|
8
|
+
* import-aware matcher (`taint/match.ts`) needs, and fills the registry with
|
|
9
|
+
* the built-in TS/JS model (`taint/typescript-model.ts`).
|
|
8
10
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* when M3 makes contact with real flows; that is a forward-declared-interface
|
|
14
|
-
* design choice, not a finished contract.
|
|
11
|
+
* Design rule: entries describe WHAT a callable is (category + how its name
|
|
12
|
+
* resolves), never HOW matching works — matching semantics (import joins,
|
|
13
|
+
* shadow checks, spread/template position rules) live in the matcher so the
|
|
14
|
+
* spec stays declarative data that can hash into `taintModelVersion`.
|
|
15
15
|
*/
|
|
16
16
|
export {};
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Per-language source/sink/sanitizer registry seam (issue #2080).
|
|
3
3
|
*
|
|
4
|
-
* A keyed registry of {@link SourceSinkSanitizerSpec} by language id. M0
|
|
5
|
-
* up the empty seam
|
|
6
|
-
*
|
|
7
|
-
*
|
|
4
|
+
* A keyed registry of {@link SourceSinkSanitizerSpec} by language id. M0 stood
|
|
5
|
+
* up the empty seam; M3 U2 (#2083) fills it with the built-in TS/JS model via
|
|
6
|
+
* the EXPLICIT `registerBuiltinTaintModels()` seam in `typescript-model.ts` —
|
|
7
|
+
* deliberately not an import side-effect. The U4 taint emit path must call it
|
|
8
|
+
* once before the pdg window consumes the registry (idempotent; the registry
|
|
9
|
+
* itself stays empty until then, preserving default-run parity).
|
|
8
10
|
*
|
|
9
11
|
* The store is module-level (matching the codebase's other per-language
|
|
10
12
|
* registries). {@link clearSourceSinkRegistry} resets it for test isolation.
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Per-language source/sink/sanitizer registry seam (issue #2080).
|
|
3
3
|
*
|
|
4
|
-
* A keyed registry of {@link SourceSinkSanitizerSpec} by language id. M0
|
|
5
|
-
* up the empty seam
|
|
6
|
-
*
|
|
7
|
-
*
|
|
4
|
+
* A keyed registry of {@link SourceSinkSanitizerSpec} by language id. M0 stood
|
|
5
|
+
* up the empty seam; M3 U2 (#2083) fills it with the built-in TS/JS model via
|
|
6
|
+
* the EXPLICIT `registerBuiltinTaintModels()` seam in `typescript-model.ts` —
|
|
7
|
+
* deliberately not an import side-effect. The U4 taint emit path must call it
|
|
8
|
+
* once before the pdg window consumes the registry (idempotent; the registry
|
|
9
|
+
* itself stays empty until then, preserving default-run parity).
|
|
8
10
|
*
|
|
9
11
|
* The store is module-level (matching the codebase's other per-language
|
|
10
12
|
* registries). {@link clearSourceSinkRegistry} resets it for test isolation.
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Built-in TS/JS taint model (#2083 M3 U2, plan KTD7).
|
|
3
|
+
*
|
|
4
|
+
* The canonical Express/Node source/sink/sanitizer set, registered for the
|
|
5
|
+
* `typescript` and `javascript` language ids via the EXPLICIT
|
|
6
|
+
* {@link registerBuiltinTaintModels} seam — deliberately not an import
|
|
7
|
+
* side-effect, so the U4 emit path controls WHEN registration happens (call
|
|
8
|
+
* it once before the pdg window runs; it is idempotent — the registry is
|
|
9
|
+
* last-write-wins on the same language id).
|
|
10
|
+
*
|
|
11
|
+
* `taintModelVersion` is a deterministic digest of the FULL model content
|
|
12
|
+
* (entries, kinds, args, modules). It joins the RepoMeta `pdg` stamp in U5 so
|
|
13
|
+
* that ANY model change — adding an entry, relabeling a kind — trips full
|
|
14
|
+
* writeback on an existing `--pdg` index (R7): persisted findings must never
|
|
15
|
+
* outlive the model that produced them.
|
|
16
|
+
*/
|
|
17
|
+
import type { SourceSinkSanitizerSpec } from './source-sink-config.js';
|
|
18
|
+
/**
|
|
19
|
+
* The built-in TS/JS model. Module provenance uses bare specifier names —
|
|
20
|
+
* the matcher normalizes the `node:` scheme prefix, so `import { exec } from
|
|
21
|
+
* 'node:child_process'` resolves identically.
|
|
22
|
+
*/
|
|
23
|
+
export declare const TS_JS_TAINT_MODEL: SourceSinkSanitizerSpec;
|
|
24
|
+
/**
|
|
25
|
+
* Deterministic digest of a spec's full content. Key order is canonicalized
|
|
26
|
+
* (recursively sorted) so the version reflects CONTENT, not literal layout;
|
|
27
|
+
* array order is semantic (entry identity) and intentionally preserved.
|
|
28
|
+
*/
|
|
29
|
+
export declare function computeTaintModelVersion(spec: SourceSinkSanitizerSpec): string;
|
|
30
|
+
/** Version stamp of the built-in TS/JS model (joins the RepoMeta pdg key in U5). */
|
|
31
|
+
export declare const taintModelVersion: string;
|
|
32
|
+
/**
|
|
33
|
+
* Register the built-in model for TypeScript and JavaScript. Explicit init
|
|
34
|
+
* seam for the U4 emit path (call before the pdg window consumes the
|
|
35
|
+
* registry); idempotent. Vue and other TS-adjacent language ids are
|
|
36
|
+
* deliberately NOT registered — the M3 scope is TS/JS only.
|
|
37
|
+
*/
|
|
38
|
+
export declare function registerBuiltinTaintModels(): void;
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Built-in TS/JS taint model (#2083 M3 U2, plan KTD7).
|
|
3
|
+
*
|
|
4
|
+
* The canonical Express/Node source/sink/sanitizer set, registered for the
|
|
5
|
+
* `typescript` and `javascript` language ids via the EXPLICIT
|
|
6
|
+
* {@link registerBuiltinTaintModels} seam — deliberately not an import
|
|
7
|
+
* side-effect, so the U4 emit path controls WHEN registration happens (call
|
|
8
|
+
* it once before the pdg window runs; it is idempotent — the registry is
|
|
9
|
+
* last-write-wins on the same language id).
|
|
10
|
+
*
|
|
11
|
+
* `taintModelVersion` is a deterministic digest of the FULL model content
|
|
12
|
+
* (entries, kinds, args, modules). It joins the RepoMeta `pdg` stamp in U5 so
|
|
13
|
+
* that ANY model change — adding an entry, relabeling a kind — trips full
|
|
14
|
+
* writeback on an existing `--pdg` index (R7): persisted findings must never
|
|
15
|
+
* outlive the model that produced them.
|
|
16
|
+
*/
|
|
17
|
+
import { createHash } from 'node:crypto';
|
|
18
|
+
import { SupportedLanguages } from '../../../_shared/index.js';
|
|
19
|
+
import { registerSourceSinkConfig } from './source-sink-registry.js';
|
|
20
|
+
/**
|
|
21
|
+
* The built-in TS/JS model. Module provenance uses bare specifier names —
|
|
22
|
+
* the matcher normalizes the `node:` scheme prefix, so `import { exec } from
|
|
23
|
+
* 'node:child_process'` resolves identically.
|
|
24
|
+
*/
|
|
25
|
+
export const TS_JS_TAINT_MODEL = {
|
|
26
|
+
sources: [
|
|
27
|
+
// Express-convention request member reads, matched name-based on the
|
|
28
|
+
// receiver (`req`/`request`) — the plan's accepted Semgrep-style trade.
|
|
29
|
+
{
|
|
30
|
+
kind: 'remote-input',
|
|
31
|
+
objects: ['req', 'request'],
|
|
32
|
+
properties: ['body', 'query', 'params', 'headers', 'cookies'],
|
|
33
|
+
},
|
|
34
|
+
],
|
|
35
|
+
sinks: [
|
|
36
|
+
// Command execution — the command string is argument 0.
|
|
37
|
+
{ name: 'exec', kind: 'command-injection', args: [0], module: 'child_process' },
|
|
38
|
+
{ name: 'execSync', kind: 'command-injection', args: [0], module: 'child_process' },
|
|
39
|
+
{ name: 'spawn', kind: 'command-injection', args: [0], module: 'child_process' },
|
|
40
|
+
// Code evaluation. `eval` takes code at 0; `new Function(...)` treats
|
|
41
|
+
// EVERY argument as source text (params + body), so `args` is omitted
|
|
42
|
+
// (= all positions) rather than pinned to 0.
|
|
43
|
+
{ name: 'eval', kind: 'code-injection', args: [0], global: true },
|
|
44
|
+
{ name: 'Function', kind: 'code-injection', global: true, newOnly: true },
|
|
45
|
+
// Filesystem path consumption — path argument 0.
|
|
46
|
+
{ name: 'readFile', kind: 'path-traversal', args: [0], module: 'fs' },
|
|
47
|
+
{ name: 'readFileSync', kind: 'path-traversal', args: [0], module: 'fs' },
|
|
48
|
+
{ name: 'writeFile', kind: 'path-traversal', args: [0], module: 'fs' },
|
|
49
|
+
{ name: 'writeFileSync', kind: 'path-traversal', args: [0], module: 'fs' },
|
|
50
|
+
// SQL — `.query(sql)` / `.execute(sql)` member calls on ANY receiver
|
|
51
|
+
// (mysql2/pg/knex handles go by many names; receiver-conventional).
|
|
52
|
+
{ name: 'query', kind: 'sql-injection', args: [0], anyReceiver: true },
|
|
53
|
+
{ name: 'execute', kind: 'sql-injection', args: [0], anyReceiver: true },
|
|
54
|
+
// Reflected XSS — Express response writes, conventional receiver `res`.
|
|
55
|
+
{ name: 'send', kind: 'xss', args: [0], receivers: ['res'] },
|
|
56
|
+
{ name: 'write', kind: 'xss', args: [0], receivers: ['res'] },
|
|
57
|
+
],
|
|
58
|
+
sanitizers: [
|
|
59
|
+
// URL-encoding: neutralizes markup injection AND path separators
|
|
60
|
+
// (`%2F` is not a separator inside a path component).
|
|
61
|
+
{ name: 'encodeURIComponent', neutralizes: ['xss', 'path-traversal'], global: true },
|
|
62
|
+
// `escape-html` exports its function as the module default — the
|
|
63
|
+
// `'default'` pseudo-name matches the default-imported / require'd
|
|
64
|
+
// module handle being invoked directly.
|
|
65
|
+
{ name: 'default', neutralizes: ['xss'], module: 'escape-html' },
|
|
66
|
+
{ name: 'encode', neutralizes: ['xss'], module: 'he' },
|
|
67
|
+
{ name: 'basename', neutralizes: ['path-traversal'], module: 'path' },
|
|
68
|
+
{ name: 'escape', neutralizes: ['xss'], module: 'validator' },
|
|
69
|
+
],
|
|
70
|
+
};
|
|
71
|
+
/**
|
|
72
|
+
* Deterministic digest of a spec's full content. Key order is canonicalized
|
|
73
|
+
* (recursively sorted) so the version reflects CONTENT, not literal layout;
|
|
74
|
+
* array order is semantic (entry identity) and intentionally preserved.
|
|
75
|
+
*/
|
|
76
|
+
export function computeTaintModelVersion(spec) {
|
|
77
|
+
return createHash('sha256').update(canonicalJson(spec)).digest('hex').slice(0, 12);
|
|
78
|
+
}
|
|
79
|
+
function canonicalJson(value) {
|
|
80
|
+
if (Array.isArray(value))
|
|
81
|
+
return `[${value.map(canonicalJson).join(',')}]`;
|
|
82
|
+
if (value !== null && typeof value === 'object') {
|
|
83
|
+
const entries = Object.entries(value)
|
|
84
|
+
.filter(([, v]) => v !== undefined)
|
|
85
|
+
.sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))
|
|
86
|
+
.map(([k, v]) => `${JSON.stringify(k)}:${canonicalJson(v)}`);
|
|
87
|
+
return `{${entries.join(',')}}`;
|
|
88
|
+
}
|
|
89
|
+
return JSON.stringify(value);
|
|
90
|
+
}
|
|
91
|
+
/** Version stamp of the built-in TS/JS model (joins the RepoMeta pdg key in U5). */
|
|
92
|
+
export const taintModelVersion = computeTaintModelVersion(TS_JS_TAINT_MODEL);
|
|
93
|
+
/**
|
|
94
|
+
* Register the built-in model for TypeScript and JavaScript. Explicit init
|
|
95
|
+
* seam for the U4 emit path (call before the pdg window consumes the
|
|
96
|
+
* registry); idempotent. Vue and other TS-adjacent language ids are
|
|
97
|
+
* deliberately NOT registered — the M3 scope is TS/JS only.
|
|
98
|
+
*/
|
|
99
|
+
export function registerBuiltinTaintModels() {
|
|
100
|
+
registerSourceSinkConfig(SupportedLanguages.TypeScript, TS_JS_TAINT_MODEL);
|
|
101
|
+
registerSourceSinkConfig(SupportedLanguages.JavaScript, TS_JS_TAINT_MODEL);
|
|
102
|
+
}
|
|
@@ -184,6 +184,7 @@ export function buildMethodProps(info) {
|
|
|
184
184
|
...(info.isAsync ? { isAsync: info.isAsync } : {}),
|
|
185
185
|
...(info.isPartial ? { isPartial: info.isPartial } : {}),
|
|
186
186
|
...(info.isConst ? { isConst: info.isConst } : {}),
|
|
187
|
+
...(info.isDeleted ? { isDeleted: info.isDeleted } : {}),
|
|
187
188
|
...(info.annotations.length > 0 ? { annotations: info.annotations } : {}),
|
|
188
189
|
};
|
|
189
190
|
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Structured-clone safety for the worker result boundary (#2112).
|
|
3
|
+
*
|
|
4
|
+
* A parse worker delivers its accumulated result to the main thread via
|
|
5
|
+
* `parentPort.postMessage(...)`. Node serializes that payload with the
|
|
6
|
+
* structured-clone algorithm SYNCHRONOUSLY on the worker thread, and it
|
|
7
|
+
* THROWS a `DataCloneError` the instant it meets a value it can't serialize —
|
|
8
|
+
* a function, a symbol, a Promise, a WeakMap, etc. The reporter of #2112 hit
|
|
9
|
+
* exactly this: a node record whose `properties` carried an own-enumerable
|
|
10
|
+
* value pointing at a native function (`function toString() { [native code] }
|
|
11
|
+
* could not be cloned`). One such value aborted the entire parse phase,
|
|
12
|
+
* because the worker re-posts the throw as `{type:'error'}` which the pool
|
|
13
|
+
* counts as a worker death — and under `GITNEXUS_WORKER_POOL_SIZE=1` the same
|
|
14
|
+
* graph re-throws on every respawn until the slot's budget is exhausted.
|
|
15
|
+
*
|
|
16
|
+
* This module is the safety net. It runs ONLY after a real clone failure on
|
|
17
|
+
* the fast-path post (zero overhead on healthy runs), and rewrites the
|
|
18
|
+
* boundary-crossing arrays so the result becomes cloneable: a non-cloneable
|
|
19
|
+
* value inside a plain extraction record is dropped (the record is otherwise
|
|
20
|
+
* kept — strictly-missing data, never wrong), and a `ParsedFile` that can't be
|
|
21
|
+
* made cloneable is dropped whole so scope-resolution re-derives it on the
|
|
22
|
+
* main thread (where there is no clone boundary) with intact edge data.
|
|
23
|
+
*
|
|
24
|
+
* Language-neutral by construction: it keys on value shape and field name
|
|
25
|
+
* only, never on a language (AGENTS.md shared-pipeline rule). The strip
|
|
26
|
+
* semantics mirror what the store path's `JSON.stringify` already silently
|
|
27
|
+
* drops, so store / no-store / cold / warm runs converge on the same graph.
|
|
28
|
+
*/
|
|
29
|
+
/** A file whose parse result was sanitized or dropped at the clone boundary. */
|
|
30
|
+
export interface SkippedPath {
|
|
31
|
+
/** Best-effort source path of the offending record (or `(unknown)`). */
|
|
32
|
+
path: string;
|
|
33
|
+
/** Human-readable reason, e.g. "dropped 1 non-serializable value from nodes". */
|
|
34
|
+
reason: string;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* True iff `value` survives Node's structured-clone algorithm (the same
|
|
38
|
+
* algorithm `postMessage` uses). This is the authoritative probe — it matches
|
|
39
|
+
* the real failure exactly, including Map/Set/Date/RegExp/TypedArray support,
|
|
40
|
+
* so it never false-positives on the `Scope` Maps that clone fine.
|
|
41
|
+
*/
|
|
42
|
+
export declare function isStructuredCloneable(value: unknown): boolean;
|
|
43
|
+
/** The leaf values the structured-clone algorithm copies verbatim. */
|
|
44
|
+
type CloneablePrimitive = undefined | null | boolean | number | bigint | string;
|
|
45
|
+
/**
|
|
46
|
+
* Maps `T` to itself when every value reachable from it is structured-clone
|
|
47
|
+
* safe, and to a type containing `never` at the first offending property
|
|
48
|
+
* otherwise. A function or symbol — the values `postMessage` rejects — becomes
|
|
49
|
+
* `never`, so a struct carrying one is no longer assignable to its own
|
|
50
|
+
* `Cloneable<T>` and `assertCloneable` rejects it, naming the bad key.
|
|
51
|
+
*
|
|
52
|
+
* Implemented as a homomorphic mapped type (`{ [K in keyof T]: … }`) so it
|
|
53
|
+
* preserves `interface` shapes and `readonly` modifiers and works WITHOUT
|
|
54
|
+
* requiring the payload types to carry an index signature — sidestepping the
|
|
55
|
+
* "closed interface is not assignable to a recursive index-signature type" wall
|
|
56
|
+
* that blocked the value-typed-`Cloneable` approach (#2143). `Map`/`Set`/array
|
|
57
|
+
* containers recurse into their element types; `Date`/`RegExp` are clone-safe
|
|
58
|
+
* leaves.
|
|
59
|
+
*/
|
|
60
|
+
/** True iff `T` is `any` (the canonical `IsAny` probe: only `any` satisfies `0 extends 1 & T`). */
|
|
61
|
+
type IsAny<T> = 0 extends 1 & T ? true : false;
|
|
62
|
+
export type Cloneable<T> = IsAny<T> extends true ? never : T extends CloneablePrimitive | Date | RegExp ? T : T extends (...args: never[]) => unknown ? never : T extends symbol ? never : T extends ReadonlyMap<infer K, infer V> ? ReadonlyMap<Cloneable<K>, Cloneable<V>> : T extends ReadonlySet<infer U> ? ReadonlySet<Cloneable<U>> : T extends readonly (infer U)[] ? T extends unknown[] ? Cloneable<U>[] : readonly Cloneable<U>[] : T extends object ? {
|
|
63
|
+
[K in keyof T]: Cloneable<T[K]>;
|
|
64
|
+
} : never;
|
|
65
|
+
/**
|
|
66
|
+
* Identity at runtime (zero cost — returns its argument unchanged); a
|
|
67
|
+
* compile-time assertion that `value` is structured-clone safe. Wrap a
|
|
68
|
+
* producer that feeds an `unknown` worker-result sink:
|
|
69
|
+
*
|
|
70
|
+
* collectCaptureSideChannel: (filePath) => assertCloneable(collectFoo(filePath))
|
|
71
|
+
*
|
|
72
|
+
* If `collectFoo`'s return type ever gains a non-cloneable member (a function, a
|
|
73
|
+
* `SyntaxNode`, …) the call fails to compile, pointing at the offending key.
|
|
74
|
+
*
|
|
75
|
+
* The parameter is a conditional type rather than an `extends Cloneable<T>`
|
|
76
|
+
* constraint because a self-referential constraint (`T extends Cloneable<T>`)
|
|
77
|
+
* is a "circular constraint" error in TypeScript. For a clone-safe `T` the
|
|
78
|
+
* parameter resolves to `T` (call type-checks as a plain identity); for an
|
|
79
|
+
* unsafe `T` it resolves to `Cloneable<T>` (which has `never` at the bad key),
|
|
80
|
+
* so the argument is rejected.
|
|
81
|
+
*/
|
|
82
|
+
export declare function assertCloneable<T>(value: T extends Cloneable<T> ? T : Cloneable<T>): T;
|
|
83
|
+
export interface MakeCloneSafeOptions {
|
|
84
|
+
/**
|
|
85
|
+
* Array field names whose offending elements are DROPPED whole rather than
|
|
86
|
+
* stripped in place (e.g. `parsedFiles` — its `captureSideChannel` drives
|
|
87
|
+
* edge resolution, so a stripped-and-delivered file would ship WRONG edges;
|
|
88
|
+
* dropping it lets scope-resolution re-derive it on the main thread).
|
|
89
|
+
*/
|
|
90
|
+
dropWholeElement: ReadonlySet<string>;
|
|
91
|
+
/** Field names to skip entirely (e.g. the `skippedPaths` field itself). */
|
|
92
|
+
skipFields?: ReadonlySet<string>;
|
|
93
|
+
/** Keys to probe for a file path when attributing a skip. */
|
|
94
|
+
pathKeys?: readonly string[];
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Make a worker result's boundary-crossing array fields structured-cloneable,
|
|
98
|
+
* mutating `result` in place. Only arrays that actually contain a
|
|
99
|
+
* non-cloneable value are rewritten; everything else keeps referential
|
|
100
|
+
* identity. Returns the list of affected file paths for reporting.
|
|
101
|
+
*
|
|
102
|
+
* Call this after ANY failure of the fast-path post — a `DataCloneError`, OR a
|
|
103
|
+
* throwing getter's own error surfaced by structuredClone (the caller in
|
|
104
|
+
* `post-result.ts` recovers on any throw, not only `DataCloneError`).
|
|
105
|
+
*/
|
|
106
|
+
export declare function makeWorkerResultCloneSafe(result: Record<string, unknown>, options: MakeCloneSafeOptions): {
|
|
107
|
+
skipped: SkippedPath[];
|
|
108
|
+
};
|
|
109
|
+
export {};
|