This page walks from "no account" to "a completed assessment you can read". The browser flow and the API reach the same objects; use whichever fits.
1. Create the account and workspace
Sign in at app.offense.sh. The first sign-in bootstraps a workspace, and every
assessment, key, target and finding belongs to that workspace. There is no
separate "create organization" step for the default case.
Authentication is Clerk in production. In a deployment without
CLERK_SECRET_KEY the app falls back to a mock email session, which is a
development convenience and not a security boundary.
2. Add the target
Open /app/onboarding/target and enter the staging URL. Registration is
normalized and private addresses are rejected, so localhost, 169.254.x,
RFC 1918 ranges and similar are refused before anything is stored.
3. Prove you control it
Control verification happens before any probing:
- Offense resolves the host through DNS-over-HTTPS (
cloudflare-dns.com) and rejects private answers, including after redirects. - You serve the verification token at
/.well-known/offense-verify.txt. - The check has a 4 KB response cap and a 5-second timeout, and redirects are rejected rather than followed.
Registering a target returns the token. From the API:
curl -sX POST https://app.offense.sh/api/v1/targets \
-H "Authorization: Bearer $OFFENSE_API_KEY" \
-H 'content-type: application/json' \
-d '{"target":"https://staging.example.com"}'
Then serve the token and confirm:
curl -sX POST https://app.offense.sh/api/v1/targets/$TARGET_ID/verify \
-H "Authorization: Bearer $OFFENSE_API_KEY"
4. Add two identities from two organizations
The core check is cross-tenant access, so you supply two logins that belong to
different organizations, each with a sample record. Passwords are encrypted with
AES-GCM (CREDENTIALS_KEY) and are readable only by the runner over a
runner-only credential endpoint. They never appear in findings, evidence, logs or
model prompts.
For nonstandard sign-in forms you can add CSS selectors. For SSO, MFA or CAPTCHA
targets the supported path is to sign in yourself and paste a Playwright
storageState; there is no automated login bot.
5. Review, then launch
The review screen shows the verified target, the identities in use, the budget,
and the agent count. 1 agent runs a normal assessment; 2 or more runs a
swarm with distinct focus areas per agent. Nothing is spent before you launch.
6. Get an agent key
Device login avoids copying a long-lived secret out of a browser:
curl -sX POST https://app.offense.sh/api/device/code \
-H 'content-type: application/json' -d '{"name":"my-laptop"}'
# → 201 {"device_code":"…","user_code":"ABCD-EFGH",
# "verification_uri":"https://app.offense.sh/activate?code=ABCD-EFGH",
# "expires_in":600,"interval":5}
Open verification_uri, sign in, approve, then exchange:
curl -sX POST https://app.offense.sh/api/device/token \
-H 'content-type: application/json' -d '{"device_code":"…"}'
# → 200 {"access_token":"offense_sk_…","scopes":["targets:read", …]}
While unapproved this returns 428 authorization_pending; keep polling. A
denied code returns 403 access_denied, an expired one 400 expired_token.
The code is single-use. Device login issues a launch-preset key valid for 90
days.
Then install the CLI or the MCP server:
npm install -g --allow-remote=all https://app.offense.sh/offense-cli.tgz
offense login
offense whoami
npm install -g --allow-remote=all https://app.offense.sh/offense-mcp-0.2.3.tgz
claude mcp add offense -- offense-mcp
The MCP server runs locally over stdio. There is no remote MCP endpoint.
7. Read the result
GET /api/v1/sandboxes/:id gives the run state, GET /api/v1/sandboxes/:id/findings gives findings, and GET /api/v1/sandboxes/:id/result gives the structured outcome. In the browser the
run page shows the phase timeline, the live event log, findings, spend and
coverage.
Which scope your call needs
Launching, driving and forking a sandbox all need assessments:create. Reading
findings needs findings:read. Scheduling a loop needs loops:write, but a
loop is only configured by that scope — see API keys and
scopes for why loops:write cannot spend by itself.
Billing
Plans and credits are monthly. The first code scan requires an active monthly
plan (402 monthly_plan_required with a checkoutUrl). Budget is reserved
before model calls and the unused portion is refunded on a terminal status or a
cancel.