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,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Taint-path reason codec (#2083 M3 U4/U6, plan KTD6).
|
|
3
|
+
*
|
|
4
|
+
* THE one shared encoder/decoder for the hop-encoded `reason` carried on
|
|
5
|
+
* persisted `TAINTED` edges: the U4 emit path writes it, the U6 MCP `explain`
|
|
6
|
+
* tool reads it. Two hand-rolled copies of a wire format drift — both sides
|
|
7
|
+
* MUST import from here.
|
|
8
|
+
*
|
|
9
|
+
* ## Wire format (version `1`)
|
|
10
|
+
*
|
|
11
|
+
* ```
|
|
12
|
+
* 1[;<kind>]|<name>:<line>[:<flags>]|<name>:<line>[:<flags>]|…[|~]
|
|
13
|
+
* ```
|
|
14
|
+
*
|
|
15
|
+
* - One-character version prefix (`TAINT_PATH_CODEC_VERSION`), then an
|
|
16
|
+
* OPTIONAL `;<kind>` header segment, then ordered source→sink hops, each
|
|
17
|
+
* `variable:line[:flags]`.
|
|
18
|
+
* - `kind` is the finding's sink category (`SinkKind`, e.g.
|
|
19
|
+
* `command-injection`). It rides the reason because it is the ONLY
|
|
20
|
+
* persisted channel: the CodeRelation columns are
|
|
21
|
+
* `type/confidence/reason/step` — `step` is INT32 and the emit-time edge id
|
|
22
|
+
* (which embeds the kind) is not a stored column. The U6 `explain` tool
|
|
23
|
+
* reads it for finding classification. Charset `[a-z0-9-]` (printable
|
|
24
|
+
* ASCII, disjoint from every structural delimiter); `;` itself is printable
|
|
25
|
+
* ASCII and never appears in hop names (identifier charset) or flags.
|
|
26
|
+
* U6 deviation note: this header was added by U6 WITHIN version `1` —
|
|
27
|
+
* U4 and U6 ship in the same release, so no reason string without the
|
|
28
|
+
* header was ever persisted by a released build; the decoder still accepts
|
|
29
|
+
* header-less strings (`kind` simply decodes as `undefined`).
|
|
30
|
+
* - `flags` is a lowercase-letter set; only `c` (= the hop passed through an
|
|
31
|
+
* unmodeled call, KTD5 `viaCall`) is defined today — the rest of the
|
|
32
|
+
* alphabet is RESERVED, and the decoder accepts unknown flag letters so a
|
|
33
|
+
* future writer's output stays decodable.
|
|
34
|
+
* - A trailing `|~` segment is the TRUNCATION MARKER: the encoded path is a
|
|
35
|
+
* source-side PREFIX of the real one (hop cap, byte cap, or an unencodable
|
|
36
|
+
* hop name). Decoders MUST report it as "path incomplete" — never an error.
|
|
37
|
+
*
|
|
38
|
+
* ## Delimiter / round-trip discipline (KTD6)
|
|
39
|
+
*
|
|
40
|
+
* Every structural character (`|`, `:`, `~`, digits, flag letters) is
|
|
41
|
+
* printable ASCII: `sanitizeUTF8` (csv-generator.ts) strips control
|
|
42
|
+
* characters, lone surrogates, and U+FFFE/FFFF — printable ASCII passes
|
|
43
|
+
* through byte-exact, so the encoding survives `escapeCSVField ∘
|
|
44
|
+
* sanitizeUTF8` and the DB load unchanged (pinned by the round-trip test).
|
|
45
|
+
* None of the delimiters can appear in a JS identifier.
|
|
46
|
+
*
|
|
47
|
+
* Hop names are identifier-charset by U1 construction (the harvest records
|
|
48
|
+
* binding names), but the encoder DEFENDS anyway: a hop whose name falls
|
|
49
|
+
* outside the safe charset (or whose line is not a non-negative integer) is
|
|
50
|
+
* never emitted — encoding stops at the offending hop and sets the truncation
|
|
51
|
+
* marker, preserving the prefix-of-the-true-path invariant rather than
|
|
52
|
+
* corrupting the format. (`#` is in the charset: JS private names are
|
|
53
|
+
* `#field`, and the propagation engine's fallback hop names are `#<idx>`.)
|
|
54
|
+
*
|
|
55
|
+
* The byte cap (`TAINT_REASON_MAX_BYTES`, KTD6's "absolute reason-byte cap")
|
|
56
|
+
* bounds the persisted reason column regardless of hop caps: overflow drops
|
|
57
|
+
* TRAILING hops (keeps the source side) and sets the marker. All structural
|
|
58
|
+
* chars and valid names are single-byte ASCII, so `string.length` IS the
|
|
59
|
+
* byte length.
|
|
60
|
+
*/
|
|
61
|
+
/** One-character format version prefix. Bump on any wire-format change. */
|
|
62
|
+
export declare const TAINT_PATH_CODEC_VERSION = "1";
|
|
63
|
+
/**
|
|
64
|
+
* Absolute cap on the encoded reason's byte length (KTD6). 4096 comfortably
|
|
65
|
+
* holds ~100 hops of realistic identifiers — far beyond the default hop cap
|
|
66
|
+
* (32) — while bounding the persisted column even at `maxHops: 0` (unlimited).
|
|
67
|
+
*/
|
|
68
|
+
export declare const TAINT_REASON_MAX_BYTES = 4096;
|
|
69
|
+
/** The truncation-marker segment content (rides as a trailing `|~`). */
|
|
70
|
+
export declare const TAINT_PATH_TRUNCATION_MARKER = "~";
|
|
71
|
+
/** Encoder input hop — shape-compatible with `TaintHop` (propagate.ts). */
|
|
72
|
+
export interface TaintPathHopInput {
|
|
73
|
+
readonly name: string;
|
|
74
|
+
readonly line: number;
|
|
75
|
+
readonly viaCall?: boolean;
|
|
76
|
+
}
|
|
77
|
+
export interface EncodeTaintPathOptions {
|
|
78
|
+
/**
|
|
79
|
+
* The hop list is already a truncated prefix (e.g. the propagation engine's
|
|
80
|
+
* `hopsTruncated` from its hop cap) — emit the marker even when every hop
|
|
81
|
+
* fits.
|
|
82
|
+
*/
|
|
83
|
+
readonly truncated?: boolean;
|
|
84
|
+
/** Byte cap override (tests). Default {@link TAINT_REASON_MAX_BYTES}. */
|
|
85
|
+
readonly maxBytes?: number;
|
|
86
|
+
/**
|
|
87
|
+
* Finding sink category (`SinkKind`) carried in the `;<kind>` header — the
|
|
88
|
+
* only persisted channel for it (see the module doc). A value outside the
|
|
89
|
+
* `[a-z0-9-]` charset is DROPPED (header omitted), never corrupted into the
|
|
90
|
+
* wire string; `SinkKind` is a closed lowercase-hyphen union so this is
|
|
91
|
+
* purely defensive.
|
|
92
|
+
*/
|
|
93
|
+
readonly kind?: string;
|
|
94
|
+
}
|
|
95
|
+
export interface EncodedTaintPath {
|
|
96
|
+
/** The wire string for the TAINTED edge's `reason` column. */
|
|
97
|
+
readonly reason: string;
|
|
98
|
+
/** True when the marker was emitted (caller-flagged, byte cap, or bad hop). */
|
|
99
|
+
readonly truncated: boolean;
|
|
100
|
+
}
|
|
101
|
+
export interface DecodedTaintHop {
|
|
102
|
+
readonly variable: string;
|
|
103
|
+
readonly line: number;
|
|
104
|
+
/** The hop passed through an unmodeled call (flag `c`, KTD5). */
|
|
105
|
+
readonly viaCall: boolean;
|
|
106
|
+
}
|
|
107
|
+
export interface DecodedTaintPath {
|
|
108
|
+
readonly ok: true;
|
|
109
|
+
readonly version: string;
|
|
110
|
+
/** Finding sink category from the `;<kind>` header; absent when not encoded. */
|
|
111
|
+
readonly kind?: string;
|
|
112
|
+
/** Ordered source→sink hops (a PREFIX when `truncated`). */
|
|
113
|
+
readonly hops: readonly DecodedTaintHop[];
|
|
114
|
+
/** Path incomplete (trailing `|~`) — informational, NOT an error. */
|
|
115
|
+
readonly truncated: boolean;
|
|
116
|
+
}
|
|
117
|
+
/** Typed parse failure — the decoder never throws. */
|
|
118
|
+
export interface TaintPathDecodeFailure {
|
|
119
|
+
readonly ok: false;
|
|
120
|
+
readonly error: string;
|
|
121
|
+
}
|
|
122
|
+
export type TaintPathDecodeResult = DecodedTaintPath | TaintPathDecodeFailure;
|
|
123
|
+
/**
|
|
124
|
+
* Encode an ordered hop list into the versioned `reason` wire string.
|
|
125
|
+
* Deterministic; never throws. See the module doc for the format and the
|
|
126
|
+
* three truncation triggers (caller flag, unencodable hop, byte cap).
|
|
127
|
+
*/
|
|
128
|
+
export declare function encodeTaintPath(hops: readonly TaintPathHopInput[], options?: EncodeTaintPathOptions): EncodedTaintPath;
|
|
129
|
+
/**
|
|
130
|
+
* Decode a `reason` wire string. Returns a typed failure for anything that is
|
|
131
|
+
* not a well-formed version-`1` path — never throws. A truncated path decodes
|
|
132
|
+
* `ok: true` with `truncated: true` ("path incomplete", per KTD6).
|
|
133
|
+
*/
|
|
134
|
+
export declare function decodeTaintPath(reason: unknown): TaintPathDecodeResult;
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Taint-path reason codec (#2083 M3 U4/U6, plan KTD6).
|
|
3
|
+
*
|
|
4
|
+
* THE one shared encoder/decoder for the hop-encoded `reason` carried on
|
|
5
|
+
* persisted `TAINTED` edges: the U4 emit path writes it, the U6 MCP `explain`
|
|
6
|
+
* tool reads it. Two hand-rolled copies of a wire format drift — both sides
|
|
7
|
+
* MUST import from here.
|
|
8
|
+
*
|
|
9
|
+
* ## Wire format (version `1`)
|
|
10
|
+
*
|
|
11
|
+
* ```
|
|
12
|
+
* 1[;<kind>]|<name>:<line>[:<flags>]|<name>:<line>[:<flags>]|…[|~]
|
|
13
|
+
* ```
|
|
14
|
+
*
|
|
15
|
+
* - One-character version prefix (`TAINT_PATH_CODEC_VERSION`), then an
|
|
16
|
+
* OPTIONAL `;<kind>` header segment, then ordered source→sink hops, each
|
|
17
|
+
* `variable:line[:flags]`.
|
|
18
|
+
* - `kind` is the finding's sink category (`SinkKind`, e.g.
|
|
19
|
+
* `command-injection`). It rides the reason because it is the ONLY
|
|
20
|
+
* persisted channel: the CodeRelation columns are
|
|
21
|
+
* `type/confidence/reason/step` — `step` is INT32 and the emit-time edge id
|
|
22
|
+
* (which embeds the kind) is not a stored column. The U6 `explain` tool
|
|
23
|
+
* reads it for finding classification. Charset `[a-z0-9-]` (printable
|
|
24
|
+
* ASCII, disjoint from every structural delimiter); `;` itself is printable
|
|
25
|
+
* ASCII and never appears in hop names (identifier charset) or flags.
|
|
26
|
+
* U6 deviation note: this header was added by U6 WITHIN version `1` —
|
|
27
|
+
* U4 and U6 ship in the same release, so no reason string without the
|
|
28
|
+
* header was ever persisted by a released build; the decoder still accepts
|
|
29
|
+
* header-less strings (`kind` simply decodes as `undefined`).
|
|
30
|
+
* - `flags` is a lowercase-letter set; only `c` (= the hop passed through an
|
|
31
|
+
* unmodeled call, KTD5 `viaCall`) is defined today — the rest of the
|
|
32
|
+
* alphabet is RESERVED, and the decoder accepts unknown flag letters so a
|
|
33
|
+
* future writer's output stays decodable.
|
|
34
|
+
* - A trailing `|~` segment is the TRUNCATION MARKER: the encoded path is a
|
|
35
|
+
* source-side PREFIX of the real one (hop cap, byte cap, or an unencodable
|
|
36
|
+
* hop name). Decoders MUST report it as "path incomplete" — never an error.
|
|
37
|
+
*
|
|
38
|
+
* ## Delimiter / round-trip discipline (KTD6)
|
|
39
|
+
*
|
|
40
|
+
* Every structural character (`|`, `:`, `~`, digits, flag letters) is
|
|
41
|
+
* printable ASCII: `sanitizeUTF8` (csv-generator.ts) strips control
|
|
42
|
+
* characters, lone surrogates, and U+FFFE/FFFF — printable ASCII passes
|
|
43
|
+
* through byte-exact, so the encoding survives `escapeCSVField ∘
|
|
44
|
+
* sanitizeUTF8` and the DB load unchanged (pinned by the round-trip test).
|
|
45
|
+
* None of the delimiters can appear in a JS identifier.
|
|
46
|
+
*
|
|
47
|
+
* Hop names are identifier-charset by U1 construction (the harvest records
|
|
48
|
+
* binding names), but the encoder DEFENDS anyway: a hop whose name falls
|
|
49
|
+
* outside the safe charset (or whose line is not a non-negative integer) is
|
|
50
|
+
* never emitted — encoding stops at the offending hop and sets the truncation
|
|
51
|
+
* marker, preserving the prefix-of-the-true-path invariant rather than
|
|
52
|
+
* corrupting the format. (`#` is in the charset: JS private names are
|
|
53
|
+
* `#field`, and the propagation engine's fallback hop names are `#<idx>`.)
|
|
54
|
+
*
|
|
55
|
+
* The byte cap (`TAINT_REASON_MAX_BYTES`, KTD6's "absolute reason-byte cap")
|
|
56
|
+
* bounds the persisted reason column regardless of hop caps: overflow drops
|
|
57
|
+
* TRAILING hops (keeps the source side) and sets the marker. All structural
|
|
58
|
+
* chars and valid names are single-byte ASCII, so `string.length` IS the
|
|
59
|
+
* byte length.
|
|
60
|
+
*/
|
|
61
|
+
/** One-character format version prefix. Bump on any wire-format change. */
|
|
62
|
+
export const TAINT_PATH_CODEC_VERSION = '1';
|
|
63
|
+
/**
|
|
64
|
+
* Absolute cap on the encoded reason's byte length (KTD6). 4096 comfortably
|
|
65
|
+
* holds ~100 hops of realistic identifiers — far beyond the default hop cap
|
|
66
|
+
* (32) — while bounding the persisted column even at `maxHops: 0` (unlimited).
|
|
67
|
+
*/
|
|
68
|
+
export const TAINT_REASON_MAX_BYTES = 4096;
|
|
69
|
+
/** The truncation-marker segment content (rides as a trailing `|~`). */
|
|
70
|
+
export const TAINT_PATH_TRUNCATION_MARKER = '~';
|
|
71
|
+
/**
|
|
72
|
+
* Safe hop-name charset: ASCII identifier characters plus `#` (JS private
|
|
73
|
+
* names / the propagation engine's `#<idx>` fallback). Deliberately ASCII-only
|
|
74
|
+
* — a Unicode identifier is VALID JS but is skipped (truncation marker) rather
|
|
75
|
+
* than risking a `sanitizeUTF8` byte change breaking decode (defensive
|
|
76
|
+
* simplification; documented FN on path completeness, never on the finding).
|
|
77
|
+
*/
|
|
78
|
+
const SAFE_NAME = /^[A-Za-z0-9_$#]+$/;
|
|
79
|
+
/** Decoder-side flags charset — `c` defined, the rest reserved (see module doc). */
|
|
80
|
+
const FLAGS = /^[a-z]*$/;
|
|
81
|
+
/**
|
|
82
|
+
* Kind-header charset: lowercase + digits + hyphen — covers every `SinkKind`
|
|
83
|
+
* label and stays disjoint from the structural delimiters (`;|:~`).
|
|
84
|
+
*/
|
|
85
|
+
const SAFE_KIND = /^[a-z0-9-]+$/;
|
|
86
|
+
/**
|
|
87
|
+
* Encode an ordered hop list into the versioned `reason` wire string.
|
|
88
|
+
* Deterministic; never throws. See the module doc for the format and the
|
|
89
|
+
* three truncation triggers (caller flag, unencodable hop, byte cap).
|
|
90
|
+
*/
|
|
91
|
+
export function encodeTaintPath(hops, options) {
|
|
92
|
+
// Kind header (defensively validated — see EncodeTaintPathOptions.kind).
|
|
93
|
+
const kindHeader = typeof options?.kind === 'string' && SAFE_KIND.test(options.kind) ? `;${options.kind}` : '';
|
|
94
|
+
// Floor: version char + kind header + room for the marker — a smaller cap
|
|
95
|
+
// could not hold even the empty truncated path. The header is identity
|
|
96
|
+
// material (finding classification), so it is never sacrificed to the byte
|
|
97
|
+
// cap; trailing hops are.
|
|
98
|
+
const maxBytes = Math.max(options?.maxBytes ?? TAINT_REASON_MAX_BYTES, TAINT_PATH_CODEC_VERSION.length + kindHeader.length + 2);
|
|
99
|
+
let truncated = options?.truncated === true;
|
|
100
|
+
const segments = [];
|
|
101
|
+
let total = TAINT_PATH_CODEC_VERSION.length + kindHeader.length;
|
|
102
|
+
for (const hop of hops) {
|
|
103
|
+
if (typeof hop.name !== 'string' ||
|
|
104
|
+
!SAFE_NAME.test(hop.name) ||
|
|
105
|
+
!Number.isInteger(hop.line) ||
|
|
106
|
+
hop.line < 0) {
|
|
107
|
+
// Unencodable hop: drop it AND everything after it so the emitted hops
|
|
108
|
+
// stay a faithful source-side prefix (a silent mid-path gap would lie).
|
|
109
|
+
truncated = true;
|
|
110
|
+
break;
|
|
111
|
+
}
|
|
112
|
+
const segment = `|${hop.name}:${hop.line}${hop.viaCall === true ? ':c' : ''}`;
|
|
113
|
+
if (total + segment.length > maxBytes) {
|
|
114
|
+
truncated = true;
|
|
115
|
+
break;
|
|
116
|
+
}
|
|
117
|
+
segments.push(segment);
|
|
118
|
+
total += segment.length;
|
|
119
|
+
}
|
|
120
|
+
if (truncated) {
|
|
121
|
+
// Make room for the trailing `|~` marker (drop trailing hops as needed).
|
|
122
|
+
while (segments.length > 0 && total + 2 > maxBytes) {
|
|
123
|
+
total -= segments.pop().length;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
const reason = TAINT_PATH_CODEC_VERSION +
|
|
127
|
+
kindHeader +
|
|
128
|
+
segments.join('') +
|
|
129
|
+
(truncated ? `|${TAINT_PATH_TRUNCATION_MARKER}` : '');
|
|
130
|
+
return { reason, truncated };
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Decode a `reason` wire string. Returns a typed failure for anything that is
|
|
134
|
+
* not a well-formed version-`1` path — never throws. A truncated path decodes
|
|
135
|
+
* `ok: true` with `truncated: true` ("path incomplete", per KTD6).
|
|
136
|
+
*/
|
|
137
|
+
export function decodeTaintPath(reason) {
|
|
138
|
+
if (typeof reason !== 'string' || reason.length === 0) {
|
|
139
|
+
return { ok: false, error: 'empty or non-string reason' };
|
|
140
|
+
}
|
|
141
|
+
const version = reason[0];
|
|
142
|
+
if (version !== TAINT_PATH_CODEC_VERSION) {
|
|
143
|
+
return { ok: false, error: `unsupported taint-path version '${version}'` };
|
|
144
|
+
}
|
|
145
|
+
let body = reason.slice(1);
|
|
146
|
+
// Optional `;<kind>` header segment (finding sink category — see module doc).
|
|
147
|
+
let kind;
|
|
148
|
+
if (body.startsWith(';')) {
|
|
149
|
+
const headerEnd = body.indexOf('|');
|
|
150
|
+
kind = headerEnd === -1 ? body.slice(1) : body.slice(1, headerEnd);
|
|
151
|
+
if (!SAFE_KIND.test(kind)) {
|
|
152
|
+
return { ok: false, error: `invalid kind header '${kind}'` };
|
|
153
|
+
}
|
|
154
|
+
body = headerEnd === -1 ? '' : body.slice(headerEnd);
|
|
155
|
+
}
|
|
156
|
+
const hops = [];
|
|
157
|
+
if (body.length === 0)
|
|
158
|
+
return { ok: true, version, ...(kind ? { kind } : {}), hops, truncated: false };
|
|
159
|
+
if (!body.startsWith('|')) {
|
|
160
|
+
return { ok: false, error: 'malformed body: expected a hop separator after the version' };
|
|
161
|
+
}
|
|
162
|
+
const parts = body.slice(1).split('|');
|
|
163
|
+
let truncated = false;
|
|
164
|
+
for (let i = 0; i < parts.length; i++) {
|
|
165
|
+
const part = parts[i];
|
|
166
|
+
if (part === TAINT_PATH_TRUNCATION_MARKER) {
|
|
167
|
+
if (i !== parts.length - 1) {
|
|
168
|
+
return { ok: false, error: 'truncation marker not in trailing position' };
|
|
169
|
+
}
|
|
170
|
+
truncated = true;
|
|
171
|
+
break;
|
|
172
|
+
}
|
|
173
|
+
const fields = part.split(':');
|
|
174
|
+
if (fields.length < 2 || fields.length > 3) {
|
|
175
|
+
return { ok: false, error: `malformed hop segment '${part}'` };
|
|
176
|
+
}
|
|
177
|
+
const [name, lineStr, flags = ''] = fields;
|
|
178
|
+
if (!SAFE_NAME.test(name)) {
|
|
179
|
+
return { ok: false, error: `invalid hop variable '${name}'` };
|
|
180
|
+
}
|
|
181
|
+
if (!/^\d+$/.test(lineStr)) {
|
|
182
|
+
return { ok: false, error: `invalid hop line '${lineStr}'` };
|
|
183
|
+
}
|
|
184
|
+
if (!FLAGS.test(flags)) {
|
|
185
|
+
return { ok: false, error: `invalid hop flags '${flags}'` };
|
|
186
|
+
}
|
|
187
|
+
hops.push({ variable: name, line: Number(lineStr), viaCall: flags.includes('c') });
|
|
188
|
+
}
|
|
189
|
+
return { ok: true, version, ...(kind ? { kind } : {}), hops, truncated };
|
|
190
|
+
}
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure intra-procedural taint propagation engine (#2083 M3 U3).
|
|
3
|
+
*
|
|
4
|
+
* Forward taint reachability over one function's reaching-definition facts
|
|
5
|
+
* (M2 `computeReachingDefs`) and matched taint sites (U2 `matchFunctionSites`)
|
|
6
|
+
* — sources in, findings + sanitizer kills + coverage status out. PURE AND
|
|
7
|
+
* DETERMINISTIC, mirroring the reaching-defs contract: no graph, no I/O, no
|
|
8
|
+
* logger; insertion-ordered worklist; explicitly sorted outputs; snapshot
|
|
9
|
+
* tests and content-derived edge ids (U4) rely on it.
|
|
10
|
+
*
|
|
11
|
+
* PRECONDITIONS: the caller gates the CFG through `hasTaintSafeSites`
|
|
12
|
+
* (taint/site-safety.ts) and the emit-safety checks before calling — this
|
|
13
|
+
* module dereferences binding/site/statement indices without re-validating.
|
|
14
|
+
*
|
|
15
|
+
* ## The two-rule model (plan HTD)
|
|
16
|
+
*
|
|
17
|
+
* - **Rule (b), statement-local:** a matched SOURCE occurrence (member read)
|
|
18
|
+
* whose intra-statement occurrence path — the member-read's `parent` chain —
|
|
19
|
+
* reaches a matched SINK argument position produces an immediate single-hop
|
|
20
|
+
* finding (`exec(req.body)`). The same statement SEEDS taint: every binding
|
|
21
|
+
* the statement defines becomes tainted (see the precision floor below).
|
|
22
|
+
* - **Rule (a), worklist:** for each tainted `(binding, defPoint)`, every
|
|
23
|
+
* def→use fact delivers the taint to a use statement, where occurrences of
|
|
24
|
+
* the binding in matched sink argument positions produce findings and the
|
|
25
|
+
* statement's own defs are tainted onward. The fact graph contains genuine
|
|
26
|
+
* cycles (loop back-edges, same-statement self-facts) — the visited-set
|
|
27
|
+
* discipline below is load-bearing, not defensive.
|
|
28
|
+
*
|
|
29
|
+
* ## Sanitizer semantics — the KIND-SET exclusion model (KTD4, sharpened)
|
|
30
|
+
*
|
|
31
|
+
* The plan sketches a binary kill; this module implements the strictly more
|
|
32
|
+
* precise SOUND refinement: a taint carries a set of *excluded* (neutralized)
|
|
33
|
+
* `SinkKind`s accumulated through sanitizer hops, and a sink fires unless its
|
|
34
|
+
* kind is in the taint's exclusion set. A binary kill is the special case
|
|
35
|
+
* where the sanitizer neutralizes the sink's kind; the kind-set model
|
|
36
|
+
* additionally keeps `const b = escape(req.body); db.query(b)` a FINDING
|
|
37
|
+
* (an HTML escaper does not neutralize SQL — un-tainting `b` outright would
|
|
38
|
+
* be a suppressed live injection, the forbidden false-negative direction)
|
|
39
|
+
* while still suppressing `res.send(b)` (xss IS neutralized).
|
|
40
|
+
*
|
|
41
|
+
* - **Occurrence interposition (KTD4a):** evaluated over the U1 site
|
|
42
|
+
* structure. An occurrence reaching a sink arg / def-feeding position
|
|
43
|
+
* through a matched sanitizer site accumulates that sanitizer's
|
|
44
|
+
* `neutralizes` kinds on that PATH; a direct occurrence contributes the
|
|
45
|
+
* empty set. Per-position narrowing (`entry.args`) is respected; receiver
|
|
46
|
+
* flow through a sanitizer is NOT neutralized (the receiver is not the
|
|
47
|
+
* sanitized payload), and spread/template positions are never neutralized
|
|
48
|
+
* (position unprovable) — both sound-direction choices (under-kill).
|
|
49
|
+
* - **Intersection over paths:** a def fed by several occurrence paths
|
|
50
|
+
* excludes a kind only when EVERY path neutralizes it
|
|
51
|
+
* (`const c = cond ? escape(b) : b` taints `c` with NO exclusions — the
|
|
52
|
+
* direct arm's ∅ intersects everything away). Equally, a taint re-derived
|
|
53
|
+
* along a second route keeps the INTERSECTION of the exclusion sets and is
|
|
54
|
+
* re-processed whenever the set SHRINKS — a less-neutralized taint is
|
|
55
|
+
* strictly more dangerous. Exclusion sets only shrink over a finite
|
|
56
|
+
* lattice, so the worklist terminates.
|
|
57
|
+
* - **Kill locality (KTD4b):** a kill applies to the def the sanitizer
|
|
58
|
+
* produces (`SiteRecord.resultDefs`) only; the flowing binding's own taint
|
|
59
|
+
* is untouched (`const c = escape(b); exec(b)` still finds `b`'s flow).
|
|
60
|
+
* `x = escape(x)` works because taint keys on the DEF POINT: the
|
|
61
|
+
* sanitizer statement's def enters the set with the sanitizer's kinds
|
|
62
|
+
* excluded, while the seed def keeps flowing wherever the CFG still
|
|
63
|
+
* carries it (zero-iteration loops, conditional sanitizers — may-path
|
|
64
|
+
* mechanics need no special handling here, kills are absent from facts).
|
|
65
|
+
*
|
|
66
|
+
* ## Statement-coalescing precision floor (documented FP)
|
|
67
|
+
*
|
|
68
|
+
* Statement facts conflate multi-declarator statements: a statement that
|
|
69
|
+
* uses tainted `b` and defines `c` taints `c` with NO exclusions even when
|
|
70
|
+
* the two are textually unrelated (`const a = clean(z), b = g(t)` floor-
|
|
71
|
+
* taints `a` from `t` — pinned by a test). The per-declarator `resultDefs`
|
|
72
|
+
* precision narrows the EXCLUSION computation (and powers kills) only — a
|
|
73
|
+
* def in a call's `resultDefs` is fed exactly through that call, so its
|
|
74
|
+
* exclusions come from the paths into it; when the tainted input provably
|
|
75
|
+
* never flows into that call, the floor still taints the def (sound) but
|
|
76
|
+
* records no kill (a kill requires evidence of flow through the sanitizer).
|
|
77
|
+
*
|
|
78
|
+
* ## Propagate-through (KTD5)
|
|
79
|
+
*
|
|
80
|
+
* Taint in any argument or in the receiver of an UNMODELED call flows to
|
|
81
|
+
* the call's result defs, marked `viaCall` on the hop so `explain` can
|
|
82
|
+
* express lower confidence. An occurrence that reaches the unmodeled call
|
|
83
|
+
* only through a sanitizer carries the neutralization through
|
|
84
|
+
* (`const y = unknownFn(escape(b))` excludes the sanitizer's kinds — the
|
|
85
|
+
* plan's deliberate precision choice over flat-conservative).
|
|
86
|
+
*
|
|
87
|
+
* ## Kills output
|
|
88
|
+
*
|
|
89
|
+
* `kills` records every sanitizer that ACTUALLY neutralized kinds on a
|
|
90
|
+
* flowing taint — U4 emits `SANITIZES` edges from them. Two shapes share the
|
|
91
|
+
* record: result-def kills (`killedDef` = the def the sanitizer produces;
|
|
92
|
+
* `bindingIdx` = that def's binding) and value-position interposition kills
|
|
93
|
+
* (`exec(escape(x))` — no def exists; `killedDef` = the sink statement's own
|
|
94
|
+
* point, `bindingIdx` = the interposed binding). Interposition kills are
|
|
95
|
+
* recorded only when the (input, sink, position) produced no finding — a
|
|
96
|
+
* bypassed sanitizer (`exec(x + escape(x))`) killed nothing.
|
|
97
|
+
*/
|
|
98
|
+
import type { FunctionCfg } from '../cfg/types.js';
|
|
99
|
+
import type { FunctionDefUse, ProgramPoint } from '../cfg/reaching-defs.js';
|
|
100
|
+
import type { FunctionSiteMatches } from './match.js';
|
|
101
|
+
import type { SinkKind, SourceKind } from './source-sink-config.js';
|
|
102
|
+
/**
|
|
103
|
+
* Default per-function findings cap (U5 config resolution; cfg/emit.ts
|
|
104
|
+
* DEFAULT_* pattern). Resolved into the RepoMeta `pdg` stamp by
|
|
105
|
+
* `resolvePdgConfig` so a cap change trips full writeback; `0` = unlimited
|
|
106
|
+
* is preserved like the other pdg caps. 200 is generous — a real function
|
|
107
|
+
* with more deduped source→sink findings is a fixture or a disaster, and
|
|
108
|
+
* the truncation is deterministic + counted (`droppedFindings`).
|
|
109
|
+
*/
|
|
110
|
+
export declare const DEFAULT_PDG_MAX_TAINT_FINDINGS_PER_FUNCTION = 200;
|
|
111
|
+
/**
|
|
112
|
+
* Default per-finding hop cap (U5; joins the RepoMeta `pdg` stamp like the
|
|
113
|
+
* findings cap). Bounds the persisted `reason` hop encoding (KTD6 pins the
|
|
114
|
+
* hop cap in config); 32 intra-procedural def→use hops is far beyond any
|
|
115
|
+
* legible path — overflow keeps the source-side prefix and sets
|
|
116
|
+
* `hopsTruncated`, parsed downstream as "path incomplete", never an error.
|
|
117
|
+
*/
|
|
118
|
+
export declare const DEFAULT_PDG_MAX_TAINT_HOPS = 32;
|
|
119
|
+
export interface TaintLimits {
|
|
120
|
+
/**
|
|
121
|
+
* Maximum findings per function AFTER dedup; the sorted finding list is
|
|
122
|
+
* truncated deterministically and the overflow counted in
|
|
123
|
+
* `droppedFindings`. `undefined`/0 ⇒ unlimited.
|
|
124
|
+
*/
|
|
125
|
+
readonly maxFindingsPerFunction?: number;
|
|
126
|
+
/**
|
|
127
|
+
* Maximum hops retained per finding (source-side prefix kept); overflow
|
|
128
|
+
* sets `hopsTruncated`. `undefined`/0 ⇒ unlimited.
|
|
129
|
+
*/
|
|
130
|
+
readonly maxHops?: number;
|
|
131
|
+
}
|
|
132
|
+
/** One hop of a finding's path — enough for U4's reason codec (name, line, flag). */
|
|
133
|
+
export interface TaintHop {
|
|
134
|
+
/** Index into the function's binding table. */
|
|
135
|
+
readonly bindingIdx: number;
|
|
136
|
+
/** Resolved binding name (carried so U4 never re-joins the table). */
|
|
137
|
+
readonly name: string;
|
|
138
|
+
readonly point: ProgramPoint;
|
|
139
|
+
/** The value passed through an unmodeled call to get here (KTD5). */
|
|
140
|
+
readonly viaCall?: boolean;
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* The KTD6 rule-(b) source identity material: the matched member-read
|
|
144
|
+
* occurrence itself — statement point + site index + object/property. For
|
|
145
|
+
* worklist findings this is the ROOT source the taint chain was seeded from.
|
|
146
|
+
*/
|
|
147
|
+
export interface TaintSourceOccurrence {
|
|
148
|
+
readonly point: ProgramPoint;
|
|
149
|
+
/** Index into the source statement's `sites` array. */
|
|
150
|
+
readonly siteIndex: number;
|
|
151
|
+
readonly objectBindingIdx: number;
|
|
152
|
+
readonly property: string;
|
|
153
|
+
readonly kind: SourceKind;
|
|
154
|
+
}
|
|
155
|
+
/** The sink side of a finding's identity: point + site + argument + binding. */
|
|
156
|
+
export interface TaintSinkOccurrence {
|
|
157
|
+
readonly point: ProgramPoint;
|
|
158
|
+
/** Index into the sink statement's `sites` array. */
|
|
159
|
+
readonly siteIndex: number;
|
|
160
|
+
/** Matched sink argument position the tainted occurrence landed in. */
|
|
161
|
+
readonly argIndex: number;
|
|
162
|
+
/**
|
|
163
|
+
* The binding whose occurrence reached the sink position (for rule-(b)
|
|
164
|
+
* findings: the source member-read's object binding).
|
|
165
|
+
*/
|
|
166
|
+
readonly bindingIdx: number;
|
|
167
|
+
/** The matched sink entry's `name` (e.g. `exec`) — finding classification. */
|
|
168
|
+
readonly entryName: string;
|
|
169
|
+
}
|
|
170
|
+
export interface TaintFinding {
|
|
171
|
+
readonly sinkKind: SinkKind;
|
|
172
|
+
readonly source: TaintSourceOccurrence;
|
|
173
|
+
readonly sink: TaintSinkOccurrence;
|
|
174
|
+
/**
|
|
175
|
+
* Ordered source→sink path, one path per finding (the CodeQL
|
|
176
|
+
* `--max-paths=1` convention): the taint chain's def hops followed by the
|
|
177
|
+
* sink-use hop. Rule-(b) findings carry the single sink-statement hop.
|
|
178
|
+
*/
|
|
179
|
+
readonly hops: readonly TaintHop[];
|
|
180
|
+
readonly hopsTruncated?: boolean;
|
|
181
|
+
}
|
|
182
|
+
/** A sanitizer that neutralized kinds on a flowing taint — U4's SANITIZES rows. */
|
|
183
|
+
export interface SanitizerKill {
|
|
184
|
+
/** Statement point of the sanitizer call site. */
|
|
185
|
+
readonly sanitizer: ProgramPoint;
|
|
186
|
+
/**
|
|
187
|
+
* The killed def's point (result-def kills — always the sanitizer's own
|
|
188
|
+
* statement in the intra-statement model) or the suppressed sink
|
|
189
|
+
* statement's point (value-position interposition kills).
|
|
190
|
+
*/
|
|
191
|
+
readonly killedDef: ProgramPoint;
|
|
192
|
+
/** The killed def's binding, or the interposed binding for value-position kills. */
|
|
193
|
+
readonly bindingIdx: number;
|
|
194
|
+
/** Sorted, deduped kinds the sanitizer neutralized at that position. */
|
|
195
|
+
readonly neutralized: readonly SinkKind[];
|
|
196
|
+
}
|
|
197
|
+
export interface FunctionTaintResult {
|
|
198
|
+
/**
|
|
199
|
+
* `computed` — full propagation ran.
|
|
200
|
+
* `coverage-gap` — the solver result was not `computed`; the function is
|
|
201
|
+
* skipped for findings entirely (R4: never partially
|
|
202
|
+
* analyzed), `gapReason` carries the solver status.
|
|
203
|
+
*/
|
|
204
|
+
readonly status: 'computed' | 'coverage-gap';
|
|
205
|
+
readonly gapReason?: 'truncated' | 'overflow' | 'no-facts';
|
|
206
|
+
/** Deduped (KTD6 identity), deterministically sorted, capped. */
|
|
207
|
+
readonly findings: readonly TaintFinding[];
|
|
208
|
+
readonly kills: readonly SanitizerKill[];
|
|
209
|
+
/** Findings dropped by `maxFindingsPerFunction` (post-dedup). */
|
|
210
|
+
readonly droppedFindings: number;
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Compute taint flows for one function. See the module doc for the two-rule
|
|
214
|
+
* model, the kind-set exclusion semantics, and the precision floor.
|
|
215
|
+
*/
|
|
216
|
+
export declare function computeTaintFlows(cfg: FunctionCfg, defUse: FunctionDefUse, matches: FunctionSiteMatches, limits?: TaintLimits): FunctionTaintResult;
|