gitnexus 1.6.8-rc.21 → 1.6.8-rc.23

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.
Files changed (37) hide show
  1. package/dist/core/incremental/subgraph-extract.js +12 -1
  2. package/dist/core/ingestion/pipeline-phases/index.d.ts +1 -0
  3. package/dist/core/ingestion/pipeline-phases/index.js +1 -0
  4. package/dist/core/ingestion/pipeline-phases/taint-summaries.d.ts +30 -0
  5. package/dist/core/ingestion/pipeline-phases/taint-summaries.js +81 -0
  6. package/dist/core/ingestion/pipeline.d.ts +13 -0
  7. package/dist/core/ingestion/pipeline.js +5 -1
  8. package/dist/core/ingestion/scope-resolution/pipeline/phase.d.ts +7 -0
  9. package/dist/core/ingestion/scope-resolution/pipeline/phase.js +15 -0
  10. package/dist/core/ingestion/scope-resolution/pipeline/run.d.ts +17 -0
  11. package/dist/core/ingestion/scope-resolution/pipeline/run.js +37 -1
  12. package/dist/core/ingestion/taint/interproc-emit.d.ts +50 -0
  13. package/dist/core/ingestion/taint/interproc-emit.js +88 -0
  14. package/dist/core/ingestion/taint/interproc-solver.d.ts +110 -0
  15. package/dist/core/ingestion/taint/interproc-solver.js +312 -0
  16. package/dist/core/ingestion/taint/propagate.d.ts +1 -1
  17. package/dist/core/ingestion/taint/propagate.js +4 -9
  18. package/dist/core/ingestion/taint/source-sink-config.d.ts +13 -0
  19. package/dist/core/ingestion/taint/source-sink-config.js +24 -1
  20. package/dist/core/ingestion/taint/summary-harvest-driver.d.ts +56 -0
  21. package/dist/core/ingestion/taint/summary-harvest-driver.js +118 -0
  22. package/dist/core/ingestion/taint/summary-harvest.d.ts +89 -0
  23. package/dist/core/ingestion/taint/summary-harvest.js +537 -0
  24. package/dist/core/ingestion/taint/summary-model.d.ts +200 -0
  25. package/dist/core/ingestion/taint/summary-model.js +86 -0
  26. package/dist/core/lbug/lbug-adapter.d.ts +18 -0
  27. package/dist/core/lbug/lbug-adapter.js +55 -0
  28. package/dist/core/run-analyze.d.ts +7 -1
  29. package/dist/core/run-analyze.js +21 -1
  30. package/dist/mcp/local/local-backend.js +93 -10
  31. package/dist/mcp/tools.js +7 -6
  32. package/dist/storage/repo-manager.d.ts +10 -0
  33. package/hooks/antigravity/gitnexus-antigravity-hook.cjs +89 -3
  34. package/hooks/claude/gitnexus-hook.cjs +88 -4
  35. package/hooks/claude/hook-db-lock-probe.cjs +53 -21
  36. package/package.json +1 -1
  37. package/skills/gitnexus-taint-analysis.md +178 -0
