Security
Security standards: a self-assessment
How Counterseal's controls line up with five OWASP standards and lists, with what is met, what is partly met and what is not, in our own words.
Self-assessed. We read our own code and tests against each standard. Nobody else has audited this, Counterseal is not certified against any of these, and OWASP does not certify anyone against them. Where a row says "met", it names the test that checks it, in the source repository. The security page describes each control, and the threat model in the repository explains why.
OWASP ASVS 5.0.0 (May 2025): Level 2, and Level 3 where we meet it
ASVS has 17 chapters. We target Level 2 for the whole service and name the Level 3 requirements we also meet. Chapters that do not apply say why.
| Chapter | Status | How |
|---|---|---|
| V1 Encoding and Sanitization | Met (L2) | Every SQL statement is parameterised; no page writes HTML from text (no innerHTML, enforced by Trusted Types and a CI check); text from agents shown to people has control, invisible and direction characters made visible. Tests: bypass.test.js, approvals.test.js. |
| V2 Validation and Business Logic | Met (L2) | Strict JSON bodies with size caps read while streaming, unknown fields refused, fixed vocabularies; per-account ceilings on everything an account can create; limits on held requests per token and per account against approval fatigue. Tests: second-pass.test.js, hardening.test.js. |
| V3 Web Frontend Security | Met (L2); L3 items on headers met | A strict Content Security Policy with require-trusted-types-for 'script', no third-party code, HSTS, COOP, CORP, frame-ancestors 'none', __Host- cookies with SameSite=Strict. The service worker caches only the app's own files and never the API. Tests: second-pass.test.js, app-shell.test.js. |
| V4 API and Web Service | Met (L2) | JSON only, with the content type checked; methods per route; CORS only on the public log's read endpoints; CSRF: an Origin equal to the one host plus a custom header on every browser write. Tests: domain.test.js, app-trust.test.js. |
| V5 File Handling | Not applicable | Nothing is uploaded. Downloads (receipts, agent keys, the data export, the evidence pack) are made in your browser. |
| V6 Authentication | Met (L2). L3 6.3.3 met with a device-bound passkey or a security key | Passkeys only (WebAuthn, user verification required, origin and RP ID checked, single-use challenges); no passwords exist, so there is no weaker path. Synced passkeys meet L2; L3 asks for a hardware-based factor. Sign-in and approval limits; new command line sign-ins and new passkeys alert your devices. Tests: leash.test.js, cli-signin.test.js. |
| V7 Session Management | Met (L2), except 7.5.2 partly. L3 7.5.3 met | Reference tokens of 256 random bits, stored as hashes; 12 hours at most on the web and 7 days for a command line; a web session also ends after an hour without an action of yours (added on this branch); logout deletes the session. Every sensitive action asks for your passkey again (7.5.3). Command line sessions are listed and can be revoked with everything they made; other browser sessions are not listed yet, but removing a passkey signs them all out. Tests: app-trust.test.js, cli-signin.test.js. |
| V8 Authorization | Met (L2) | Every query is scoped to the account; team roles are checked on the server for every request; a passkey proves presence, not authority. Tests: team.test.js, leash.test.js. |
| V9 Self-contained Tokens | Met where used | Sessions and agent tokens are reference tokens, not JWTs. Agent request signatures (RFC 9421) cover the method, URL, body digest and content type, within 5 minutes, each nonce once. Tests: leash.test.js, joints.test.js. |
| V10 OAuth and OIDC | Not applicable | Counterseal is not an OAuth client, server or identity provider. |
| V11 Cryptography | Met (L2) | AES-256-GCM per vaulted key with its own data key, wrapped by a master key and bound to its row; Ed25519 checkpoints; SHA-256; the platform's random generator. The master key rotates without losing a stored key: only the data keys are wrapped again, the secrets are never decrypted for it (procedure in docs/KEY_ROTATION.md, test key-rotation.test.js). It has not been run on the live service yet. |
| V12 Secure Communication | Met (L2) | HTTPS only, with HSTS for two years on every subdomain (preload asked for; the parent domain is not on the preload list yet); calls to providers go only to their fixed HTTPS hosts (Shopify: to a strictly checked store address); redirects are never followed. |
| V13 Configuration | Met (L2) | Secrets live in the platform's secret store, never in the repository; zero runtime dependencies; request logs off; a generic error to clients. |
| V14 Data Protection | Met (L2) | No secret in a URL (one-time links carry their token in the fragment and POST bodies); Cache-Control: no-store on every API answer; data minimised and listed in the privacy policy; an owner can download all of it and choose how long closed holds are kept; account deletion leaves no row that names the account (tested). Tests: app-trust.test.js. |
| V15 Secure Coding and Architecture | Met (L2) | A threat model; zero runtime dependencies; GitHub Actions pinned by commit; on this branch, an SBOM and npm provenance in the publish workflow. |
| V16 Security Logging and Error Handling | Partly met | Every decision and every security change is in your hash-chained audit log, which you can export and verify. Security events (bad tokens, CSRF, replays, limits) go to a live log stream with no personal data, but are not stored, by choice (request logs are off for privacy); that falls short of keeping security logs for later investigation. |
| V17 WebRTC | Not applicable | No WebRTC. |
OWASP Top 10:2025
| Risk | What Counterseal does |
|---|---|
| A01 Broken Access Control | Account-scoped queries, server-side roles, CSRF checks on the one host, the two-person seal for held actions. |
| A02 Security Misconfiguration | The same security headers on every page, checked by a test against the Worker's own list; request logs off; secrets only in the platform's store. |
| A03 Software Supply Chain Failures | Zero runtime dependencies in the Worker, CLI and SDK; actions pinned by commit; an SBOM and provenance for the published CLI (this branch). |
| A04 Cryptographic Failures | Per-secret AES-256-GCM with additional data, Ed25519, SHA-256, WebCrypto only. The master key rotates by a tested procedure (docs/KEY_ROTATION.md). |
| A05 Injection | Parameterised SQL; Trusted Types; strict parsers for the bodies the irreversible map reads. |
| A06 Insecure Design | A threat model; irreversible actions are held outside the model; dangerous switches need a passkey, never a command line session. |
| A07 Authentication Failures | Passkeys only; device-code phishing defences for command line sign-in; idle and absolute session limits. |
| A08 Software or Data Integrity Failures | The hash-chained audit log, the public Merkle log with signed checkpoints, approvals bound to one exact request and used once. |
| A09 Security Logging and Alerting Failures | Alerts on your devices for held calls, new sign-ins, new passkeys and team changes; the audit log. Security events are not stored (see V16). |
| A10 Mishandling of Exceptional Conditions | Fails closed: an unreadable request is held, an unreachable server makes the guard deny, and a full or broken public log never blocks a decision, which is still written to your audit log. |
OWASP API Security Top 10 2023
| Risk | What Counterseal does |
|---|---|
| API1 Broken Object Level Authorization | Every object is looked up with its account; another account's id gets 404 (tested). |
| API2 Broken Authentication | Passkeys, hashed bearer tokens, short device codes with limits. |
| API3 Broken Object Property Level Authorization | Answers name their fields; no secret column is ever returned (the data export test checks every secret is absent). |
| API4 Unrestricted Resource Consumption | Rate limits on every route, size caps, per-account ceilings, a breaker per token, Workers Free budgets tested (D1 queries and CPU). |
| API5 Broken Function Level Authorization | Roles per route; passkey-only routes refuse a command line session. |
| API6 Unrestricted Access to Sensitive Business Flows | The irreversible map, money caps, the two-person seal, limits on how many holds can wait. |
| API7 Server Side Request Forgery | Fixed upstream hosts, push services from an allow list, no redirects, a strictly checked store address. |
| API8 Security Misconfiguration | As A02. |
| API9 Improper Inventory Management | Every route is in one table in server/src/index.js; the protocol is documented; the map is versioned. |
| API10 Unsafe Consumption of APIs | Upstream answers cannot act on a browser at our origin (their CORS, cookies and policies are dropped); upstream data read for money caps is parsed strictly. |
OWASP Top 10 for LLM Applications 2025
Counterseal runs no model. It sits between AI agents and the APIs they call, so the risks that apply are the ones about what an agent can do.
| Risk | What Counterseal does |
|---|---|
| LLM01 Prompt Injection | A hold does not depend on what the model was told: an irreversible call waits for a person whatever the prompt said. Your deny reason goes back to the agent as data. |
| LLM02 Sensitive Information Disclosure | The agent never holds the real key, so a model that leaks its context leaks only a revocable, scoped token. |
| LLM03 Supply Chain | As A03, for the CLI and MCP server the agent runs. |
| LLM05 Improper Output Handling | What the agent sends is parsed strictly before it is allowed; anything ambiguous is held; hidden characters are made visible to the approver. |
| LLM06 Excessive Agency | The reason Counterseal exists: scoped tokens, irreversible calls held for a passkey, money caps, a breaker and Freeze. |
| LLM10 Unbounded Consumption | Rate limits and the breaker cap calls through Counterseal. They do not cap the model's own token spend. |
| LLM04, LLM07, LLM08, LLM09 | Not applicable: Counterseal trains no model, has no system prompt, stores no embeddings and generates no text. |
OWASP Top 10 for Agentic Applications 2026
Published by the OWASP GenAI Security Project on 9 December 2025.
| Risk | What Counterseal does |
|---|---|
| ASI01 Agent Goal Hijack | A hijacked agent still cannot take an irreversible action through Counterseal without a person's passkey. |
| ASI02 Tool Misuse and Exploitation | The irreversible map (10 APIs) and the coding-agent guard's shell map hold the dangerous calls of each tool. |
| ASI03 Identity and Privilege Abuse | Scoped, expiring tokens instead of real keys; agent keys so a stolen token alone is useless; command line sessions cannot approve. |
| ASI04 Agentic Supply Chain Vulnerabilities | Zero dependencies and provenance for our own CLI. The guard can ask for approval of MCP tool calls; checking third-party tool definitions is not in this branch. |
| ASI05 Unexpected Code Execution | The guard holds destructive shell commands until approved. It runs on the agent's machine: a seatbelt, not a sandbox. |
| ASI06 Memory and Context Poisoning | Out of scope: Counterseal keeps no agent memory. A poisoned agent is still held at the irreversible step. |
| ASI07 Insecure Inter-Agent Communication | Agent-to-broker requests can be signed (RFC 9421) and replays are refused. Agent-to-agent traffic does not pass through Counterseal. |
| ASI08 Cascading Failures | A breaker per token, Freeze for every token at once, rate limits, and fail-closed defaults. |
| ASI09 Human-Agent Trust Exploitation | The approver sees the exact request or command with hidden characters made visible; limits on waiting holds and alerts against approval fatigue; device-code phishing defences; the two-person seal. |
| ASI10 Rogue Agents | Freeze, revocation of tokens and keys, the audit log, and public receipts anyone can verify. |
What is not met yet
- The master key rotation is tested, but has not been run on the live service yet (V11).
- The public log's signing key is never rotated, by design: a new key would look like a forked log. A leak would mean a new log, announced (
docs/KEY_ROTATION.md). - The public log lives in a Durable Object with no restore route; database backups (D1 time travel) do not cover it.
- HSTS preload: the header asks for it, but the parent domain is not on the browsers' preload list yet, so the first visit can still be plain HTTP.
- Security events are streamed, not stored (V16, A09). This is a privacy choice; turning on stored logs would keep IP addresses at Cloudflare.
- Browser sessions are not listed for you to end one by one (V7 7.5.2); removing a passkey ends all of them.
- Level 3 authentication needs a hardware-based factor; a synced passkey is not one (V6 6.3.3).
- No external audit or penetration test has been done.
Fixed while writing this: a web session now ends after an hour without an action (V7 7.3.1); deleting an account now also removes rate limit counters that name it (V14); the app's offline copy is tested never to hold API data (V3, V14).
Sources (read 9 October 2026)
- OWASP Application Security Verification Standard 5.0.0, released May 2025: github.com/OWASP/ASVS
- OWASP Top 10:2025: top10.owasp.org/2025
- OWASP API Security Top 10 2023: owasp.org/API-Security
- OWASP Top 10 for LLM Applications 2025: genai.owasp.org/llm-top-10
- OWASP Top 10 for Agentic Applications for 2026: genai.owasp.org