Threat model: MCP endpoint

Scope: mcp.ainumbers.co, the Cloudflare Worker that runs deterministic kernels for AI agents. Written from the worker source (worker.mjs, wrangler.jsonc in the MCP worker repository) on . All threat models.

Assets

Trust boundaries

Adversaries

In scope and out of scope

In scope: resource exhaustion at the application layer; request-size abuse; cross-caller data exposure; accidental retention of inputs; unauthorised browser origins.

Out of scope: volumetric network attacks (left to the edge provider); a compromise of the Cloudflare account; the legal correctness of any model a kernel implements; the weekly self-check fixtures, which hold no user content.

Controls

ThreatControlWhere it lives
M1: request floodFour rate-limit bindings: per-IP 60/60 s before parsing, per-IP 30/60 s, global 300/60 s, and a separate global 1200/60 s bucket for static discovery calls so tool-list walks cannot starve computewrangler.jsonc (unsafe.bindings), rateLimitExceeded() in worker.mjs
M1: limiter silently absentAn unprovisioned binding reads as undefined and the function does not block. That is a known fail-open; the two-step rollout was chosen so the binding was proven live before it was relied oncomment above rateLimitExceeded(); no gate detects a missing binding
M2: oversized body1 MiB cap, checked on Content-Length first and again on the read text, returning 413MAX_REQUEST_BODY_BYTES in worker.mjs
M3: retention of inputsThe analytics writes carry tool name, network number, outcome, correlation id, latency and chain depth for tool calls, and client name, client version, user agent and network number for initialize. No argument or output field is passed to writeDataPointworker.mjs, the two ANALYTICS.writeDataPoint call sites
M3: IP address stored by usThe IP is used only as the rate-limit key. It is not written to the analytics datasetrateLimitExceeded(); comments at both write sites record that the truncated IP hash was dropped
M3: stateless sessionsThe Mcp-Session-Id issued at initialize is a random UUID echo token, accepted but never required, and keeps no server stateworker.mjs, initialize handler
M4: wrong kernel resultCanonical hash path, golden-parity and forbidden-hash lint over kernelschaingraph/kernels/{lint-forbidden-hash,golden-parity.test,syntax-check}.mjs in the site repository
M4: duplicate tool names break the handshakeName-uniqueness check before any tool is addedmcp-apps-poc/scripts/check-tool-names.mjs
Browser origin abuseCORS reflects only allowlisted originsALLOWED_ORIGINS in worker.mjs

UNTESTABLE claims

Residual risk