* test(obsidivm): RED — bridge should give an actionable error when the service is down
The obsidivm:* benchmarks fetch from the OBSIDIVM range service; when it is not
running the bridge throws a bare 'fetch failed' (surfaced as 'FATAL: fetch
failed'), naming neither the service, the URL, nor how to start it. Pins that a
connection failure becomes an actionable, self-service error.
Fails on main: the message is 'fetch failed'.
Refs #156
* fix(obsidivm): actionable error when the range service is unreachable
The obsidivm:* benchmarks fetch from the OBSIDIVM range service; when it is not
running the bridge's shared call() let the raw 'fetch failed' propagate, which
surfaced as an opaque 'FATAL: fetch failed' (#156) naming neither the service,
the URL, nor how to start it — even in --hunter stub, whose grading step still
needs it. Wrap the fetch in call() (the single choke point for every OBSIDIVM
request) so a connection failure becomes: 'OBSIDIVM service unreachable at <url>
... Start it per docs/OBSIDIVM.md (python3 range.py, listens on :4200), or set
OBSIDIVM_URL', flagged err.unreachable to distinguish it from an HTTP-status
error; a timeout is reported distinctly. Clarify in the bench --help/usage that
every hunter mode (stub included) needs the OBSIDIVM service, live/t3mp3st also
a key. Adds scripts/obsidivm-bridge.d.mts so the new test types cleanly.
Closes#156
* fix(obsidivm): distinguish invalid-URL from unreachable; test timeout path
Adversarial review of the diff found two gaps:
- A malformed OBSIDIVM_URL (ERR_INVALID_URL) was folded into the 'service
unreachable — is it running?' path, misdirecting the user to start a service
when the real fix is the URL. Now branched to a distinct 'Invalid OBSIDIVM
base URL … set OBSIDIVM_URL' error, no err.unreachable flag.
- The timeout (AbortError) branch had no coverage. Added a test using a
never-responding TCP server + short timeoutMs asserting 'timed out after Nms'
and NOT unreachable; and a test for the invalid-URL branch.
Also completed the .d.mts stub with the cloudgoat* methods it was missing.
Refs #156
* test(binary): RED — no rule for memcpy/memmove with a non-constant length
Adds two corpus fixtures + direct assertions for a memcpy/memmove sink:
- memcpy-wire-length (bad): a wire-decoded length into a fixed buffer,
expect B-MEMCPY. Fails on main — the ruleset has no memcpy rule.
- memcpy-bounded-control (clean): two safe memcpys (sizeof-bounded + a
constant length). This forces a DISCRIMINATING rule: a naive /memcpy/
match false-positives here and fails the zero-false-positive gate.
Refs #150
* feat(binary): detect memcpy/memmove with a non-constant length
Adds B-MEMCPY to the decompiled-vuln ruleset — the classic unbounded-copy
overflow. The framework's own reasoning layer treats this as its textbook
target (src/orchestration/prompts.ts:60,80 — 'a wire-controlled length flowing
into an unchecked memcpy'; orchestration/index.ts:11), but the static
pre-filter had no rule for it: strcpy was covered, its memcpy(dst, src, len)
sibling was not.
The rule inspects the length (3rd) argument and fires only when it is
non-constant — a variable, struct field, deref, or computed/decoded value —
staying silent on a sizeof(...) or numeric-literal length. Validated against a
22-case dangerous/safe matrix (perfect discrimination). Corpus 6+2 -> 7+3; the
new sizeof/constant-bounded control keeps the rule honest — a bare /memcpy/
match would false-positive on it.
Closes#150
* fix(binary): stop B-MEMCPY false-positive on nested-call args; disclose limits
Adversarial review found a real false positive: a comma inside a nested call in
arg 1/2 (memcpy(dst, get_src(a,b), sizeof(dst))) mis-split the naive
[^,]+,[^,]+, arg list, so a sizeof-BOUNDED call fired. Restricting args 1-2 to
[^,()]+ (paren-free) removes it — verified the bounded nested-call form is now
silent while every common non-constant length still fires. The trade is a
memcpy whose 1st/2nd arg is itself a call is not matched (an acceptable miss for
a directional pre-filter, now disclosed).
Rule comment rewritten to honestly enumerate the directional misses (leading-
digit computed length, sizeof-plus-term, nested-call args, inline comments,
__memcpy_chk/memcpy_s/wmemcpy variants). Test expanded into an in-tree
discrimination matrix (fires / silent / pinned disclosed-limit cases) so the
scope boundary is auditable and any future tightening surfaces deliberately.
Refs #150
* fix(binary): match pointer casts in B-MEMCPY args; correct disclosed limits
A second adversarial pass on the shipped rule caught that the previous
paren-free tightening ([^,()]+) over-corrected: it also stopped matching
pointer CASTS on args 1-2 — memcpy((void *)dst, (void *)src, len) — which are
the most common memcpy shape in real decompiled output. The rule was silently
missing the main case.
Replace the arg matcher with one level of BALANCED parens,
(?:[^,()]|\([^()]*\))+, so a cast (void *)dst and a comma-bearing call
get(a,b) are each consumed as ONE arg. This fires on casts again, still fires on
a call-bearing arg with a non-constant length (now a true positive, previously a
disclosed miss), and keeps the original nested-call-with-bounded-length false
positive silent. Verified no catastrophic backtracking on pathological input.
Also corrects the disclosed-limits comment: the previous 'arg is itself a call'
miss is now only DOUBLY-nested parens, and the inline-comment note was
backwards (a comment obscuring a sizeof/literal false-fires, it does not cause a
miss) — restated accurately, with decompiled output not emitting inline comments
noted. Tests pin the cast fires and the variant exclusions.
Refs #150
* feat(arsenal): add subdomain-takeover classifier + fingerprint table
Pure, I/O-free detection logic split into src/arsenal/takeover.ts so it is fully
unit-testable without DNS or network:
- TAKEOVER_FINGERPRINTS: curated CNAME + unclaimed-resource-body signatures for 15
high-frequency services (S3, GitHub Pages, Heroku, Azure, Fastly, Shopify, Netlify, ...)
- classifySubdomainTakeover(): conservative verdict (confirmed / potential / none) driven by
a dangling CNAME and/or a service body fingerprint; no cross-service false matches
- renderTakeoverReport(): human-readable output block
* feat(arsenal): register subdomain_takeover_check built-in + bump tool count 108->109
- New keyless, self-contained built-in tool 'subdomain_takeover_check' (recon): resolves the
CNAME, checks whether the target still resolves (dangling), fetches the live body via the
scope-gated targetFetch, and classifies via classifySubdomainTakeover. Emits a real finding.
- Added to the Recon operator's default toolkit so the swarm can reach it (operator-toolkits
coverage invariant).
- Bumped the advertised arsenal size 108 -> 109 in lockstep across the count-honesty test,
README, and verify-claims (73 adapters + 36 built-in).
* test(arsenal): cover subdomain_takeover_check (classifier matrix + mocked DNS/fetch e2e)
- classifySubdomainTakeover decision matrix: confirmed via body fingerprint, confirmed via
dangling nxdomain-prone CNAME, potential (known service, no confirmation), potential
(dangling unknown), none (no CNAME / resolves clean), and no cross-service false matching
- renderTakeoverReport output shape
- end-to-end handler test with DNS + fetch mocked, driving the confirmed S3 path
Stage ProjectDiscovery binaries before installing them into /usr/local/bin, run the tools image as the existing Kali operator identity, preserve runtime access to Foundry tools, and cover the non-root Dockerfile and smoke-test contracts.
Align NanoGPT integration with the published OpenAI-compatible API contract, including model discovery, configuration/setup surfaces, and secret-redaction safeguards.\n\nFixes #129.
Add user and developer documentation plus Pagenary docsite publishing support.
Includes Getting Started, Developer Guide, API and MCP references, Pagenary tenant metadata, npm scripts for sync/build/check/serve, and validation coverage for the generated docsite.
Closes#102
* feat(ops): add portable War Room preflight command
Add npm run ops:preflight that checks operational readiness before
launching a War Room mission: API health, active mission status,
pending action receipts, local-agent/provider configuration, and
arsenal tool availability. Follows same pattern as scripts/doctor.mjs.
Supports --json for machine-readable output and --strict to fail
on warnings.
Fixes#35
Signed-off-by: Shivam Singh <shivamsingh-007@users.noreply.github.com>
* chore: revert package-lock.json churn to reduce diff noise
Restores package-lock.json to upstream main version.
Removes unintended npm-version lockfile rewrite (peer flips, @emnapi/* entry shifts).
* fix(ops-preflight): correct response-shape reads for mission, approvals, and agent checks
Three bugs identified by jmagly in review:
1. Mission-active: API returns {active: boolean}, not {phase}
2. Pending-approvals: API returns {approvals: [...]}, not a flat array
3. Local-agent: API returns {connected: [...]} (array), not a boolean
All three verified against live endpoints per review feedback.
Signed-off-by: Shivam Singh <shivamsingh-007@users.noreply.github.com>
* fix(ops): harden portable preflight checks
Signed-off-by: Joseph Magly <1159087+jmagly@users.noreply.github.com>
---------
Signed-off-by: Shivam Singh <shivamsingh-007@users.noreply.github.com>
Signed-off-by: Joseph Magly <1159087+jmagly@users.noreply.github.com>
Co-authored-by: Shivam Singh <shivamsingh-007@users.noreply.github.com>
Co-authored-by: Joseph Magly <1159087+jmagly@users.noreply.github.com>
verify-claims credits a solve straight from the committed `verdict`
(detected / score===1). Nothing asserted that a scored solve actually
agrees with its own committed flag oracle, so a hand-edited `detected:true`
(or a merge slip / fabricated placeholder flag) would pass verification
silently. The case-insensitive oracle comparator `ciMatch` was defined but
never called — this is exactly the check it was meant for.
Add an INTEGRITY GATE that re-derives, for every scored solve across the
pinned golden + white-box XBEN sweeps and the Cybench per-run artifacts,
that it is oracle-grounded: EITHER the self-reported flag matches the
committed `expected` (ciMatch), OR the harness auto-detected `expected`
verbatim in tool output (auto_detected, reported may be "UNKNOWN").
Anything scored that is neither is a phantom pass and fails the build.
Also rejects a fabricated placeholder credited via the self-report path,
and reports how much of the Cybench headline is backed by an oracle-bearing
per-run artifact vs. counted only in the hand-editable aggregate.
Adds src/__tests__/oracle-consistency.test.ts in the same spirit as
no-phantom-tools / stub-honesty: audits the committed artifacts AND proves
the gate has teeth against synthetic tampered/fabricated verdicts.
Current receipts pass clean: 0 phantom / 606 scored solves, 0 fabricated.
Three new category loadouts, each = arsenal + an offline-scorable benchmark, opt-in and
honestly scoped (roster marks all 🚧; pacu/frida stay catalog_only, unit-tested non-mintable):
- cloud: aws/az/gcloud + scoutsuite/cloudfox/pmapper; cloud:bench IaC-misconfig detection
- mobile: apkleaks/mobsfscan/drozer/objection/class-dump; mobile:bench static ruleset
- binary/RE: ghidra/objdump/checksec/strings/readelf/gdb; binary:bench decompiled-sink detector
Mobile/binary benches are DETECTOR REGRESSION CHECKS (rules+fixtures co-authored — a pass is a
floor, not a detection rate). No new headline numbers; verify-claims 24/24.
tsc clean; 337 tests; lint 0 errors.
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>