* 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>
* feat: update DeepSeek models to V4 Flash and V4 Pro
- Replace deprecated deepseek-chat / deepseek-reasoner with deepseek-v4-flash / deepseek-v4-pro
- Update base URL from api.deepseek.com/v1 to api.deepseek.com
- Update context window (1M) and max output (384K) per official API docs
- Old models deprecated 2026-07-24
* fix: resolveGeneralLLMConfig missing baseUrl + Admiral timeout 180s
resolveGeneralLLMConfig constructed a new LLMConfig but omitted
baseUrl, causing the adapter to fall back to api.openai.com/v1
regardless of provider — DeepSeek keys hit OpenAI (401).
Also raised Admiral preview timeout to 180s for thinking-mode models.
* fix: createTempestCommandInstance missing baseUrl for operator exec
Operator LLM calls also lacked baseUrl, same 401-for-DeepSeek bug
as resolveGeneralLLMConfig. Reads provider-specific baseUrl from config.
* fix(deepseek): migrate retired persisted defaults
---------
Co-authored-by: Joseph Magly <1159087+jmagly@users.noreply.github.com>
* feat(settings): Universal API Config panel with server-side model fetch (#90)
Consolidates provider configuration into one "Universal API Config" panel in
Settings — provider dropdown (OpenAI-compatible / Anthropic), base URL, API key,
model with a live "Fetch models" button, and max context tokens — above the
existing per-provider key sections.
Backward-compatible by design: the panel reads and writes the SAME state.settings
keys the individual sections already use (${provider}Key, selectedModel), so
nothing migrates and nothing is lost; the individual sections keep working and
stay in sync.
- src/config/provider-models.ts (new): listProviderModels queries a provider's
own model endpoint (OpenAI-compatible Bearer /models, Anthropic x-api-key
/v1/models, OpenRouter public list); resolveModels wraps it fail-open.
- POST /api/models (new route): server-side so the browser dodges provider CORS.
Binds key+baseUrl by source — a caller-supplied base URL must bring its own
key, so the server never lends its configured key to a body-chosen URL. Always
responds, failing open to the static AVAILABLE_MODELS list on a missing key,
network error, or unknown provider (incl. local-agent) — never hangs.
- Model options are built via the DOM API (createElement/textContent), never
innerHTML, so untrusted provider-returned model ids cannot inject markup.
Scope notes: augment-not-replace (removing the individual sections is deferred
to maintainer preference); base URL + context cap are stored and used for model
discovery and server/agent flows — in-browser mission routing still uses
OpenRouter/Venice, matching the existing "not wired" hints; the "Model Reasoning
(Eino)" sub-proposal is a separate follow-up pending a mapping.
Verified on macOS: 12 unit tests (RED->GREEN) for the model module, full suite
462 green, and Playwright QA (17 checks) driving the real panel — render,
provider switch, persistence across reload, backward-compat, and both the static
and live Fetch paths.
Refs #90
* fix(settings): harden model list fetch
---------
Co-authored-by: Joseph Magly <1159087+jmagly@users.noreply.github.com>
When the operator binds the server to a non-loopback address
(T3MP3ST_HOST=0.0.0.0 or a LAN IP) they've explicitly opted into network
access — the same decision that already stands the anti-rebinding Host guard
down. But the CSRF origin guard and the SSE guard were NOT updated to match:
they still only trust loopback origins, so the operator's own LAN-served UI
(e.g. http://192.168.x.x:3333/ui) had every state-changing request and the
event stream rejected with "Cross-origin request rejected". That makes hunts
fail on any target from a LAN-accessed War Room (issue #84).
Fix: add isSameOriginAsHost() and, ONLY on a non-loopback bind, also trust a
request whose Origin/Referer host:port equals this server's own Host header —
i.e. a genuine same-origin request from the operator's own UI. A foreign origin
(evil.com) still mismatches the Host and is rejected, so the CSRF/drive-by
protection is preserved. On a loopback bind the branch is skipped entirely:
the strict loopback-only gate plus the Host guard remain fully in force, so
there is no new CSRF or DNS-rebinding surface for the default configuration.
Verified against a 0.0.0.0-bound instance:
- same-origin LAN request (Origin==Host, non-loopback) → passes the guard ✅
- foreign origin (evil.com) → still 403 ✅
- no Origin (curl/CLI) → allowed ✅
Typecheck clean.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Bind to 0.0.0.0 inside containers (DOCKER=true env marker)
- Healthcheck was failing because localhost resolved to IPv6 ::1
but server was bound to IPv4 127.0.0.1
- External exposure still protected via Coolify port mapping to 127.0.0.1
Adds three coordinated capabilities and tightens two defaults on purpose — this is
safety hardening, NOT a backward-compatible drop-in (see "Tightened defaults").
Fifteen independent adversarial review passes; findings fixed and test-pinned.
- Pack-hunt safety: a strict-majority adjudicator with a comment/string/regex/
raw-string/heredoc/POD-aware cite-check (language-gated across C/C++/Py/Rust/Go/
Java/JS/TS/shell/SQL/PHP/Ruby/Perl) so a hallucinated killing-guard can't refute
a real finding — the cited comparison's operator must match source, and a
string/regex/heredoc/generic-arglist/template-interpolation is blanked before
matching. Plus a rate-limit/degraded-health tracker, a global LLM semaphore, and
a PackBoard with an atomic claim CAS + snapshot-on-emit/clone-on-ingest.
- Opt-in Kali+ arsenal (T3MP3ST_FULL_ARSENAL, OFF by default): 35 default tools
(31 built-in + 4 external-CLI wrappers) -> 83 with the catalog armed (verify-claims
CLAIM 4). Most adapters need the underlying binary installed. metasploit / hydra /
bloodhound are never minted through the generic factory.
- Capability-approval + spicy-action gate for the NEW dangerous arsenal only
(pre-existing built-ins keep running freely): approve-once, fail-safe deny when
unattended, redacted audit trail over SSE. T3MP3ST_GATE_BUILTINS=1 extends it to
the spicy built-in probes. Audit redaction is a pure, unit-tested module.
Tightened defaults (intentional, ON by default — NOT backward-compatible):
- Egress containment: once a mission target is set, built-in networked tools refuse
off-scope public hosts (not the target/subdomains, not loopback/private) with
SCOPE DENIED — keeps the agent on authorized scope.
- Role-specialized operator toolkits: each archetype gets a curated default tool
menu (scanner -> scan tools, exploiter -> sqli/xss/etc). Tools stay registered
team-wide; the per-role menu is the operator's working set.
UX (from live-tester feedback): a one-click "Approve & run" modal when a standard
Hunt blocks on an external-target receipt (was a dead-end); bounded-parallel
benchmark file ingestion; the deterministic mission-gate chip co-located with the
subjective SITREP pace narration (no more "behind schedule" next to a clean gate);
and click-through finding detail + evidence in the UI (was export-only).
Docs: README + INTEGRITY_LEDGER made re-derivable — honest 35/83 tool count, the
egress-containment default documented, and the Cybench number reconciled to the
single verify-claims-enforced 23/40 = 58% (dropped the stale/uncited 21 and 26).
README voice pass: killed the underselling `Successful Exploits: 0` disqualifier
(read as "never popped anything" — false, given CVE-Zero + the live disclosure
pipeline); swagger now rests on receipts, not vibes. Also corrected the operator
status — the downstream operators run the SAME real tool-backed ReAct loop as
recon (not "orchestration stubs"); the honest limit is that the coordinated swarm
is unproven (single-agent is the benchmarked path), not that they're scaffolding.
Gates: 292 tests, tsc + lint clean, verify-claims 20/20, arsenal:smoke 125/0,
disclosure-CLI self-test 11/11.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>