Enable authenticated scans across the HTTP arsenal while binding configured credentials to an exact target origin.
Includes case-insensitive per-request overrides, cross-origin redirect containment, protected curl configuration, result redaction, documentation, and regression coverage.
Co-authored-by: Fred Blaise <fred.blaise@protonmail.com>
Two War Room UI fixes (docs/index.html):
1. The floating "guided tour" relaunch button (#t3mpTourRelaunch) was
position:fixed pinned bottom-left, landing on top of the sidebar
"Connected" status pill and hiding its first character. Moved it to
bottom-right (the conventional help-FAB corner) so it collides with
nothing.
2. Added a theme switcher in the sidebar footer with four themes: Storm
(the existing teal/green default, visually unchanged), Ember (amber),
Crimson (red), and Void (violet). The choice persists in localStorage
and is applied before first paint to avoid a flash of the default theme.
To make themes reskin the whole UI rather than only the ~87 var(--accent)
sites, the dominant hardcoded brand colors (#00ff88 / #2fffd2 / #00d4ff
and their rgba() forms) were converted to CSS variables -- but only
inside <style> and inline style="..." attributes, where var() is valid.
Bare JS string literals and SVG fill= attributes are left as-is; most are
semantic status colors (green=ready, red=error) that should stay fixed.
No JS logic changed; the page renders and all scripts run unchanged.
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* fix(ui): reset War Room to idle when a hunt is declined or aborted
A declined-approval hunt ("hunt not started") — or the red-X abort — left the
War Room stuck advertising an active run: the ENGAGE button stayed on the
"ENGAGING…" spinner (disabled), the LIVE fidelity banner kept claiming "real
tools probing the target", and the narration rail kept showing "Looking at your
target". completeMission() only toggled the button's display, never restoring
its label/disabled/opacity, and never cleared the fidelity banner or narration
rail; abortMission() had the same gap and no-op'd entirely when nothing was
"running" (so the red X did nothing on a declined hunt).
Add resetEngageUI(): restores the ENGAGE button (▶ ENGAGE, enabled) and clears
the fidelity banner (setFidelity('off')) + narration rail. Route both
completeMission() and abortMission() through it, and let the red X clear a
leftover engaging/probing UI even when missionRunning is already false.
Client-side only; no behavior change to an actually-running mission.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(ui): clear stale substatus when the red X resets an idle War Room
Review nit: the idle-abort branch set cmdMissionName='STANDBY' but left
cmdMissionStatus unchanged, so after an approval-declined the header could read
STANDBY with a stale red "approval declined" line beneath it. Reset the
substatus to the initial idle state ("Awaiting orders", #666) too.
Verified: abort after a decline now shows STANDBY / "Awaiting orders" (grey).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* fix(ui): route OBSIDIVM benchmarks through any backend, not OpenRouter-only
The OBSIDIVM benchmark/self-improvement UI ran its own client-side,
OpenRouter-only LLM path: getApiKey() read only the OpenRouter key, every
call hardcoded openrouter.ai, and the gate hard-blocked with "Configure
OpenRouter API key first" — so a configured Venice key was ignored and a
connected local agent was never used.
Add a client-side backend resolver (OpenRouter key -> Venice key -> connected
agent -> server LLM) and route all benchmark executors, judges, and gates
through it:
- resolveLLMBackend() + LLM_BACKEND_HINT; safeLLMCall()/_safeLLMCallOnce()
become backend-aware (Venice uses api.venice.ai with a Venice-valid model;
OpenRouter unchanged). _backendCall() routes keyless backends through the
existing server endpoints: /api/agents/local/dispatch (drives the connected
Claude Code/Codex/Hermes agent) and /api/llm/chat (server LLM).
- runLiveBenchmark, ctfCallLLM, the self-improvement analyzer, and the config
optimizer now call safeLLMCall instead of a hardcoded openrouter.ai fetch.
- Live-Test / improvement-loop gates and runLLMJudge accept any backend.
Client-side only; no server changes (endpoints already existed). The
recon-only SPECTRA helper is intentionally left OpenRouter-only (out of scope).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(ui): keyless routing must use the API base URL (cross-origin) + model consistency
Addresses review feedback on the OBSIDIVM backend-routing PR.
1. Relative keyless URLs broke the primary cross-origin deployment. _backendCall
fetched '/api/agents/local/dispatch' and '/api/llm/chat' as RELATIVE URLs, but
when the static War Room (GitHub Pages docs/) talks to a separate backend the
app sets T3MP3ST_API.baseUrl = http://<host>:3333. There the relative fetches
hit the Pages origin → 404/CORS, so the headline local-agent / server-LLM
routing silently failed (worked only same-origin). Fix: prefix
`${T3MP3ST_API.baseUrl}` on both; empty base stays relative for same-origin.
Verified: with baseUrl set, both now resolve to http://<host>:3333/api/...
2. Model default consistency. Four benchmark fallbacks read
'anthropic/claude-opus-4.8', out of step with the state default, the <option>,
renderModels and saveApiKey (all 'claude-opus-4.6'). Reverted to 4.6 to match.
(The /api/agents/local/dispatch server route exists — src/server.ts.)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(ui): backend-aware gates on the last OpenRouter-only benchmark paths
Addresses the re-review on this PR: two flows this PR claims support Venice /
connected local agent / server LLM still hard-gated on an OpenRouter key.
1. runSingleBenchTest() (single benchmark click): replaced the getApiKey()
length check with `resolveLLMBackend().kind === 'none'` (+ LLM_BACKEND_HINT),
matching runBenchmarks('live'). A Venice-only / agent / server backend can now
click-run a single test instead of being blocked before safeLLMCall().
2. Self-improvement auto-apply:
- autoApplyForLoop() now falls back to applyFallbackImprovements() only when
NO backend exists (resolveLLMBackend().kind === 'none'), not merely when the
OpenRouter key is empty — so the optimizer step uses Venice/agent/server.
- autoApplyRecommendations() entry gate uses the backend check.
- The Auto-Apply button show/hide (displayAnalysis) uses `hasBackend =
resolveLLMBackend().kind !== 'none'` instead of an OpenRouter-only key test.
Verified: gates pass for venice/agent/server, block only when kind==='none';
node --check clean on the inline scripts.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
The Settings agent card's render() set window.__t3mpLiveAgents from CLIENT-side
pings only (Object.keys(pingStatus).filter(ok)). Agents connected server-side —
via autoReconnect (ping:false) or a status?check=1 re-ping — are live on the
server even though this client never pinged them, so every re-render reset the
count to 0. Since upstream's t3mpHasBackend() is now `llmAvailable || live > 0`,
that clobber made the War Room show "No LLM backbone — connect an agent before
hunting" on localhost even with a genuinely-live local agent (repro: 3 agents
connected, hermes lastPing.ok on the server, yet hasBackend=false after any
detect/render).
Fix: live = max(client-pinged-ok, server-reported-live), reading the server's
own agent state (window.__t3mpServerAgents, refreshed via status?check=1). The
summary and the global now both reflect reality and re-renders no longer break
the gate. Verified: hasBackend stays true across detect/connect re-renders.
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Fixes all issues raised in PR #47 review:
1. Add .dockerignore to prevent:
- Real .env secrets baked into image layers
- Host node_modules (glibc) overwriting alpine (musl) → ABI mismatch
- .git, reports/, evidence/ bloating the image
2. Fix compose host-binding:
- Remove T3MP3ST_HOST=127.0.0.1 override (was breaking port forwarding)
- Server now binds to 0.0.0.0 in-container (DOCKER=true from Dockerfile)
- Host mapping stays 127.0.0.1:3333:3333 for security
3. Add critical security warning in DOCKER.md:
- Explain exposure risk of 0.0.0.0 binding + disabled Host-header guard
- Document safe vs unsafe deployment patterns
- Warn against network_mode: host and -p 3333:3333 without 127.0.0.1
4. Update base image:
- node:18-alpine → node:22-alpine (Node 18 EOL)
- Pin by digest @sha256:16e22a550... (prevent floating tag drift)
5. Fix healthcheck in compose:
- Was always returning success (process.exit(0))
- Now actually tests /api/health endpoint
- Add start_period: 40s to align with Dockerfile
6. Fix port typos in DOCKER.md:
- 3000 → 3333 in troubleshooting examples
- Update all example snippets to node:22-alpine with digest
- Add Dockerfile with Node.js 18 alpine build
- Add docker-compose.yml with environment configuration
- Add docs/DOCKER.md deployment guide
- Update README.md with Docker quick start section
- Server binds to 0.0.0.0:3333 for external access
- UI available at http://localhost:3333/ui/
- Volumes mounted for reports and evidence persistence
The nine "Pliny Specials" dashboard cards (HYDRA / ARACHNE / CERBERUS /
GORGON / SPHINX / LEVIATHAN / TYPHON / GRIFFIN / SIMURGH) were decorative.
Their backend routes were retired long ago; the in-browser versions ran
randomized [SIMULATED] output; none of them were ever on the hunt path.
They only inflated the arsenal with power:100 / offense:100 stat bars that
measured nothing.
Cut them entirely — the cards, the ~5k-line client-side engine block, their
helper functions (runPlinyDemo / renderStatBars / the tabbed info panel),
the associated CSS, and the "Pliny Specials" category filter. The real
tools (CHIMERA / SPECTRA / ORACLE and the rest of the arsenal) are untouched.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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>