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.