08 — Security & Privacy

Authentication mechanisms

Credential Used by Verified how Lifetime
SIWE signature Users at login Signature recovery → wallet address One-shot (nonce)
Session JWT Terminal → agent, terminal → gateway HMAC-SHA256 with shared JWT_SECRET_KEY Short-lived
API key hk_live_… Programmatic clients SHA-256 hash lookup Until revoked
x402 payment External agents Facilitator /verify Per request
Service token Worker → agent (X-ACP-Token), → platform (X-API-KEY) Constant string compare Rotated manually

API key handling

Keys are generated with secrets.token_urlsafe(32), returned once, and stored only as a SHA-256 hash with a short display prefix. There is no reveal endpoint because there is no stored plaintext. Revocation is a soft revoked_at timestamp, which keeps historical usage rows joinable for billing while immediately failing authentication.

Why a shared JWT secret

The agent issues SIWE sessions and the gateway must accept them so users can manage keys with the session they already have. The alternative — a second login against the gateway — is worse UX for no security gain, since both services sit in the same trust zone.

Authorization model

Two independent axes, both enforced at the gateway:

  1. Tier — derived from chain state (min_tier per model). Pay-per-call requests are additionally capped, so per-call payment cannot reach models reserved for subscribers.
  2. Quota — monthly credits per tier, checked against the usage ledger before every request; 402 on exhaustion.

Tier resolution fails closed: if the platform API is unreachable and no cached value exists, the wallet resolves to tier 0 and premium models are denied. A cached value is preferred over an outage-induced downgrade for already-known wallets, bounding the blast radius of a platform hiccup.

Privacy guarantees

This is a product feature, so it is enforced structurally rather than by policy — with one honest caveat below on where prompts currently travel.

Guarantee How it is enforced
Prompts are not persisted by us by default The usage table has no prompt column — it records token counts only
No training on user data We run no training pipeline on inference traffic
Retention is opt-in per key Retention defaults to off; a customer must explicitly enable it for their own key to aid debugging
No KYC/PII collected Identity is a wallet address; no email, no name

Current upstream: OpenRouter, not our own cluster. Inference is routed through OpenRouter while our owned hardware (2× DGX + 3× AGX) is not yet production-ready — see the decision log. This means prompts leave our infrastructure and are subject to OpenRouter's data policy and that of whichever model provider serves the request, not only ours. The "no third-party model sees user data" guarantee does not hold during this period. It returns once inference moves onto owned hardware, which is the plan, not a promise already kept. Do not market self-hosted privacy as a present-tense fact until that migration lands.

These properties are structural rather than policy promises: storing prompt content would require a schema change, and therefore an explicit, reviewable decision. Describe them accurately — "not retained by default, retention is opt-in per key" — rather than as absolutes.

Trust boundaries and what crosses them

Boundary Crosses Never crosses
Internet → gateway API keys, JWTs, payment payloads, prompts (in memory only) Cluster topology, upstream model ids
Gateway → cluster Prompt payloads (transient) User identity, wallet addresses
Workers → agent Service token, job parameters User credentials
Agent → MCP servers Tool arguments API keys of the caller
Anything → chain Signed user transactions Private keys (users sign in their wallet)

Threat model

Threat Mitigation Residual risk
Stolen API key Hash-at-rest, instant revoke, per-key usage visible in /usage Bounded by per-wallet quota; keys are revocable immediately
Tier spoofing Tier derived from chain, never from client input Indexer lag (minutes)
Quota evasion via many keys Quota is per wallet, not per key Sybil wallets — bounded by tier cost
x402 replay / fake payment Facilitator verification before service Facilitator compromise
Payment settlement failure Verification precedes service; failures are logged and reconciled Bounded by the per-call price
Keeper key compromise On-chain maxClipSize + minClipInterval + minAmountOut Bounded per-clip slippage
Owner key compromise Ownable2Step, multisig, Pausable Multisig quorum compromise
Reentrancy / token weirdness ReentrancyGuard, SafeERC20, checks-effects-interactions Fee-on-transfer tokens unsupported by design
Prompt injection via scraped content Tool output is data, never instructions; critic gate before publishing Model-level susceptibility remains
Agent posts something harmful Critic node must APPROVE; human gate on publishing initially Reduced, not eliminated
Twitter account ban Read path uses twitterapi.io with no account session (ADR-003) Write account only
Secret leakage in CI No secrets in workflows; tests use fixtures and mock facilitators

Smart-contract security posture

Reporting a vulnerability

Please use GitHub's private vulnerability reporting on the affected repository, or the contact in the org security policy. Do not open a public issue.

HUNTER · $DRPXBT · drpxbt.xyz edit this page