@@ -0,0 +1,118 @@
1
+ /**
2
+ * Summary-harvest driver (#2084 M4 U1) — the in-phase orchestration that turns
3
+ * per-function CFGs into call-graph-keyed {@link FunctionSummary} objects.
4
+ *
5
+ * Runs inside the scope-resolution pdg window (alongside `emitFileTaint`),
6
+ * where both the live CFG side channel AND the structure-phase `Function` /
7
+ * `Method` graph nodes are available. For each emit-safe CFG it:
8
+ *
9
+ * 1. resolves the CFG's source anchor `(filePath, functionStartLine)` to its
10
+ * graph node id, so the summary speaks the call graph's language directly —
11
+ * the interprocedural fixpoint then joins summaries to `CALLS` edges by node
12
+ * id with no fragile re-derivation;
13
+ * 2. runs the pure {@link harvestFunctionSummary} over the same RD facts +
14
+ * matched sites the M3 taint pass uses;
15
+ * 3. stamps the own-facts `version` (#2084 review P1-1: callee-version
16
+ * composition is RESERVED — the fixpoint does not recompute it today).
17
+ *
18
+ * ## The Function↔CFG join (load-bearing)
19
+ *
20
+ * `FunctionCfg.functionStartLine` is 1-based (the TS visitor's `row + 1`);
21
+ * `Function`/`Method` node `startLine` is 0-based (`startPosition.row`). The
22
+ * join therefore looks up node start line `functionStartLine - 1`
23
+ * ({@link NODE_TO_CFG_LINE_OFFSET}). Function nodes carry no start column, so a
24
+ * `(filePath, startLine)` collision — two functions opening on one line,
25
+ * `{ a: () => x(), b: () => y() }` — is ambiguous: the CFG disambiguates with
26
+ * `functionStartColumn` but the node does not, so a colliding anchor is DROPPED
27
+ * (counted as `unresolved`) rather than risk attaching a summary to the wrong
28
+ * function. Rare in practice; the alternative (cross-wired summaries) is unsound.
29
+ */
30
+ import { computeReachingDefs } from '../cfg/reaching-defs.js';
31
+ import { DEFAULT_PDG_MAX_REACHING_DEF_FACTS_PER_FUNCTION } from '../cfg/emit.js';
32
+ import { buildTaintImportIndex, matchFunctionSites } from './match.js';
33
+ import { harvestFunctionSummary } from './summary-harvest.js';
34
+ import { ownFactsDigest, summaryVersion } from './summary-model.js';
35
+ /** `cfg.functionStartLine` (1-based) − this = the node's 0-based `startLine`. */
36
+ export const NODE_TO_CFG_LINE_OFFSET = 1;
37
+ /** Node labels that can own a CFG / be a `CALLS` endpoint. */
38
+ const FUNCTIONISH_LABELS = new Set(['Function', 'Method']);
39
+ export function buildFunctionNodeIndex(graph) {
40
+ const index = new Map();
41
+ const add = (node) => {
42
+ if (!FUNCTIONISH_LABELS.has(node.label))
43
+ return;
44
+ const filePath = node.properties.filePath;
45
+ const startLine = node.properties.startLine;
46
+ if (typeof filePath !== 'string' || typeof startLine !== 'number')
47
+ return;
48
+ let byLine = index.get(filePath);
49
+ if (!byLine) {
50
+ byLine = new Map();
51
+ index.set(filePath, byLine);
52
+ }
53
+ const ids = byLine.get(startLine);
54
+ if (ids)
55
+ ids.push(node.id);
56
+ else
57
+ byLine.set(startLine, [node.id]);
58
+ };
59
+ for (const node of graph.iterNodes())
60
+ add(node);
61
+ return index;
62
+ }
63
+ /** Resolve a CFG anchor to a unique functionish node id, or undefined. */
64
+ function resolveFnId(fnIndex, cfg) {
65
+ const byLine = fnIndex.get(cfg.filePath);
66
+ if (!byLine)
67
+ return undefined;
68
+ const ids = byLine.get(cfg.functionStartLine - NODE_TO_CFG_LINE_OFFSET);
69
+ // Unique match only — a same-line collision is unresolvable (no node column).
70
+ return ids && ids.length === 1 ? ids[0] : undefined;
71
+ }
72
+ /**
73
+ * Harvest summaries for one file's emit-safe CFGs. `cfgs` MUST already be
74
+ * `isEmitSafeCfg`-filtered (the same `wellFormed` array fed to `emitFileTaint`).
75
+ * Pure aside from the read-only graph lookup; never throws on valid input.
76
+ */
77
+ export function harvestFileSummaries(fnIndex, cfgs, parsedImports, spec, maxFacts = DEFAULT_PDG_MAX_REACHING_DEF_FACTS_PER_FUNCTION) {
78
+ const importIndex = buildTaintImportIndex(parsedImports);
79
+ const summaries = [];
80
+ let unresolved = 0;
81
+ let gaps = 0;
82
+ for (const cfg of cfgs) {
83
+ const fnId = resolveFnId(fnIndex, cfg);
84
+ if (fnId === undefined) {
85
+ unresolved++;
86
+ continue;
87
+ }
88
+ const defUse = computeReachingDefs(cfg, { maxFacts });
89
+ const matches = matchFunctionSites(cfg, spec, importIndex);
90
+ const harvested = harvestFunctionSummary(cfg, defUse, matches);
91
+ if (harvested.status !== 'computed') {
92
+ gaps++;
93
+ continue;
94
+ }
95
+ const facts = harvested.facts;
96
+ // Skip functions with NO taint behaviour at all — they cannot participate
97
+ // in any flow and would only bloat the fixpoint's working set.
98
+ if (facts.paramToReturn.length === 0 &&
99
+ facts.paramToCallArg.length === 0 &&
100
+ facts.paramToSink.length === 0 &&
101
+ facts.sourceToReturn.length === 0 &&
102
+ facts.sourceToCallArg.length === 0 &&
103
+ facts.callResults.length === 0) {
104
+ continue;
105
+ }
106
+ const digest = ownFactsDigest(facts);
107
+ summaries.push({
108
+ fnId,
109
+ filePath: cfg.filePath,
110
+ startLine: cfg.functionStartLine,
111
+ ...facts,
112
+ // Provisional own-only version; the fixpoint recomputes with callee
113
+ // versions once the call graph is condensed.
114
+ version: summaryVersion(digest, []),
115
+ });
116
+ }
117
+ return { summaries, unresolved, gaps };
118
+ }
@@ -0,0 +1,89 @@
1
+ /**
2
+ * Per-function taint SUMMARY harvest (#2084 M4 U1).
3
+ *
4
+ * Pure, deterministic derivation of one function's {@link FunctionSummary}
5
+ * facts from the SAME substrate the M3 intra-procedural pass consumes — the M2
6
+ * reaching-definition facts (`computeReachingDefs`) and the matched taint sites
7
+ * (`matchFunctionSites`). No graph, no I/O, no logger; mirrors the
8
+ * `computeReachingDefs` / `computeTaintFlows` contract (insertion-ordered
9
+ * worklist, explicitly sorted outputs) so snapshot tests and the version stamp
10
+ * stay stable. Runs IN-PHASE inside the scope-resolution pdg window where the
11
+ * CFG side channel is live (plan KTD1); the cross-function fixpoint that
12
+ * COMPOSES these summaries runs afterward over the complete call graph.
13
+ *
14
+ * ## What a summary captures (whole-parameter granularity)
15
+ *
16
+ * Seeding each formal parameter as taint and running forward reachability over
17
+ * the def→use facts yields four edge categories:
18
+ *
19
+ * - **param→return** — a param's value reaches a `return <expr>`. Return
20
+ * statements are identified structurally: the SOURCE block of every CFG edge
21
+ * of kind `return` terminates in the return jump (the M2 edge-kind
22
+ * invariant), so its last statement's `uses` are the returned bindings.
23
+ * - **param→callee-arg** — a param occurrence lands in argument position
24
+ * `argIndex` of a call at `callLine`. The fixpoint resolves `callLine` to a
25
+ * callee via the caller's `CALLS` edges and applies the callee's summary
26
+ * (TITO composition).
27
+ * - **param→sink** — a param reaches a modelled sink position (the partial
28
+ * flow that a cross-function source completes).
29
+ * - **source→return** — a modelled source read (`req.body`) reaches the return
30
+ * (a generative summary: calling the function yields tainted data).
31
+ *
32
+ * ## Soundness model (context-insensitive first cut)
33
+ *
34
+ * Onward propagation uses the M3 STATEMENT-LEVEL precision floor: a statement
35
+ * that uses a tainted binding taints all of its defs (and `mayDefs`). This is
36
+ * the same sound over-approximation M3 documents — it may over-taint
37
+ * (multi-declarator conflation) but never drops a real flow. Sanitizer
38
+ * `resultDefs` narrow the EXCLUSION set (a def produced by a matched sanitizer
39
+ * carries that sanitizer's neutralised `SinkKind`s), so a sanitised value does
40
+ * not trigger a downstream sink of the neutralised kind — the kind-set
41
+ * exclusion model, simplified to the result-def channel (occurrence
42
+ * interposition, field paths, and callbacks are deferred — plan KTD).
43
+ *
44
+ * The summary edges themselves (return / call-arg / sink) are recorded from
45
+ * ACTUAL binding occurrences (a tainted binding present in a return's uses, a
46
+ * call's arg list, or a matched sink position), never the floor — the floor
47
+ * governs only onward def-tainting, keeping the recorded edges precise.
48
+ *
49
+ * ## Known limitation — destructured / rest params (documented FN)
50
+ *
51
+ * Param indices are assigned by ORDINAL over the flattened param-binding list,
52
+ * which equals the FORMAL parameter position only when every param is a simple
53
+ * identifier. A destructured or rest param contributes several bindings (or
54
+ * shifts the count), so a simple param positioned AFTER one
55
+ * (`function f([a, b], x) { sink(x) }`) gets a summary port index that does not
56
+ * match the formal argument position the interprocedural solver joins against
57
+ * — a cross-function false negative for that function. The precise fix needs a
58
+ * formal-param index threaded from the worker harvest (`BindingEntry`), a
59
+ * cache-namespace-affecting change deferred with the other documented FN
60
+ * classes (closures, fields — see the taint skill). Functions with all-simple
61
+ * params (the common case) are unaffected.
62
+ */
63
+ import type { FunctionCfg } from '../cfg/types.js';
64
+ import { type FunctionDefUse } from '../cfg/reaching-defs.js';
65
+ import type { FunctionSiteMatches } from './match.js';
66
+ import type { CallResult, ParamToCallArg, ParamToReturn, ParamToSink, SourceToCallArg, SourceToReturn } from './summary-model.js';
67
+ /** The own-facts portion of a summary (fnId/version are added by the caller). */
68
+ export interface HarvestedSummaryFacts {
69
+ readonly paramCount: number;
70
+ readonly paramToReturn: readonly ParamToReturn[];
71
+ readonly paramToCallArg: readonly ParamToCallArg[];
72
+ readonly paramToSink: readonly ParamToSink[];
73
+ readonly sourceToReturn: readonly SourceToReturn[];
74
+ readonly sourceToCallArg: readonly SourceToCallArg[];
75
+ readonly callResults: readonly CallResult[];
76
+ }
77
+ export interface HarvestResult {
78
+ /** `computed` — facts derived; `coverage-gap` — the RD solver was not
79
+ * `computed`, so no summary is produced (consistent with M3 R4). */
80
+ readonly status: 'computed' | 'coverage-gap';
81
+ readonly gapReason?: FunctionDefUse['status'];
82
+ readonly facts: HarvestedSummaryFacts;
83
+ }
84
+ /**
85
+ * Harvest the summary facts for one function. PRECONDITION: `cfg` is
86
+ * `isEmitSafeCfg`-filtered and `defUse` was computed from it; sites are assumed
87
+ * `hasTaintSafeSites`-valid (the caller gates exactly as the M3 emit path does).
88
+ */
89
+ export declare function harvestFunctionSummary(cfg: FunctionCfg, defUse: FunctionDefUse, matches: FunctionSiteMatches): HarvestResult;