feat(ingest): multi-language white-box ingest (web-tree-sitter) (#75)

* chore(ingest): pin web-tree-sitter@0.25.10 + tree-sitter-wasms@0.1.13; add multilang-ingest design docs

* feat(ingest): multi-language grammar registry (web-tree-sitter, 8 langs, fail-open); 100% cov

* feat(ingest): language-aware param-name splitter (py/go/ts/java/c); 100% cov

* feat(ingest): tree-sitter CodeBlock extractor with wall-clock parse budget + fail-open

* feat(ingest): cross-lang sinks + multilang config + wire tree-sitter into ingestRepository (sync)

* feat(ingest): repoint white-box production callers to multilang config + prod regression test

* server-init-wiring: load multi-language grammars once at bootstrap

initGrammars() runs after LLM init, before app.listen. Internally
fail-open (never rejects): on failure the grammar registry stays empty
and ingest falls back to the Python regex parser.

* adversarial-tests: hostile-input hardening for untrusted target source

Malformed per-language source, wrong-language content, binary/non-UTF8,
empty, unicode identifiers, 20k-func large file (wall-clock budget),
deeply nested input, and a symlink-loop crawl — all must fail-open, never
throw or spin. Drives parseFileMultiLang + ingestRepository/crawl.

* readme-status + docs: flip white-box status to multi-language ingest

README source-code rows now state web-tree-sitter multi-language ingest
(fail-open to Python regex). whitebox.ts scope caveat + no-source error
message updated off the stale Python-only wording.

* coverage-setup: enforce 100% coverage gate on the three new ingest files

vitest.config.ts adds a coverage-only block (v8, perFile thresholds=100)
scoped to ts-grammars.ts, param-split.ts, ts-parse.ts. Test discovery and
env stay on defaults. Edited files gate on new-branch coverage via the
regression suites; the server.ts initGrammars bootstrap line is the
documented smoke-covered carve-out.

* review: fix WASM heap leak, language-scoped fail-open, sink recall/precision

Adversarial security + strict code review (two independent subagents)
returned one blocking bug and several correctness findings; remediated:

BLOCKING #1 — native WASM memory leak: web-tree-sitter Tree/Parser objects
were never .delete()'d. Both reviewers reproduced a process-global wedge:
after ~7 large parses the shared WASM heap exhausts, every subsequent parse
aborts (caught by fail-open), and non-Python ingest silently returns []
forever. Fix: free the tree in a finally; delete the parser in resetParser.
Regression test parses ~8MB through the real shared parser 12x.

regex, which mis-parses brace/class syntax into corrupt blocks (wrong line
spans, lost methods). Now non-.py fail-open returns [] (honest absence); the
Python regex only ever runs on .py. Isolated ts-parse-fallback test.

http(s).request, axios(config), C exec-family); negative lookbehind stops
bare system(/popen(/fetch( from matching Python receivers (os.system etc.
still matched, corpus rankings unchanged). Recall+precision tests added.

operator sees non-Python degradation.

(one-line call-graph edges, no decorators, C++ member methods).

* review round 2: fix sink-regex regressions, real timeout test, deterministic leak guard

Third review round (correctness + security re-attack + test-quality) against
the round-1 fixes surfaced issues in the fixes themselves; remediated:

- Sink-regex regressions I introduced: (1) my fetch lookbehind dropped
  window.fetch/this.fetch (top JS SSRF idiom) — reverted to \bfetch\(;
  (2) bare .Get/.Post/.Do flagged db.Get/cache.Get/queue.Do as SSRF —
  receiver-qualified to [Cc]lient. Recall+precision tests cover both;
  regex is ReDoS-free (timed).
- Placebo leak test: the 12x function-dense loop did NOT wedge the parser
  on machines with heap headroom (proven: passed with tree.delete removed).
  Replaced with a deterministic vi.spyOn(Tree.prototype,'delete') assertion,
  verified RED without the fix.
- Real (non-mocked) parse-timeout test: parseFileMultiLang gains an injectable
  budgetMs; a 0ms budget drives the real progressCallback cancellation to [].
- runWhiteboxAnalysis swap now has coverage (spy on orchestrator.run) so the
  2nd production caller can't silently revert to the Python-only config.
- Un-vacuoused weak tests: unicode now asserts the extracted block; nested/
  wrong-language wrapped in invariants() + timing bounds.
- CI now runs npm run test:coverage (the 100% gate was defined but unenforced).
- Security: documented maxFileBytes as the control bounding a single parse's
  peak WASM memory below the 2GB heap-abort ceiling.
- Stale 'fail-open to Python regex' docs fixed (ts-parse, whitebox, server,
  adversarial); circular-import note added; temp-dir cleanup; honest coverage
  wording in spec + config.

* docs(ingest): align fallback-contract text with the shipped language-scoped fail-open

Review feedback (#75, @jmagly): the design docs + one ts-grammars comment
still described the pre-implementation 'fall back to the Python regex'
model for non-Python files. The shipped contract is language-scoped: .py
uses parseFile; any non-.py file with a missing/unsupported/failed grammar
or a parse timeout returns [] (never the Python regex, which would mis-parse
brace/class syntax into corrupt blocks). Updated every stale reference in
multilang-ingest-spec.md, multilang-ingest-plan.md, and ts-grammars.ts so
the docs match the security boundary. Docs/comment only — no code change.

* fix(test): make the ReDoS wall-clock guard robust under coverage instrumentation

Review feedback (#75, @jmagly): the CI step this PR adds (npm run
test:coverage = vitest run --coverage) runs the full suite under V8
coverage, and that overhead pushes the pathological-brace DoS timing guard
in local-agent-tool-calling.test.ts:69 over its <300ms bound (320 < 300
fails on CI). Raise the bound to 1000ms — still 2x below the ~2100ms
quadratic-ReDoS regression it guards against, so it fails hard if the
regression returns, but no longer false-fails under coverage/slow CI.

Chose the reviewer's option (a) (robust bound) over scoping test:coverage,
so the gate stays a simple full-suite run and no test-file enumeration can
rot. Verified: npm run test:coverage is green and the 3 new files stay at
100%.

* fix(ingest): cover pointer-returning C functions

---------

Co-authored-by: Joseph Magly <1159087+jmagly@users.noreply.github.com>
This commit is contained in:
Lyubomir BozhinovandJoseph Magly authored and GitHub committed 2026-07-20 11:41:21 -04:00
1 parent 440dedd9c1
commit 151d3555c6
24 files changed
+1681 -25

No files matched your search

+232 -1
View File
@@ -23,7 +23,9 @@
"gradient-string": "^2.0.2",
"inquirer": "^9.2.15",
"ora": "^8.0.1",
"tsx": "^4.7.0"
"tree-sitter-wasms": "0.1.13",
"tsx": "^4.7.0",
"web-tree-sitter": "0.25.10"
},
"bin": {
"t3mp3st": "dist/cli.js",
@@ -37,6 +39,7 @@
"@types/gradient-string": "^1.1.5",
"@types/inquirer": "^9.0.7",
"@types/node": "^20.11.0",
"@vitest/coverage-v8": "4.1.9",
"eslint": "^9.39.2",
"typescript": "^5.3.3",
"typescript-eslint": "^8.51.0",
@@ -46,6 +49,66 @@
"node": ">=18.0.0"
}
},
"node_modules/@babel/helper-string-parser": {
"version": "7.29.7",
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz",
"integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-validator-identifier": {
"version": "7.29.7",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz",
"integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/parser": {
"version": "7.29.7",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz",
"integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/types": "^7.29.7"
},
"bin": {
"parser": "bin/babel-parser.js"
},
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/@babel/types": {
"version": "7.29.7",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz",
"integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-string-parser": "^7.29.7",
"@babel/helper-validator-identifier": "^7.29.7"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@bcoe/v8-coverage": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-1.0.2.tgz",
"integrity": "sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=18"
}
},
"node_modules/@colors/colors": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz",
@@ -768,6 +831,16 @@
"node": ">=18"
}
},
"node_modules/@jridgewell/resolve-uri": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
"integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/@jridgewell/sourcemap-codec": {
"version": "1.5.5",
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
@@ -775,6 +848,17 @@
"dev": true,
"license": "MIT"
},
"node_modules/@jridgewell/trace-mapping": {
"version": "0.3.31",
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
"integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@jridgewell/resolve-uri": "^3.1.0",
"@jridgewell/sourcemap-codec": "^1.4.14"
}
},
"node_modules/@modelcontextprotocol/sdk": {
"version": "1.29.0",
"resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.29.0.tgz",
@@ -1864,6 +1948,37 @@
"url": "https://opencollective.com/typescript-eslint"
}
},
"node_modules/@vitest/coverage-v8": {
"version": "4.1.9",
"resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.1.9.tgz",
"integrity": "sha512-G9/lgqibheLVBDRuya45EbsEXTYcWoSG+TLg7i2axuzx0Eq62eXn+aWXyaVdV5vKvFSWd6ywcX8hA7la9Pvu8g==",
"dev": true,
"license": "MIT",
"dependencies": {
"@bcoe/v8-coverage": "^1.0.2",
"@vitest/utils": "4.1.9",
"ast-v8-to-istanbul": "^1.0.0",
"istanbul-lib-coverage": "^3.2.2",
"istanbul-lib-report": "^3.0.1",
"istanbul-reports": "^3.2.0",
"magicast": "^0.5.2",
"obug": "^2.1.1",
"std-env": "^4.0.0-rc.1",
"tinyrainbow": "^3.1.0"
},
"funding": {
"url": "https://opencollective.com/vitest"
},
"peerDependencies": {
"@vitest/browser": "4.1.9",
"vitest": "4.1.9"
},
"peerDependenciesMeta": {
"@vitest/browser": {
"optional": true
}
}
},
"node_modules/@vitest/expect": {
"version": "4.1.9",
"resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.9.tgz",
@@ -2134,6 +2249,18 @@
"node": ">=12"
}
},
"node_modules/ast-v8-to-istanbul": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-1.0.4.tgz",
"integrity": "sha512-0bC0/4bTSrnwdhU3IsZDwEdojvuPrSg59OYZfKsLRtJZ0u8VBx9DebfqqG8bRdCC0I7vjgxmPi41P0lpkhJHtA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@jridgewell/trace-mapping": "^0.3.31",
"estree-walker": "^3.0.3",
"js-tokens": "^10.0.0"
}
},
"node_modules/atomically": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/atomically/-/atomically-2.1.0.tgz",
@@ -3600,6 +3727,13 @@
"node": ">=16.9.0"
}
},
"node_modules/html-escaper": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz",
"integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==",
"dev": true,
"license": "MIT"
},
"node_modules/http-errors": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz",
@@ -3988,6 +4122,45 @@
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
"license": "ISC"
},
"node_modules/istanbul-lib-coverage": {
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz",
"integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==",
"dev": true,
"license": "BSD-3-Clause",
"engines": {
"node": ">=8"
}
},
"node_modules/istanbul-lib-report": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz",
"integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==",
"dev": true,
"license": "BSD-3-Clause",
"dependencies": {
"istanbul-lib-coverage": "^3.0.0",
"make-dir": "^4.0.0",
"supports-color": "^7.1.0"
},
"engines": {
"node": ">=10"
}
},
"node_modules/istanbul-reports": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz",
"integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==",
"dev": true,
"license": "BSD-3-Clause",
"dependencies": {
"html-escaper": "^2.0.0",
"istanbul-lib-report": "^3.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/jose": {
"version": "6.1.3",
"resolved": "https://registry.npmjs.org/jose/-/jose-6.1.3.tgz",
@@ -3997,6 +4170,13 @@
"url": "https://github.com/sponsors/panva"
}
},
"node_modules/js-tokens": {
"version": "10.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-10.0.0.tgz",
"integrity": "sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==",
"dev": true,
"license": "MIT"
},
"node_modules/js-yaml": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.0.tgz",
@@ -4392,6 +4572,34 @@
"@jridgewell/sourcemap-codec": "^1.5.5"
}
},
"node_modules/magicast": {
"version": "0.5.3",
"resolved": "https://registry.npmjs.org/magicast/-/magicast-0.5.3.tgz",
"integrity": "sha512-pVKE4UdSQ7DvHzivsCIFx2BJn1mHG6KsyrFcaxFx6tONdneEuThrDx0Cj3AMg58KyN4pzYT+LHOotxDQDjNvkw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/parser": "^7.29.3",
"@babel/types": "^7.29.0",
"source-map-js": "^1.2.1"
}
},
"node_modules/make-dir": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz",
"integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==",
"dev": true,
"license": "MIT",
"dependencies": {
"semver": "^7.5.3"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/math-intrinsics": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
@@ -5517,6 +5725,15 @@
"node": ">=0.6"
}
},
"node_modules/tree-sitter-wasms": {
"version": "0.1.13",
"resolved": "https://registry.npmjs.org/tree-sitter-wasms/-/tree-sitter-wasms-0.1.13.tgz",
"integrity": "sha512-wT+cR6DwaIz80/vho3AvSF0N4txuNx/5bcRKoXouOfClpxh/qqrF4URNLQXbbt8MaAxeksZcZd1j8gcGjc+QxQ==",
"license": "Unlicense",
"dependencies": {
"tree-sitter-wasms": "^0.1.11"
}
},
"node_modules/ts-api-utils": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.3.0.tgz",
@@ -5870,6 +6087,20 @@
"defaults": "^1.0.3"
}
},
"node_modules/web-tree-sitter": {
"version": "0.25.10",
"resolved": "https://registry.npmjs.org/web-tree-sitter/-/web-tree-sitter-0.25.10.tgz",
"integrity": "sha512-Y09sF44/13XvgVKgO2cNDw5rGk6s26MgoZPXLESvMXeefBf7i6/73eFurre0IsTW6E14Y0ArIzhUMmjoc7xyzA==",
"license": "MIT",
"peerDependencies": {
"@types/emscripten": "^1.40.0"
},
"peerDependenciesMeta": {
"@types/emscripten": {
"optional": true
}
}
},
"node_modules/when-exit": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/when-exit/-/when-exit-2.1.5.tgz",