Threat model: anchor relay
Scope: anchor.ainumbers.co, a Cloudflare Worker (src/worker.mjs, wrangler.jsonc in the Anchor Suite repository) that relays timestamp requests to public authorities and exposes a small MCP tool set. Written from that source on . All threat models.
Assets
- Timestamp proofs. A returned proof must be the authority's real reply, unmodified.
- The hash a visitor submits. It is a digest, not the artifact, but it can still identify a document to anyone who already has the document.
- Upstream goodwill. The relay spends the free quota of third-party authorities. Abuse would get the relay blocked.
Trust boundaries
- Browser or agent to the Worker, over TLS. No authentication (
authentication: nonein the discovery document). - Worker to third-party timestamp authorities: FreeTSA, DigiCert, Sectigo, Sigstore, GitHub, four OpenTimestamps calendars and the Sigsum log. These operators receive the request and see the relay's address, not the visitor's.
- Static pages (
public/) are served by the platform without invoking the Worker. Only/relay/*and/mcprun code.
Adversaries
- A1. A client that floods the relay to exhaust upstream quotas.
- A2. A client that sends large or malformed bodies, or uses the relay as a general proxy.
- A3. A hostile origin that calls the relay from a victim's browser.
- A4. A hostile or faulty authority that returns a bad proof.
In scope and out of scope
In scope: relay abuse, request size, who may call from a browser, what the relay stores about a request, and the page headers on the anchor site.
Out of scope: the trustworthiness of each timestamp authority; the legal weight of a timestamp; availability of upstream services.
Controls
| Threat | Control | Where it lives |
|---|---|---|
| A1: flood | 4 requests per minute per IP, enforced by a rate-limit binding; over-limit callers get 429 with Retry-After | wrangler.jsonc (RELAY_LIMITER), relay and tool handlers in src/worker.mjs |
| A2: oversized body | 2048-byte cap on relay bodies, 413 above it; empty bodies refused | MAX_BODY_BYTES in src/worker.mjs |
| A2: generic proxy | Only POST with Content-Type: application/timestamp-query is accepted, and only on fixed relay paths for named authorities (unknown paths are refused); other methods and content types get 405 or 415 | relay handler in src/worker.mjs |
| A2: batch size | Batch anchoring accepts 2 to 1024 hashes | toolAnchorBatch in src/worker.mjs |
| A3: browser origin | Relay CORS is set to https://anchor.ainumbers.co; MCP browser origin allowlist holds only the ledger origin | ALLOWED_RELAY_ORIGIN, MCP_ORIGIN_ALLOWLIST in src/worker.mjs |
| A4: bad proof | Relay replies are passed back as the authority sent them, with no-store; a separate tool checks a binding | verify_anchor_binding tool and relay handler in src/worker.mjs. This page does not claim the check is offline or complete |
| Stored request data | The Worker source contains no logging calls and no storage bindings; the rate limiter keeps only a counter keyed on IP | src/worker.mjs (no console.* calls), wrangler.jsonc, comment "stores nothing else" |
| Anchor site pages | Strict header set: default-src 'none', trusted types required, frame-ancestors 'none', HSTS, no referrer, camera, microphone, geolocation and payment disabled | public/_headers |
UNTESTABLE claims
- "Stores nothing." The source has no write path, and that is what this page relies on. Cloudflare's own request logging for the Worker is not controlled from the code and is not tested here; see data handling.
- What each authority retains. Third parties receive the hash and log it under their own terms. We cannot test or limit that.
- That the headers are served. The header file is in the repository. We did not find a gate in the site repository that fetches the live anchor site and compares headers.
- The 4-per-minute limit on the live service. It is configured in code, not probed.
Residual risk
- A hash submitted for anchoring is disclosed to the chosen authorities, and some of them publish what they stamp (a Bitcoin-anchored calendar and a public transparency log are permanent). Submit a digest only if you are content for that digest to be public forever.
- A client behind shared infrastructure shares the per-IP limit, and an attacker with many addresses can still consume upstream quota.
- The relay is a single point of failure for in-browser anchoring; verification of an existing proof does not need it.