Threat model
What AgentPack defends against, and — just as importantly — what it does not.
In scope
1. Cross-agent data exposure via a compromised device
Defense: every Cloud Function rewrites agent_id from the verified device
binding before any SQL executes. A leaked device key k bound to agent A
cannot read, write, or delete any row owned by B, even if the caller
forges the body.
2. Prompt injection in inbound email
Defense: postbox.detect_prompt_injection(body) returns a 0–1 score.
postbox.ingest() stamps injection_score and quarantined=(score > 0.8)
on every inbound row. The SDK and Pocket UI display a badge. Agents that
choose to auto-act on inbound mail can gate on the score.
3. Audit tampering
Defense: audit.events is hash-chained and read-only to every role
except service_role. audit.verify_chain() is idempotent and a daily
cron records the result in audit.monitor. Silent row deletes leave
first_bad_id non-null.
4. Email sender abuse
Defense: postbox.outbound_suppressions hard-blocks sends to addresses
that have hard-bounced recently. is_hard_bounce(reason) auto-adds
addresses after record_bounce events classified as terminal. Per-agent and
per-minute/day quotas live in postbox.send_quotas.
5. Tunable-driven privilege escalation
Defense: identity.set_safe_setting() has an allow-list of keys and
their JSON-schema contract. A caller cannot write an arbitrary
service_role_key through /settings/set.
6. Stale credentials
Defense: device keys, delegations, and capability tokens have TTLs and
revoke_* RPCs. Revocations append to the audit log. Verifiers check
revocation before honoring a token.
7. PSK reuse across peer pairs
Defense: WireGuard PSKs in mesh.psk are anchored on
(agent_lo, agent_hi) pairs and rotated per-pair. mesh.rotate_psk(a, b)
generates a fresh 32-byte secret and never returns it through the edge
layer — callers re-fetch via wg_config through their normal authed path.
8. Webhook replay
Defense: inbound webhooks carry an HMAC of the body plus an
x-agentpack-webhook-timestamp header. agent-postbox/webhook/rotate
supports dual-secret grace windows so rotations don't drop deliveries.
Out of scope
We are explicit about what we don't defend against. Listing them keeps the security story honest.
1. A compromised Firebase service role key
If FIREBASE_SERVICE_ROLE_KEY leaks, everything is readable and
writable. The Go identity service's Ed25519 signing key is held outside
Firebase precisely because of this — capability tokens stay unforgeable
even in a full DB compromise, but the ledger and data would be exposed.
Rotate the service role key on any suspicion.
2. A hostile Firestore admin
AgentPack assumes the DBA is trusted. Encrypted column values (secrets) protect against DB snapshots and replicas, not against live SQL access.
3. DoS on the edge runtime
Cloud Functions share a runtime. We rely on the platform's rate
limits; AgentPack has no per-IP throttle beyond what postbox.send_quotas
enforces at the application layer.
4. A malicious agent acting within its scope
If agent A is compromised, it can send spam from A's outbound address,
burn A's budget, and leak A's memory. Cross-agent damage is what we
stop. Self-harm by one agent is contained by the Governor budgets but not
fully prevented.
5. Supply-chain compromise of a model provider
AgentPack does not inspect model outputs. An exfiltration attack encoded in a response body will pass through unless the agent integrates something like the Governor's allow-listed domain policy.
6. Pocket PWA device theft
The Pocket install stores a device key at rest with OS keychain protection but will trust the device. A stolen, unlocked phone with Pocket open is equivalent to an authenticated session on that agent.
Defense-in-depth choices
- Least privilege by default. Edge functions bind to one schema each.
- No SELECT * to service_role by convention. RPCs return only what the caller needs, which also keeps payloads tight.
- Everything interesting is logged.
audit.eventsis the single source of truth for "what happened." If a mitigation exists, it should append a row. - Rotate, don't expire. Every long-lived credential (device key, webhook secret, WireGuard PSK) has a rotate RPC so operators never feel pressure to keep a weak secret alive.