bugxhunter
v0.9.5
Published
BugXHunter: an AI security-testing agent with a sandboxed runner, a passkey-sealed key vault and read-only GitHub access. `npx bugxhunter` runs it on your machine.
Maintainers
Readme
BugXHunter
The BugXHunter security-testing agent runner: a web UI for the
OpenCode coding agent, running on open-weight models (GLM-5.3 by default)
through SCX.ai. The UI follows bugxhunter.com's design system — a
macOS-style terminal window, the red-team palette (GitHub-dark surfaces with red, amber, cyan and
green accents) and Fira Code throughout. Started from the sovgov-poc chatbot designer.
- Runner — point it at a project folder and drive OpenCode's
build(edits + shell) orplan(read-only) agent. Every file edit and shell command waits for your approval. Markdown replies, collapsible tool calls and reasoning, per-turn token counts, and a Changes view of what the agent edited (git repos only — OpenCode tracks changes via git). - Playground — chat with any SCX, Anthropic (Claude), NVIDIA or OpenRouter model directly (no agent), with
tool-calling, JSON mode, an embeddings-backed knowledge base and browser voice. Also:
- Security presets (code review, threat modelling, AI system review, pentest planning,
diagram review, diagram drawing, tabletop exercises, CTF coaching, phishing, log triage,
vulnerability explaining) and compare mode (2–3 models side by side, with speed and tokens).
Every preset, the canvas and the agent work from one catalogue (
web/src/intel): OWASP Top 10:2025, API Security Top 10:2023, Top 10 for LLM Applications 2025, Top 10 for Agentic Applications 2026, Mobile Top 10 2024, ASVS 5.0, WSTG 4.2, STRIDE, and MITRE ATLAS tactics, techniques and mitigations for attacks on AI systems, so findings cite the same ids everywhere. - Flag highlighting for CTFs and labs: anything matching the flag format (a regular expression
in the sidebar, default
flag{...}and the like) is marked in replies and tool output, in both modes. - Images for vision models, and draw.io files (
.drawio, compressed or not, and.drawio.svg) for any model: the file becomes an exact text outline of its zones, boxes, legend colours and arrows, so labels are never misread. - Diagrams in replies: a model writes a
```drawiospec and the app draws it as an editable.drawio.svg(drawgen), checks it, and can send the problems back for a fix. - Threat-model canvas: build a model from a diagram or description, track findings,
scope and coverage, zoom and pan, export JSON,
findings.md, SVG or draw.io. - Saved sessions: chats and canvases are sealed with the vault key on the runner
(Docker: the
runner-datavolume), never in browser storage. - Retired NVIDIA models (their endpoints answer 404) are found and hidden automatically.
- Security presets (code review, threat modelling, AI system review, pentest planning,
diagram review, diagram drawing, tabletop exercises, CTF coaching, phishing, log triage,
vulnerability explaining) and compare mode (2–3 models side by side, with speed and tokens).
Every preset, the canvas and the agent work from one catalogue (
Screenshots
The agent working in a project — live tool calls, reasoning, and a markdown summary:

Point it at any folder; resume past sessions; approve edits and commands as they happen. The key vault's state and lock live in the sidebar footer:

Playground — compare SCX models directly with full generation controls:

Install
The quickest way, with Node 22+ installed, is the npm package (OpenCode comes with it):
npx bugxhunter # runs the agent on this machine; UI opens at http://localhost:8790
npx bugxhunter docker # the sandboxed stack instead (needs Docker Desktop): pulls the signed
# images for this version into ~/.config/bugxhunter/docker and starts themnpm i -g bugxhunter makes it a plain bugxhunter command; bugxhunter --help lists the
options (--port, --dir, --no-open; docker --slim|--full, docker down|logs|ps,
docker export <path> [dest]; vault reset). The current
folder becomes the default project. Then, in the sidebar: set up the vault with a
passkey, add your SCX.ai key as SCX_API, and connect GitHub.
The package is published by the release workflow with npm provenance: npm audit signatures
confirms it was built from this repository.
Requirements (from source)
- Node 22+
- OpenCode on
PATH:npm installhere provides it, ornpm i -g opencode-ai - An SCX.ai key: in the vault (preferred), else
opencode auth login(choose Other, provider idscx) orSCX_APIin a.envhere.
Run from source
npm install
npm run dev # backend :8790 + web :5190, with hot reload
npm start # or: the compiled app, the same way `npx bugxhunter` runs it (npm run build first)Open http://localhost:5190 (dev) or http://localhost:8790 (npm start).
The backend starts its own OpenCode server on 127.0.0.1:8791 (it stops a stale one left on
that port by a previous run). Everything listens on localhost only. There are no passwords:
until you set up the vault the app is open, and from then on you sign in with the
vault's passkey.
Docker (sandboxed agent + login)
npx bugxhunter docker does all of this (any OS). By hand, from a checkout:
powershell -File docker/setup.ps1 # once: writes .env — the secrets the two containers share
docker compose up -d --build # build locally, or `docker compose pull && docker compose up -d`
# for the published images (set BXH_VERSION in .env to pin a signed release)The images are multi-arch (linux/amd64 and linux/arm64, so Apple Silicon and Graviton run them
natively) with zstd-compressed layers (Docker 23+). The agent image comes in two flavours: the
default carries the scan toolchain (nuclei, nmap, httpx, katana, ffuf, gitleaks, testssl and
wordlists) plus the Rust and Go toolchains, about 750 MB to download; -slim leaves all of that out for code review only, about a third of
the size (npx bugxhunter docker --slim, or BXH_AGENT_FLAVOR=-slim in .env). Open
http://localhost:8790 and set up the vault in the sidebar: it seals your SCX
key and turns on passkey sign-in. (setup.ps1 -CopyScxKey copies
the key into .env instead, in the clear.)
Three containers (plus the egress logger):
| Container | Holds | Network |
|---|---|---|
| runner | Web UI, login, SCX key, /scx/v1 key proxy | shared bridge, published on 127.0.0.1 |
| agent | OpenCode, the agent's shell + scan tools, /workspace volume | shared bridge — direct internet |
| render | drawgen: model-written diagram specs → .drawio.svg, and .drawio files → outlines | internal network shared only with runner — no internet |
Model output and uploaded diagram files are parsed in
render, not in therunnerthat holds the keys: no volumes, no secrets, read-only, non-root, 512 MB and 1 CPU, each job in its own process with a 10 s limit. drawgen is installed from a release wheel pinned by SHA-256.The agent's model calls go to
runner's proxy with a proxy token; the proxy swaps in the real key and only allows/chat/completionsand/models. The SCX key never enters the agent container — this holds even though the agent has direct internet.Projects live in the
workspaceDocker volume (/workspace, a git repo so Changes work). No host folders are mounted.Workspaces and files in the UI: the sidebar's workspaces button lists the folders under
/workspace(locally~/bugxhunter/repos, orOPEN_RUNNER_WORKSPACE), including GitHub clones, and creates a new one (an empty git repository) or deletes one. A delete asks for the name typed back, the open project can't be deleted, and only a listed workspace can be. The files button browses the open project: click a file to download it, and upload with the button or by dropping files on the list (up to 200 MB each). An upload never replaces a file; a clash is saved asname-1.ext.From the command line, copy a repo in as a snapshot, and results back out:
powershell -File docker/copy-in.ps1 C:\code\my-repo # -> /workspace/my-repo (skips node_modules, .env, …) powershell -File docker/copy-out.ps1 my-repo C:\temp\my-repo-out(Plain
docker compose cpalso copies in, but leaves files owned by root, so neither the agent nor uploads can write there. To fix a folder copied that way:docker run --rm -u root -v bugxhunter_workspace:/workspace --entrypoint chown ghcr.io/cintelis/bugxhunter-agent -R node:node /workspace/<folder>.)Getting files out:
/workspaceis a Docker volume, so there is no host folder to open. Any file path the agent prints under the project (for example "Saved as/workspace/acme/app/analysis.md") is a download link in the UI, served only for files inside the project folder. From a terminal,npx bugxhunter@latest docker export acme/app/analysis.md(or an absolute/workspace/...path; optional destination as the second argument) copies a file or folder out.The agent has direct internet so scan tools run at full speed (
nmap/ping/raw DNS work viaNET_RAW). Outbound connections are logged, not restricted — only point the agent at targets you are authorised to test, and run this on a trusted machine/VM.Both containers run as non-root with
no-new-privileges; the agent drops all capabilities exceptNET_RAWand is limited to 2 CPUs, 4 GB RAM, 512 processes. Shell commands and file edits are approved in the UI — that, plus the container/VM boundary and the isolated SCX key, is what contains the agent.Logs (the audit log of prompts, tool calls and approvals, plus the egress log of hosts the agent connected to) live in the
logsvolume, which the agent container can't see. Read them withdocker compose cp runner:/logs ./logs.Port 8790 is published on localhost only. Put TLS (a reverse proxy) in front and set
COOKIE_SECURE=1,TRUST_PROXY=1andALLOWED_HOSTS=<your domain>before exposing it to anyone else.
Key vault
The SCX key (and any other secret the agent needs) lives in a vault that is sealed at rest: on
disk there is only ciphertext and wrapped keys, so a copied .env, volume or container yields
nothing. Set it up from the sidebar after the first start; it takes a minute:
- Passkey — Windows Hello, Touch ID, Android, or a security key. One touch unlocks the vault
and signs you in; the authenticator's own biometric or PIN is the second factor. It derives a
secret (WebAuthn PRF) that never leaves the device. Chrome, Edge and Safari 18+; Firefox can't
do this yet. Open the app as
http://localhost:…, not by IP. - Recovery code — shown once, stored nowhere. Keep it offline. It opens the vault if the passkey is lost.
- Backup passphrase (optional) — a way in on a device without the passkey.
The decryption key lives only in the backend's memory, so after every restart, and after
2 hours without activity (OPEN_RUNNER_VAULT_IDLE_MINUTES), someone has to unlock it before the
agent can call a model. Ctrl+Shift+L locks it at once. The crypto is symmetric only
(AES-256-GCM, HKDF-SHA-256, PBKDF2-SHA-256), which keeps it out of reach of a quantum attacker;
see SECURITY.md.
While the vault is locked, model calls fail with a clear "vault is sealed" message. Without a vault
the app takes the key from a key file (SCX_API_FILE), else falls back to SCX_API in .env or
OpenCode's auth.json, in the clear. A key file is for a runner nobody unlocks, such as one behind
Sigilant: mount it as a secret; it is re-read on every call, so replacing it rotates the key, and if
it cannot be read the call fails rather than falling back to a key in the clear.
Sign-in is the same passkey. Once a vault exists the API needs a session, and the ways in are
a WebAuthn assertion with one of the vault's passkeys (verified server-side), the backup
passphrase, or the recovery code. One touch at the sign-in screen signs in and unlocks the vault.
Keep two passkeys on different devices (keys dialog → add passkey). Lost every factor? Someone with
access to the server's files runs npm run vault:reset (Docker: docker compose exec runner rm
/data/vault.json): the vault and its secrets are gone and the app reopens for a fresh setup.
Nothing on the network can do that, which is what keeps the lock meaningful.
Other models via OpenRouter
SCX.ai is the default provider. To use anything from OpenRouter's catalogue as well:
- Add your OpenRouter key to the vault as
OPENROUTER_API_KEY(keys dialog in the sidebar). The Playground then lists OpenRouter's models under their own heading, addressed asopenrouter/<id>. - For the agent, list the ids it may use in
OPENROUTER_MODELS(comma-separated, e.g.anthropic/claude-sonnet-4.5,openai/gpt-6.1-sol), in.env. Only those appear in the Runner's model picker, asopenrouter/…. In Docker, both containers read the same variable.
The agent never holds the key: its calls go to the runner's /openrouter/v1 proxy with the proxy
token, exactly like SCX, in both local and Docker modes. Pricing is OpenRouter's; the Playground
shows each model's per-token rates.
Claude via the Anthropic API
- Add your Anthropic key to the vault as
ANTHROPIC_API_KEY(keys dialog). The Playground then lists every Claude model the key can use under an Anthropic heading, addressed asanthropic/<id>(for exampleanthropic/claude-opus-5-5). - The agent gets Claude Opus 5.5, Sonnet 5.5 and Haiku 5.5 in the Runner's model picker.
Locally,
ANTHROPIC_MODELS(comma-separated model ids) changes that list; in Docker the list is indocker/opencode.json.
As with the other providers, the agent never holds the key: OpenCode's own Anthropic client calls
the runner's /anthropic/v1 proxy with the proxy token, and the runner adds the key.
Things that differ from the OpenAI-style providers:
- Sampling settings are not sent. Current Claude models reject temperature and top-p, so the Playground's sliders don't apply to them. Forced tool use is sent as "auto" for the same reason.
- Safety classifiers. Claude's classifiers can decline some security requests. On models that support it, the Playground asks for Anthropic's server-side fallback, so a declined request is retried on the model Anthropic recommends for that category. A decline that still stands shows as an error naming the category. For authorised security work, Anthropic's Cyber Verification Program can lift cyber-category declines.
- Prompt caching is on, so repeated history in a long chat is billed at the cached rate.
GitHub repositories (read-only)
To review a repository, clone it into the workspace from the sidebar (clone from GitHub under the project folder). The first time, click Connect GitHub: a short code appears, you enter it at github.com/login/device, and the runner is signed in. Then Choose repositories on GitHub opens the app's install screen, where you pick which repositories it may read; the list in the dialog refreshes when you come back. Nothing to register, no token to paste.
The sign-in is to the public BugXHunter GitHub App, which declares only Contents: read-only and Metadata: read-only, so even GitHub will refuse a push with its token. It reaches only the repositories you install it on, and you can change that list or revoke the app on GitHub at any time. Its tokens expire after eight hours and are refreshed by the runner; the pair is sealed in the vault (or, before a vault exists, kept in memory until the runner restarts).
A clone lands in <workspace>/<owner>/<repo> (Docker: the shared workspace volume; locally
~/bugxhunter/repos, or OPEN_RUNNER_WORKSPACE) and becomes the project folder. Cloning again
fast-forwards it and never resets local edits.
The runner does the cloning, the agent gets a working copy. The token is passed to git as a
process-scoped header, so it is in neither the command line nor the clone's .git/config. The
agent can read, grep and scan the code (gitleaks over the history included) and edit it locally,
but nothing in the sandbox can fetch, push, open issues or see any other repository. Pushing
results back is deliberately not built: findings leave through the UI, not through your GitHub
account.
A fork that wants its own app registers one (device flow on, Contents: read-only, installable
on any account) and sets GITHUB_CLIENT_ID and GITHUB_APP_SLUG.
Security-testing tools (authorised use only)
The agent image ships a standard pentest toolchain for authorised testing (your own sites,
labs, CTFs). Build with --build-arg SECTOOLS=0 to leave them out.
- Vuln / web:
nuclei(templates baked in offline),httpx,katana(SPA/JS crawler),ffuf(+ SecLists wordlists in/opt/wordlists),testssl,curl,openssl,subfinder(passive). - Secrets (local, no network):
gitleaks— scan copied-in repos and JS bundles for leaked keys. - CTF and forensics (full image):
file,binutils,binwalk,exiftool,exiv2,pngcheck,steghide,foremost,7z,tshark,john,sqlmap,gdb,strace(ltraceon amd64), and Python'spwntools,pycryptodome,z3andyara, for labs and CTFs the user owns or is registered for.--build-arg CTFTOOLS=0leaves them out;CTFTOOLS=1adds them to a slim build. gef, RsaCtfTool, Volatility 3, radare2 and rockyou install on demand into/workspace/.tools(recipes indocker/agent-AGENTS.md), and the agent's instructions carry a first-look playbook per category. - Rust and Go:
cargo(withclippy,rustfmtandcargo auditfor RustSec advisories) andgo(withgovulncheck), to build, test and audit Rust and Go repositories.--build-arg RUST=0orGO=0leaves one out;RUST=1/GO=1adds it to a slim build. - Raw-socket:
nmap,ping,traceroute,dig/host(incl. zone transfer). - Not included — LLM red-teaming:
promptfoo(~1.4 GB installed) andgarak(~2 GB with CPU-only PyTorch, 5 GB+ if pip is left to pull the CUDA build) would more than double the image, so the agent installs them on demand into the persistent/workspace/.toolsvolume. The recipe, including pointing them at the runner's model proxy for grading, is indocker/agent-AGENTS.md.
All tools run directly against targets. The agent's AGENTS.md tells it to confirm authorisation
before scanning and to scan only hosts you name.
Making scans fast (they're slow if done naively):
- Scope nuclei templates — the biggest lever.
-tags cve,exposureor-severity critical,high,mediuminstead of all ~14k templates. Fingerprint with httpx first and target the detected stack. - Pre-filter with katana/httpx and feed only live URLs into nuclei/ffuf.
- Tune to the target:
-c 50 -rl 300 -bulk-size 50 -timeout 5on a tolerant host; lower on a CDN/WAF target, where aggression trips rate-limits and is slower. - Run long scans detached, writing to
/workspace(-o result.txt), and poll the file so an aborted turn doesn't lose progress.
To fence the agent to a specific lab/target range, add firewall rules on the Docker host or the VM you run this in — Docker's bridge NAT doesn't restrict outbound on its own.
Through Sigilant (governed runs)
The runner can run under Sigilant, Cintelis's agent governance platform: it is a
registered agent there, every call it makes is decided by a gateway against the scope the customer signed,
risky ones wait for a person, and each is written to a signed audit chain. Sigilant's side of that (the
runner's tools behind the gateway, the MCP bridge its agent calls through, the GitHub App, the webhooks and
the scheduler) lives in Sigilant's repository, which is private. This repository needs nothing for it beyond
what it already has: the runner's API, the agent's OPENCODE_CONFIG merge, and SCX_API_FILE for a runner
nobody is there to unlock.
Configuration (environment variables)
| Variable | Default | Purpose |
|---|---|---|
| OPEN_RUNNER_MODEL | scx/GLM-5.3 | Default model for both agents |
| OPEN_RUNNER_DIR | this folder | Default project folder |
| OPEN_RUNNER_AGENT_PORT | 8791 | Port for the OpenCode server |
| PORT | 8790 | Backend port |
| SCX_API_FILE | unset | A file holding the SCX key, re-read on every call; for a runner with no one to unlock the vault. Ignored once a vault exists |
| SCX_API | the vault, else opencode auth login | SCX key in the clear; ignored once a vault or key file exists (prefer either) |
| OPENROUTER_API_KEY_FILE | unset | A file holding the OpenRouter key, as SCX_API_FILE |
| OPENROUTER_API_KEY | the vault, else unset | OpenRouter key in the clear; prefer the vault item of the same name |
| OPENROUTER_MODELS | unset | Comma list of OpenRouter model ids the agent may use; becomes an OpenCode provider through the proxy |
| OPENROUTER_BASE_URL | https://openrouter.ai/api/v1 | OpenRouter endpoint |
| ANTHROPIC_API_KEY_FILE | unset | A file holding the Anthropic key, as SCX_API_FILE |
| ANTHROPIC_API_KEY | the vault, else unset | Anthropic key in the clear; prefer the vault item of the same name |
| ANTHROPIC_MODELS | claude-opus-5-5,claude-sonnet-5-5,claude-haiku-5-5 | Claude model ids in the agent's picker (local mode; Docker: docker/opencode.json) |
| ANTHROPIC_BASE_URL | https://api.anthropic.com/v1 | Anthropic endpoint |
| GITHUB_CLIENT_ID | the BugXHunter app | A fork's own GitHub App (device flow); GITHUB_APP_SLUG names its install page |
| OPEN_RUNNER_VAULT_FILE | ~/.config/bugxhunter/vault.json | The sealed vault (Docker: /data/vault.json on the runner-data volume) |
| OPEN_RUNNER_VAULT_IDLE_MINUTES | 120 | Auto-lock after this long without a model call or API write; 0 = never |
| OPEN_RUNNER_SECRET | random per start | Signs session cookies; set it to keep sign-ins across restarts |
| COOKIE_SECURE | unset | 1 marks the session cookie Secure (behind TLS) |
| TRUST_PROXY | unset | Behind a reverse proxy: 1 (hop count), loopback, or a CIDR list — so the cookie's Secure flag and client IPs are right |
| OPEN_RUNNER_LOG_DIR | unset (off) | Folder for the JSONL audit log (Docker sets /logs) |
| ALLOWED_HOSTS | localhost only | Extra Host header values to accept (comma list), e.g. the domain a reverse proxy serves. Everything else gets 403, which blocks DNS-rebinding attacks |
| OPEN_RUNNER_ALLOWED_ROOTS | any folder | Local mode: folders the UI may open, PATH-style list (; on Windows, : elsewhere). Set it before sharing the app |
| OPENCODE_URL | unset (spawn locally) | Use a remote OpenCode server (Docker sets http://agent:4096) |
| OPEN_RUNNER_RENDER_URL | unset (diagrams off) | The render service for diagrams and .drawio files (Docker sets http://render:8080) |
| OPEN_RUNNER_PLAYGROUND_DIR | playground/ beside the vault | Saved Playground chats and canvases, sealed with the vault key |
| OPEN_RUNNER_WORKSPACE | /workspace (local: ~/bugxhunter/repos) | Remote mode: projects must live under this folder. Both modes: where GitHub clones land |
| SCX_PROXY_TOKEN | unset | Enables the /scx/v1 key proxy for the sandboxed agent |
Releases are on the releases page. See SECURITY.md to verify one by hand, or to report a vulnerability.
Releases
Every published release, newest first. Each is signed with the Cintelis release key;
scripts/verify-release.sh refuses anything that isn't. A release ships an app archive (the
compiled server and web app, runnable with Node and no Docker), a deploy archive
(docker-compose.yml and the setup scripts, pinned to that version's images), images.txt (both
GHCR images by digest) and the signed checksums.txt that covers them all.
| Release | Published | Checksums | Signature | Signature check | Build | | --- | --- | --- | --- | --- | --- | | v0.9.4 | 2026-10-29 | checksums.txt | checksums.txt.sig | ✅ release key | workflow run | | v0.9.3 | 2026-10-29 | checksums.txt | checksums.txt.sig | ✅ release key | workflow run | | v0.9.2 | 2026-10-28 | checksums.txt | checksums.txt.sig | ✅ release key | workflow run | | v0.9.1 | 2026-10-28 | checksums.txt | checksums.txt.sig | ✅ release key | workflow run | | v0.9.0 | 2026-10-28 | checksums.txt | checksums.txt.sig | ✅ release key | workflow run | | v0.8.7 | 2026-10-28 | checksums.txt | checksums.txt.sig | ✅ release key | workflow run | | v0.8.6 | 2026-10-27 | checksums.txt | checksums.txt.sig | ✅ release key | workflow run | | v0.8.5 | 2026-10-21 | checksums.txt | checksums.txt.sig | ✅ release key | workflow run | | v0.8.4 | 2026-10-21 | checksums.txt | checksums.txt.sig | ✅ release key | workflow run | | v0.8.3 | 2026-10-21 | checksums.txt | checksums.txt.sig | ✅ release key | workflow run | | v0.8.2 | 2026-10-21 | checksums.txt | checksums.txt.sig | ✅ release key | workflow run | | v0.8.1 | 2026-10-09 | checksums.txt | checksums.txt.sig | ✅ release key | workflow run | | v0.8.0 | 2026-10-09 | checksums.txt | checksums.txt.sig | ✅ release key | workflow run | | v0.7.0 | 2026-10-08 | checksums.txt | checksums.txt.sig | ✅ release key | workflow run | | v0.6.0 | 2026-10-08 | checksums.txt | checksums.txt.sig | ✅ release key | workflow run | | v0.5.0 | 2026-10-07 | checksums.txt | checksums.txt.sig | ✅ release key | workflow run | | v0.4.0 | 2026-10-07 | checksums.txt | checksums.txt.sig | ✅ release key | workflow run | | v0.3.0 | 2026-10-07 | checksums.txt | checksums.txt.sig | ✅ release key | workflow run | | v0.2.0 | 2026-10-07 | checksums.txt | checksums.txt.sig | ✅ release key | workflow run | | v0.1.0 | 2026-10-07 | checksums.txt | checksums.txt.sig | ✅ release key | workflow run |
Signature check: checksums.txt.sig verified against the release key (SHA256:RY9yd61LBZCa5WrzSkEet+1Dnt2zVu9zpMRbZMWIV+I)
when this table was generated by scripts/release-table.sh. To check a download
yourself, see SECURITY.md.
To use a release, verify it first (scripts/verify-release.sh vX.Y.Z, see
SECURITY.md), then either unpack the deploy archive and run docker compose pull
&& docker compose up -d (its VERSION file pins the images), or unpack the app archive and run
npm ci --omit=dev && NODE_ENV=production node server/dist/index.js.
Development
npm run check # typecheck + lint + unit tests (what CI runs)
npm run typecheck # tsc, both workspaces
npm run lint # eslint (typescript-eslint + react-hooks), warnings fail
npm test # vitest: the pure logic in server/src and web/src
npm run build # web -> web/dist, server -> server/dist (what the Docker image ships)GitHub Actions: ci.yml runs the checks (including the render service's Python tests) and builds the Docker images on every push and PR,
codeql.yml scans for security issues weekly and on PRs, docker-publish.yml pushes the images to
GHCR (ghcr.io/cintelis/bugxhunter-runner, bugxhunter-agent and bugxhunter-render) on master and on v* tags, and Dependabot
keeps npm, Actions and base images current.
Server and browser share one type contract in shared/ (agent.d.ts, scx.d.ts): the server's
event mapping must produce those shapes and the client consumes them, so a change on either side
fails the other's type-check.
Releasing
Bump version in package.json, then tag and push; the release workflow checks, builds the
archives and the versioned images with provenance attestations, and creates a draft. Then sign
it with the offline release key, which verifies the draft's attestations and checksums first and
publishes it:
git tag -a v1.0.0 -m "BugXHunter v1.0.0"
git push origin v1.0.0
# once the workflow finishes (10-15 minutes, the agent image is large):
scripts/sign-release.sh v1.0.0On Windows, run the signing script from PowerShell through Git Bash:
& 'C:\Program Files\Git\bin\bash.exe' scripts/sign-release.sh v1.0.0.
It asks for the key's passphrase once. Only admins can push v* tags.
After publishing, the script regenerates the release table above (scripts/release-table.sh)
and opens a pull request for it that merges itself once CI passes.
Publishing the release (the signing script's last step) also publishes the npm package:
the workflow's npm job runs on the release published event, so npm never carries an
unsigned release (npm run pack:npm assembles it from the same build, with provenance). That needs the package's trusted
publisher on npmjs.com set to this repository and release.yml, once; no npm token is stored.
Layout
| Path | Role |
|---|---|
| server/src/opencode.ts | Starts/stops the OpenCode server, one SDK client per project folder, event mapping |
| server/src/index.ts | Express: /api/agent/* (OpenCode) and /api/chat, /api/models, /api/kb (SCX) |
| server/src/scx.ts, rag.ts | SCX client and in-memory knowledge base (Playground) |
| server/src/providers.ts, anthropic.ts, nvidia-health.ts | Anthropic / NVIDIA / OpenRouter providers (Claude via the official SDK); hides NVIDIA models whose endpoints are retired |
| server/src/playground-store.ts | Saved Playground sessions, sealed with the vault key |
| server/src/render.ts, docker/render-server.py, docker/drawio_outline.py | The render service (diagrams, .drawio outlines) and the runner's client for it |
| web/src/Canvas.tsx, web/src/canvas/ | The threat-model canvas |
| web/src/intel/, web/src/presets.ts, docker/agent-AGENTS.md | The security catalogue (OWASP, ASVS, WSTG, STRIDE, MITRE ATLAS) as data, the prompts built from it, and the agent's methodology, which a test keeps in step with the catalogue |
| web/src/DrawioBlock.tsx, diagrams.ts | Diagrams in replies |
| shared/ | Type contract between server and browser |
| scripts/ | sign-release.sh (maintainer, offline key), verify-release.sh (users), release-table.sh, the release public key |
| web/src/AgentPanel.tsx | Runner transcript, approvals and session flow; DiffModal.tsx and QuestionCard.tsx hold the Changes view and agent questions |
| web/src/Sidebar.tsx | Project folder picker (Runner) and model/prompt settings (Playground) |
| web/src/styles.css, brand.tsx, Terminal.tsx | bugxhunter.com theme tokens, the >_ BugXHunter wordmark, terminal title bar and typed boot sequence |
AI assistance
BugXHunter was built with Claude Code, and the codebase was reviewed and hardened by Claude Mythos 5.1 (bug fixes, the security hardening, tests and CI). The app shows this with Anthropic's Claude wordmark, used as published. It is a model credit, not an endorsement by Anthropic; the maintainers are responsible for the code.
License
MIT © Cintelis.
BugXHunter ships a security-testing toolchain for authorised testing only (your own systems, labs, CTFs). You are responsible for how you use it.
