# Security Model
Claw Patrol is a forward proxy that intercepts outbound traffic (HTTPS, SSH, Postgres, …), injects credentials on behalf of the agent, and enforces policy. The agent — an AI tool, a script, a batch job, anything we won’t hand raw secrets to — sees the result of the authenticated operation but never the credential.
This page describes how Claw Patrol stops a hostile agent from reading injected credentials, using another agent’s credentials, or reaching Claw Patrol’s own administrative surfaces.
The agent must not be able to:
- read any injected credential,
- use credentials assigned to a different agent,
- read Claw Patrol’s state files (SQLite DB, policy, registrations),
- modify the Claw Patrol binary,
- call Claw Patrol’s HTTP API or reach its dashboard.
Two deployment modes: remote (agent and Claw Patrol on separate hosts, isolated by a network) and local (same host, isolated by UNIX users). Remote is strictly stronger.
# Remote mode
Agent host and Claw Patrol host are separate. The agent host initiates a WireGuard tunnel during onboarding; the tunnel stays up for the life of the registration.
# Registration
Starts on the agent host, finishes with a human approving in the Claw Patrol dashboard:
- Agent host calls Claw Patrol with its public IPv4 + IPv6 addresses.
- Claw Patrol records them and issues a join credential — the only Claw-Patrol-issued secret the agent host ever holds.
- Agent host brings up the WireGuard tunnel. Tunnel up, registration unapproved: zero traffic forwarded.
- Operator approves in the dashboard and assigns one or more profiles. Traffic begins flowing.
A leaked registration endpoint is worthless on its own: no human approval, no credentials, no traffic.
# What lives where
| Host | Holds |
|---|---|
| Agent host | The join credential. Nothing else of value to Claw Patrol. |
| Claw Patrol host | All injected credentials, the state DB, the policy, the dashboard, the HTTP API. |
Because injected credentials never reach the agent host, the agent can have root on its own host and still not compromise Claw Patrol. This is the strongest property remote mode buys you.
# Traffic flow
Per protocol:
- HTTPS — Claw Patrol terminates TLS with a local CA whose root was installed in the agent’s trust store at onboarding. Decrypted, the request is inspected, the credential injected, the request re-encrypted with the destination’s real cert, then forwarded.
- SSH / Postgres / other authenticated protocols — Claw Patrol completes the upstream authentication handshake with the real credential, then proxies the authenticated session back to the agent. The agent never participates in auth and never sees the credential.
- Non-credentialled traffic (public web, DNS) — forwarded unchanged.
Non-credentialled traffic is outside the security surface. If the agent bypasses the tunnel, it gets the same internet it would have without Claw Patrol — no credential leaks, just no protection.
# Leaked join credential
The join credential can leak: from a backup, shell history, a compromised process on the agent host. To bound the damage, Claw Patrol pins each join credential to the exact IPv4/IPv6 pair the agent host presented at registration. A request from a deviating pair — different v4, different v6, or v6 on a host that registered with v4 only — blocks the credential in the state DB and tears down the tunnel. Restoring access takes explicit re-approval.
Two caveats: IPv6 privacy extensions rotate the source address — disable them or deploy a stable prefix scheme. And an attacker on the same NAT shares the public v4, so pinning isn’t a standalone defence; it’s a blast-radius limiter for credentials that have already escaped.
# Local mode
Agent and Claw Patrol on the same host. No network between them, so the boundary moves into the OS.
Local mode is strictly weaker than remote. In remote mode, nothing on the agent host can hurt Claw Patrol. In local mode, injected credentials sit on the same physical machine as the agent, separated only by UNIX permissions.
# UNIX user separation
Two accounts:
- The agent user — the agent runs here, normally the primary interactive user on a desktop install.
- The Claw Patrol user — an unprivileged service account created at onboarding; the Claw Patrol process runs here.
The agent user can’t read the state DB (owned by the Claw Patrol
user), can’t replace the binary (owned by root or the Claw Patrol
user), and can’t read the dashboard’s access token. Recovering the
token uses sudo clawpatrol get-token, which requires a password
the agent can’t supply.
# Host preconditions
Two properties must hold; Claw Patrol can’t enforce them itself:
- The agent user is not root-equivalent.
- The agent user cannot use
sudowithout a password.
Passwordless sudo for the agent user defeats the entire model.
# Defense in depth
Claw Patrol’s proxy listener, HTTP API, and dashboard all bind to loopback only in local mode. UNIX user separation is doing the real work; loopback bind closes accidental network exposure.
# Pre-existing secrets on the host
A local install lands on a host that likely already contains credentials the agent user can read — shell dotfiles, credential helpers, cloud CLI configs, SSH keys. These are outside Claw Patrol’s control. Onboarding offers to import recognised credentials and delete the originals; anything not recognised or not migrated stays readable to the agent.
# Dashboard and management API
Everything the agent must not reach — credential storage, profile assignment, human-in-the-loop decisions, registration approval — sits behind the dashboard’s HTTP API. Network reachability alone must never grant access to it.
# App-layer auth, on every bind
The dashboard refuses to serve any management endpoint until an operator credential has been established at the app layer. Network- layer reachability is treated as cheap defense in depth, never as the trust boundary. This is non-negotiable: an agent that finds its way onto the same network as the gateway — including the tailnet that the gateway joined — must still be denied.
Why we cannot rely on network reachability:
clawpatrol joinpersists a Tailscale node identity (machine key- node key) under
~/.config/clawpatrol/tsnet-client/. Anyone who can read that directory can stand up a tsnet server and rejoin the tailnet as the same peer, indefinitely.
- node key) under
- That tailnet peer can route to the gateway’s tailnet IP. Without app-layer auth, "I’m on the tailnet" would silently equal "I am an operator." It must not.
# First-run root password
On a fresh install the dashboard has no operator yet. The first
request — from anywhere — is redirected to a "set password" form;
the chosen password becomes the bcrypt-hashed root row in
clawpatrol.db. Subsequent requests must present that password
(via the cp_dash cookie or the X-Clawpatrol-Secret header).
The first-run window is benign by construction: the dashboard is
the only path that creates credentials / profile assignments /
HITL decisions, and all of those endpoints sit behind the same gate
the first-run flow protects. So no sensitive state can predate the
root password — losing the first-run race to an attacker means
they hold an empty dashboard. Recover with
clawpatrol gateway --reset-dashboard-password.
To skip the web first-run entirely, set the password from the CLI before the dashboard ever serves a request:
clawpatrol gateway --set-dashboard-password '<pw>' gateway.hcl
# Tailnet operator allowlist (tailscale block)
When the tailscale {} block is declared the gateway can additionally
accept requests on the strength of a Tailscale whois identity, gated
by an explicit allowlist inside the same block:
tailscale {
authkey = "{{secret:TS_AUTHKEY}}"
operators = ["alice@example.com", "*@example.com"]
}
The gateway pulls the whois login directly off the tsnet socket
(LocalClient.WhoIs), so this is a kernel-attested per-peer
identity, not a forgeable header. Tagged devices — the shape
operators use for agent service accounts (tag:cp-agent) — return
their tag name from whois, not a user login, so a *@example.com
wildcard never matches an agent.
Allowlist auth composes with password auth: either gets a request in. The first-run password is still mandatory, so an operator can always fall back to it (and tests / break-glass paths don’t depend on a working tailnet).
# Untagged-key prohibition
A subtle failure mode worth calling out: if the gateway ever minted
a Tailscale auth key with an empty tags list, the resulting node
would be "owner-associated" — whois on its requests would return
the OAuth client owner’s user login, not a tag. With
tailscale { operators = ["*@example.com"] } configured, that node
would silently match the allowlist and inherit operator powers.
The auth-key minting path
(onboard.go → mintTailscaleAuthKey) refuses to call Tailscale’s
create-key API with an empty tag list — it both defaults to
tag:client and errors out if the default is somehow stripped.
Treat the comment block at that call site as load-bearing.
# Out of band
/api/onboard/{start,poll,claim}, /info, /ca.crt, and the
plugin webhook prefix (/api/cred/...) are intentionally
reachable without the dashboard password — they carry their own
auth (signed onboarding handshake; webhook signature header) or
need to be reachable before any credential exists (CA fingerprint
fetch, fresh client onboarding). The full route table lives in
web.go:routes(); every other path is gated.
# Isolation between agents
One Claw Patrol instance can serve many agents, each with its own credentials. A hostile agent must not be able to make Claw Patrol inject credentials assigned to a different agent.
Claw Patrol enforces this by scoping injection to the originating registration. Each registration is assigned one or more profiles; each profile names a set of credentials. The originating registration is identified from the channel the request arrived on — the WireGuard peer (remote) or the authenticated local channel (local) — not from anything the agent can claim. From there:
- Only credentials from the originating registration’s profiles can be injected.
- A request for a service whose credentials live only in another registration’s profile is treated like a request for a service Claw Patrol has no credentials for — forwarded without injection or rejected by policy, never signed with the wrong agent’s key.
Default-profile auto-assignment is a UX convenience for fresh registrations; the security-relevant property is the scoping rule above.
# Out of scope
Claw Patrol does not defend against:
- physical access to the Claw Patrol host;
- compromise of the Claw Patrol host or user — any attacker with those privileges holds every injected credential;
- a kernel or hypervisor compromise that bypasses UNIX user separation;
- supply-chain compromise of the binary or its build toolchain;
- cross-user side channels (shared-CPU timing, etc.).