OBEP Security Model
This document states the trust model plainly so a customer's security team can
audit it from the open /obep code alone. The governing rule: anything whose
secrecy would create a vulnerability is in open OBEP; anything whose secrecy only
protects the business may be in closed OpenErrand. The proof the split is
honest: even a fully malicious OpenErrand relay cannot read a vault or widen a
playbook, because the open extension re-verifies everything.
#Trust anchors (all open, all in /obep)
- Playbook signing/verification — Ed25519 over RFC 8785 (JCS) canonical JSON. The tenant signs; the relay holds only the public key; the extension re-verifies the signature and the content hash against a locally-trusted key before executing. (protocol, extension enforcer)
- Permission enforcement — default-deny, per-action, client-side, identical for deterministic steps and LLM-fallback commands. (enforcement)
- The wire protocol + token formats — pairing tokens, identity assertions, and task tokens are all open and verifiable. (wire, token)
- The relay protocol contract — encoded as the runnable conformance suite.
#"Even a malicious relay cannot…"
| Attack | Why it fails |
|---|---|
| Swap in a wider playbook | Extension recomputes the hash and re-verifies the tenant signature; a widened body fails both. |
| Re-sign a widened playbook | The relay never holds the tenant private key; a different key fails the extension's check. |
| Read a credential | fillSecret carries only a vault key reference; the value is AES-GCM encrypted on-device and resolved at the moment of use. The relay never sees it. |
| Route across tenants | tenantId is derived from the app's authenticated API key, never from a message; routing requires a matching (tenantId, userId) binding. |
| Read another tenant's audit | /audit/:tenantId requires that tenant's API key (cross-tenant ⇒ 403). |
| Replay a task/pairing token | Tokens are single-use (nonce-tracked) and short-lived (exp). |
#Layered defenses for "no sensitive data leaves" (in order of strength)
- Capture minimization (strongest). Default is the stripped interactive-element list — labels/refs/types, no values, no screenshot. No payload ⇒ nothing to leak.
- Playbook domain allowlist. Can't reach a surface ⇒ can't capture it. Hard stop.
- Egress lock. The extension's
connect-srcCSP permits only secure transports (https:/wss:, pluslocalhostfor dev) — never cleartext remote origins — and the code opens exactly one connection: to the relay (and, if configured, your decider) endpoint you set. The single reachable endpoint is fixed by configuration, not by the CSP host list; lock it down in fleet deployments via managed-config pinning (ENTERPRISE_DEPLOYMENT.md). - Local redaction (layer 4, best-effort). Regex + Luhn + entropy over labels/DOM before transmission. Catches structured PII/keys on allowed pages we didn't anticipate. Not a guarantee — unstructured PII (names) needs NER and is out of scope. (redact)
- Dry-run recorder + audit. Developers see leaks before launch; runtime audit logs that a capture occurred (domain + hash), never the content.
Redaction is layer 4, not layer 1. Capture minimization and the allowlist are what keep secrets in; redaction is the safety net. Customers must not treat redaction as a guarantee.
#Credential vault
- AES-GCM via Web Crypto, key derived from a user passphrase (PBKDF2, 210k iters),
encrypted before anything touches
chrome.storage.local(neversync). - Namespaced per binding, with the binding key as AES AAD ⇒ cross-binding reads fail cryptographically.
- Decrypt-at-moment-of-use; the key lives only in service-worker memory.
- We cannot decrypt a vault server-side and cannot recover a lost passphrase — by design.
#Extension hardening
- Outbound-only WSS; reconnect with exponential backoff + jitter.
- Sender validation: a relay-signed, single-use task token is verified against the relay's public key before the extension acts — a message is never trusted just because it arrived on the socket.
- Only touches tabs it opened; no remote code, no
eval; strict CSP. - Kill switch detaches the connection, aborts tasks, and locks the vault instantly.
#What we deliberately cannot do
- Decrypt a user's vault server-side (we never hold the key).
- Recover a lost vault passphrase (offer re-entry, not a backdoor).