Skip to main content
Back to Home
Security

Security and known issues.

Last updated: October 3, 2026. Measurements below retain their original dates and version scope.

Current limits and historical security findings

Reference runtime 3.6.6 is published. Use synthetic inputs in disposable isolation for runtime evaluation. Release tests and the changes below do not establish deployment readiness or independent certification.

Current runtime: changes and limits

Since version 3.6.5, the proxy binds to loopback by default, snapshots validated policies, isolates rate limits by proxy, remaps stdio request IDs, preserves split UTF-8, refuses repeated JSON members and bounds transport work. It cancels pending waits on disconnect and cleans up the child after a failed listener bind. Child processes receive an explicit environment without implicit gateway-key variables. The control channel rejects foreign Host and browser Origin headers.

Version 3.6.6 reuses one set of tree levels for the exported proofs. It preserves the signed format and checkpoint construction. Export is still synchronous; proof output grows with receipt count and tree depth.

The embedded verifier fails a supplied malformed expected key; only omission selects an integrity-only check. Signed receipt bytes and the historical specification remain unchanged. Other verifier implementations retain their separately documented input behavior.

Still outside the supplied boundary: client authentication, local-process authorization, privileged key custody, filesystem containment, complete MCP session and server-request handling, durable retention and independent pilot validation. Export remains synchronous. A PERMITTED receipt is a decision record, not proof of execution or complete capture.

Version 3.6.6 passed 488 tests on each of Node 22 and Node 24 in credential-free, network-disabled containers, including exact classical and hybrid export comparisons and a regression that counts hashing operations. A fresh installed package reproduced the retained single-proof output for 257 receipts. The Node 22 and Node 24 archives both match the published bytes. The release gate also passed the retained classical and hybrid corpora. The September 30 record of 461 tests on Node 20 and Node 22 applies to version 3.6.5. Read the version-specific disposition of all thirteen findings. The detailed cases below preserve measurements through 3.6.4.

Current published verifier inputs

On October 3, exact file bytes were checked against npm aga-verify 2.2.3 and Python aga-governance 0.3.2 in an offline disposable container, with an expected signing key. Both reject a changed signed decision and a UTF-8 byte-order mark. Both accept duplicate decision fields when the last value is genuine, and reject the tested record when the last value is changed.

The two CLIs disagree on a leaf index written as 0.0 or 0e0: npm verifies the tested record; Python rejects it. npm also verifies the tested record containing an invalid UTF-8 byte in an unsigned extra field; Python rejects it before signature checks. A verified result authenticates the parsed signed fields. Unsigned fields and file encoding remain outside that authentication.

Preserve the original file when investigating a disagreement. Do not use the historical object-level corpus as evidence of universal file-parser agreement. This nine-input check does not cover Go, the browser verifiers or every malformed input. Read the exact compatibility matrix, input hashes and reproduction procedure.

Each finding below starts with its current runtime 3.6.6 disposition, followed by the preserved historical observation. Proposed remedies in those observations describe the plan at the time of measurement, not a current delivery commitment.

Measurement dates and version scope

Published September 23, 2026. Items 1 to 4 were reproduced that day on @attested-intelligence/aga-mcp-server 3.6.0, installed from npm, and concern the aga-proxy gateway in that package. Item 5, added September 25, concerns the verifiers and was reproduced on September 25 on the current releases. Item 6, also added September 25, concerns aga-proxy with an HTTP upstream and was reproduced on September 25 on 3.6.2. Item 7, also added September 25, concerns tools/call messages that aga-proxy refuses without a receipt and was reproduced on September 25 on 3.6.2 (its oversized-message case was added and reproduced on September 26). Items 8 to 12, added September 26, concern non-ASCII text, the cost of exporting evidence, what policy constraints check, oversized tool results and the control channel; each was reproduced on September 26 on 3.6.2, as were the memory and Windows port cases added to item 1 that day. Item 13, also added September 26, concerns an HTTP upstream's response of 8,388,608 characters or more on Linux and was reproduced on September 26 on 3.6.2. 3.6.1 and 3.6.2 change only documentation files and the package description: their runtime, all 200 files under dist/, is byte-identical to 3.6.0's (compared from the registry tarballs on 2026-09-25), so every item below also applies to 3.6.3 and 3.6.4. The September 30 registry comparison found all 200 runtime files unchanged from 3.6.2. Those releases update documentation and metadata. Version 3.6.5 changed runtime behavior. Version 3.6.6 retains those controls and changes export proof construction; current dispositions appear above.

  1. 1. Unauthenticated agent port and shared-host exposure

    Runtime 3.6.6: Loopback is the default bind. An explicit --host can expose the service. Client authentication and shared-host privilege separation remain outside the supplied boundary.

    Historical observation and measured workaround follow. They describe the versions and dates named below.

    The agent port listens on every network interface, with no authentication. Anyone who can reach the host on that port can send governed calls through the proxy. The proxy also sets no limit on the number of connections, so although each connection's unfinished message is capped (item 7), the memory they hold together is not: on 3.6.2, ten connections that each sent 7.5 MiB without ending a message raised the proxy's working set from 68 MiB to 392 MiB while they stayed open, and a governed call on another connection was still answered. On Windows, a process running under the same user account as the proxy can bind 127.0.0.1 on the same port while the proxy listens, and a local client that connects to 127.0.0.1 then reaches that process instead of the proxy, with no policy check and no receipt (measured on 3.6.2 with both processes under one account). Workaround: block inbound traffic to the port in the host firewall, or admit only the agent with network policy; on Windows, run nothing untrusted under the proxy's account, and check while the proxy runs that its process is the only listener on the port.
  2. 2. JSON-RPC ID collisions between clients

    Runtime 3.6.6: Stdio request IDs are remapped and replies remain associated with their originating request. Repeated in-flight IDs from one client are refused. Complete MCP session support is still outside scope.

    Historical observation and measured workaround follow. They describe the versions and dates named below.

    Two clients that reuse a JSON-RPC id through one proxy can receive each other's tool results. Workaround, measured on 3.6.0: give each client its own id range, or run one proxy per client. With disjoint ids, every result reached the client that asked for it.
  3. 3. Signing-key environment inherited by the upstream

    Runtime 3.6.6: Children receive an explicit environment without implicit signing-variable inheritance, and explicit gateway-key variables are refused. Same-account filesystem and network access remain possible.

    Historical observation and measured workaround follow. They describe the versions and dates named below.

    When the gateway key is supplied through AGA_GATEWAY_KEY (with or without --ephemeral) or AGA_GATEWAY_KEY_FILE, the stdio upstream inherits that variable (the seed, or the file's path), so the upstream sits inside the key's trust domain. Workaround, measured on 3.6.0: run without either variable. The upstream then sees neither, but the proxy signs with a per-process key that cannot be pinned across restarts.
  4. 4. HTTP upstream transport is not MCP Streamable HTTP

    Runtime 3.6.6: The HTTP upstream mode still does not implement MCP Streamable HTTP. Select a supported transport and qualify the actual upstream.

    Historical observation and measured workaround follow. They describe the versions and dates named below.

    The --upstream-url mode forwards raw JSON-RPC over HTTP POST with only a content-type header. It does not implement the MCP Streamable HTTP transport (the Accept header, the request metadata headers and event-stream handling), so a spec-conformant HTTP MCP server rejects its requests. Workaround: bridge to the server over stdio.
  5. 5. Duplicate JSON fields are accepted by verifiers

    Runtime 3.6.6: Proxy transport and policy-file JSON reject repeated decoded member names. Other verifier parsers retain their separately documented behavior; the library receives parsed objects.

    Historical observation and measured workaround follow. They describe the versions and dates named below.

    A bundle file can repeat a field name anywhere: in a receipt, in the checkpoint or in the envelope. For example, a forged "decision": "PERMITTED" can be placed ahead of the signed "decision": "DENIED", or a forged checkpoint leaf_count ahead of the signed one. The published verifiers (aga-verify 2.2.2, aga-governance 0.3.2, aga-proxy verify in aga-mcp-server, and the reference verifiers in the source repository), the /verify page, and verifybundle.com's offline verifier.html and its hosted /verify read the last occurrence, and when it holds the genuine value they report VERIFIED, with provenance when the key is pinned. They do not reject the file, so a tool or a person reading the first occurrence can see a value that was never signed. Measured on the public sample bundle, pinned to the sample key: with the repeated name inserted in a receipt, in the checkpoint or in the envelope, aga-verify 2.2.2, aga-governance 0.3.2, aga-proxy verify in aga-mcp-server and /verify report VERIFIED, and a real change of the checkpoint value fails. Measured on September 25 on a VerifyBundle record whose bundle.json repeats bytes_hash, a forged value first and the signed value last: verifybundle.com's offline verifier.html reports VERIFIED and shows the signed value, and its hosted /verify reads the file with the same last-copy JSON parse. Workaround: treat the verifier's parsed output as the record's content, and reject or flag files with repeated field names before displaying them. A strict rejection of repeated field names is planned for the reviewed release.
  6. 6. Duplicate methods can bypass HTTP policy evaluation

    Runtime 3.6.6: Ambiguous request JSON is refused before either HTTP or stdio forwarding. A syntax refusal is not treated as an attributable tool decision and need not produce a receipt.

    Historical observation and measured workaround follow. They describe the versions and dates named below.

    With an HTTP upstream (--upstream-url), a message can repeat the "method" member, with tools/call first and another method last. aga-proxy reads the last copy, so it never checks the tool call against the policy, and it forwards every method other than tools/call to the HTTP upstream as the exact bytes it received. An upstream whose JSON parser keeps the first copy of a repeated name then runs the tool call, even one the policy denies. The bundle holds no receipt for that call: nothing at all when the last method is one the proxy passes through without a receipt (such as ping, initialize, a list method or a notification), and otherwise only a passthrough receipt that names the last method. An upstream that keeps the last copy handles the message as the method the proxy read. The stdio upstream, the default, is not affected: the proxy re-serializes each message before writing it, so the upstream receives one method. Measured on 3.6.2 from npm on September 25, with the restrictive profile and with the default permissive profile. Workaround: keep the stdio default, or have the HTTP upstream reject any message that repeats a member name. A strict rejection of repeated member names in the proxy is planned for the reviewed release.
  7. 7. Some refused calls leave no receipt

    Runtime 3.6.6: Missing or invalid tool names and uncanonicalizable arguments receive denied receipts and responses. Invalid syntax, oversized frames and connection or resource refusals need not produce receipts. Complete capture is not established.

    Historical observation and measured workaround follow. They describe the versions and dates named below.

    aga-proxy does not record every tools/call it refuses. It signs a tools/call's receipt before it forwards the call, so with a stdio upstream a call it cannot record never reaches the tool (for an HTTP upstream, see item 6), but such a call leaves no receipt. These cases were measured on 3.6.2 from npm on September 25, each with no receipt and no response to the client: a tools/call whose tool name is a non-zero number, an array or an object (a name of 0, false, null or an empty string counts as no name and gets a DENIED receipt and an error); one whose tool name or string id holds an unpaired surrogate (the JSON escape \ud800, for example); under a --policy file in allowlist or denylist mode whose constraints member is missing or null, every tools/call with a tool name and arguments the proxy can canonicalize; and, under an allowlist file, a call it would otherwise permit that carries a string path when that tool's path_prefix is neither a string nor false, 0 or null. The proxy starts with such a policy file, and it reports each refusal listed above only on its own stderr. A message sent as a JSON-RPC batch array or without "jsonrpc": "2.0" is refused differently: the client gets an error, and there is no receipt. A message of 8,388,608 characters or more (UTF-16 code units, about 8.4 million), not counting the newline that ends it but counting a carriage return before that newline, also gets an error and no receipt, and the proxy then closes the connection, dropping any reply still due on it. The limit counts input not yet split into messages, so a message just under the limit can be refused the same way when the read that completes it also carries enough input after it to pass the limit; whether that happens depends on where the reads fall. On 3.6.2, when a 100-character message, a message 58 characters under the limit and a 100,000-character message were sent in one write, the first was answered, the second got the error and the connection closed; sent without the 100,000-character message, or without the 100-character one, every message was forwarded. These are the cases measured, not a proof that no other input does the same. Workaround: give every policy file a constraints object whose path_prefix values are strings, and have the client time out a call that gets no reply. A DENIED receipt and an error for a malformed tool name, and a check of the policy file at startup, are planned for the reviewed release.
  8. 8. Split non-ASCII input can be altered

    Runtime 3.6.6: Framing preserves split UTF-8 characters and refuses malformed UTF-8. Arbitrary downstream protocol compatibility is not established by that correction.

    Historical observation and measured workaround follow. They describe the versions and dates named below.

    aga-proxy can alter non-ASCII text whose bytes are split between two reads. It decodes each chunk it reads from the agent's connection, and from a stdio upstream's output, on its own, so a character split between two chunks becomes one or more replacement characters (U+FFFD). A tool call's arguments can then reach the upstream altered, and the receipt's arguments hash is the hash of the altered arguments; a large non-ASCII result from a stdio upstream can reach the agent altered. An HTTP upstream's result is decoded whole. Whether a split happens depends on how the bytes arrive, so any message with non-ASCII text can be affected, and large ones more often. Measured on 3.6.2 from npm on September 26: a forced split inside "é" reached the upstream as two replacement characters, and a result of 200,000 "€" reached the client with 15 replacement characters in it. Workaround: send JSON whose non-ASCII characters are written as \uXXXX escapes, so every byte the proxy reads from the agent is ASCII, and have a stdio upstream do the same; a forced split of the escaped message then arrived intact. A fix is planned for the reviewed release.
  9. 9. Exports block calls and can cause retries

    Runtime 3.6.6: Proofs share tree levels, removing repeated whole-tree hashing. Export remains synchronous and can still block calls or contribute to timeouts after an upstream effect. Do not interpret a timeout as proof that the tool did not run.

    Historical observation and measured workaround follow. They describe the versions and dates named below.

    Exporting the evidence bundle takes time that grows with the square of the number of receipts, and aga-proxy handles nothing else while it runs: every governed call waits until the export ends. A call forwarded to a stdio upstream that has not answered when an export starts gets a timeout error if the export ends more than 30 seconds after the call was forwarded, although the upstream ran it and its receipt says PERMITTED, so an agent that retries can run the tool twice. Measured on 3.6.2 from npm on September 26 through the control channel's GET /export: 2.8 seconds at 1,000 receipts and 17.4 seconds at 2,500, with a tools/call sent during the export waiting as long; at 4,000 receipts an export took 43.8 seconds, and a call forwarded just before it, which the upstream answered in 2 seconds, got the timeout error. At 1,000 to 4,000 receipts a compact bundle took about 1.7 to 1.9 KB per receipt, rising with the count. An audit the same day measured 88 to 113 seconds at 5,000 receipts. Verification time grows close to linearly. Workaround: each export covers every receipt since the proxy started and does not shorten the chain, so bound the chain by restarting the proxy on a schedule: pause the agents, export and verify, then restart. A restart begins a new chain that is not linked to the last one and resets the rate-limit counts; with a per-process key (--ephemeral, or neither AGA_GATEWAY_KEY nor AGA_GATEWAY_KEY_FILE set) it also begins a new signing key, printed at startup, that cannot be pinned across restarts (item 3). Export outside busy periods, and export and verify before any stop, because the live chain is kept in memory and a stop loses receipts not yet exported. A fix that leaves the bundle's bytes unchanged is planned for the reviewed release.
  10. 10. Policy constraints accept unsafe shapes and types

    Runtime 3.6.6: Policies are validated immutable snapshots and rate state belongs to each proxy. Required paths must be strings. Lexical prefixes and top-level patterns do not provide filesystem containment.

    Historical observation and measured workaround follow. They describe the versions and dates named below.

    Policy constraints check less than their names suggest. A path_prefix is checked only when the value under the checked key (path, or the keys a rule lists in path_keys) is a string, so the same path sent inside an array or an object is not checked. denied_patterns match case-sensitively and only in top-level string arguments, so an uppercased command, or one inside an array or a nested object, is not matched. A constraint key the proxy does not recognize, such as a misspelling, is ignored without a warning, and a value of the wrong JSON type is not rejected: allowed: "false", a string, allows the tool; in denylist mode a tool listed as false, null or 0 instead of an object is allowed; and a non-empty path_keys string instead of an array makes the check read each character as a key name, so the intended key goes unchecked. Rate limits count per tool name across the whole proxy, shared by every client, and in allowlist mode the limit is checked before the path and pattern rules, so a call those rules deny still uses up a slot. Measured on 3.6.2 from npm on September 26 with allowlist and denylist policy files: a path_prefix of /home denied "/etc/passwd" and forwarded ["/etc/passwd"]; a denied pattern of rm -rf denied rm -rf / and forwarded RM -RF / and the same command inside an array; a rule spelled denied_pattern denied nothing; allowed: "false" forwarded the call in both modes; a denylist entry of false forwarded the call; path_keys: "path" forwarded /etc/passwd past a /home prefix; and, under a limit of 2 a minute, two calls denied by a /home prefix left a third call, to a path under /home, denied for the rate limit, while after one such denial that call was forwarded. Workaround: treat path and pattern rules as a convenience rather than a boundary, restrict paths in the upstream server itself, and check a policy file's keys against the constraint names in the package's dist/proxy/types.d.ts. Checks that fail closed on these inputs, and a check of the policy file at startup, are planned for the reviewed release.
  11. 11. Large stdio results can be dropped

    Runtime 3.6.6: Stdio output and pending work are bounded. Malformed or oversized output rejects pending work. An upstream effect can occur before the failure; PERMITTED is not proof of execution.

    Historical observation and measured workaround follow. They describe the versions and dates named below.

    A stdio upstream's response whose JSON line is 8,388,608 characters or more (UTF-16 code units, counting JSON escaping and a carriage return before the newline, but not the newline itself) is dropped. The call already has a PERMITTED receipt, and the agent gets a timeout error after 30 seconds, so an agent that retries can run the tool twice; the proxy reports the drop only on its own stderr. The limit counts upstream output not yet split into lines, so a response just under it can be dropped when the read that completes it also carries enough output after it to pass the limit; every response after it that begins in that read, possibly to other clients' calls, is dropped with it. Whether that happens depends on where the reads fall. Measured on 3.6.2 from npm on September 26: a 9,000,000-character result was dropped and the agent got the timeout after 30.0 seconds, while a 1,000,000-character result came back in 31 milliseconds. Through a running proxy, a response line of 8,388,607 characters was returned and one of 8,388,608 was dropped; and when the upstream answered three calls in one write (100 characters and 58 under the limit for one client, then 100 for the other), the first was answered and the other two, one of them the other client's, timed out, while the same answers without the leading 100-character answer were both returned. These are the cases measured. An HTTP upstream's result is read whole and is not dropped this way; on Linux, one at the same bound closes the agent's connection instead (item 13). Workaround: keep tool results well under the bound, for example by reading large files in parts, and where results are large run one proxy per client. An error returned at once is planned for the reviewed release.
  12. 12. Unauthenticated control channel and browser access

    Runtime 3.6.6: Control requests require the bound loopback Host and no Origin; cross-site browser metadata is refused. A local process can still read the ledger. Local-process authorization is not supplied.

    Historical observation and measured workaround follow. They describe the versions and dates named below.

    The control channel listens on 127.0.0.1 (port 18801 by default) so that a separate aga-proxy export can fetch the live bundle (routes /export, /status and /receipts). It does not check a request's Host or Origin header. Measured on 3.6.2 from npm on September 26: GET /receipts and GET /export sent with the Host and Origin of another site returned 200, and both carried a denied call's argument path in its denial reason. A web page open in a browser on the same host can therefore read the live receipts and the evidence bundle through DNS rebinding, unless the browser blocks a public site's requests to the loopback address; any local user on the host can read them as well. A page can also start an export with a plain GET /export without rebinding, unless the browser blocks it, and each export holds up governed calls while it runs (item 9). The CLI has no option meant to turn the channel off; a --control-port of 70000, or any number above 65535, leaves it unstarted while governance runs, but then no command can export the running proxy's receipts. Workaround: do not browse the web on the host while the proxy runs, or run the proxy on a host where no one does, and on a host shared with other users treat the live receipts as readable by all of them. A Host and Origin check is planned for the reviewed release.
  13. 13. Large HTTP results can close Linux client connections

    Runtime 3.6.6: HTTP work has a 30-second deadline, an 8 MiB response bound, fatal UTF-8 decoding and response-ID validation. Failure can follow an upstream effect. Do not infer non-execution or retry automatically.

    Historical observation and measured workaround follow. They describe the versions and dates named below.

    On Linux, an HTTP upstream's (--upstream-url) response whose JSON line, as the proxy serializes it, is 8,388,608 characters or more (UTF-16 code units, counting JSON escaping but not the newline) closes the agent's connection. The proxy writes the whole line to the agent's socket in one call. A Linux kernel with its default socket buffers takes only part of a write that size; the rest goes out as the client reads, but the whole line counts as waiting until it has all gone, so the proxy's guard against a client that does not read its responses sees more than 8,388,608 characters waiting, the newline included, and destroys the socket. The call already has a PERMITTED receipt and the upstream has run the tool; the agent's connection closes part-way through the reply with no error message, so an agent that reconnects and retries can run the tool twice; every other call in flight on that connection is lost with it; and the proxy reports nothing. Measured on 3.6.2 from npm on September 26 on a Linux 6.18 host with Node 24 and the default socket buffers (net.ipv4.tcp_wmem 4096 16384 4194304): result lines of 4,000,000, 8,000,000, 8,388,606 and 8,388,607 characters were returned, and lines of 8,388,608, 8,388,609, 9,000,000, 10,000,000, 12,000,000, 16,000,000, 20,000,000 and 32,000,000 characters closed the connection after 3.1 to 5.0 MB of the reply had arrived; a client that read nothing for 4 seconds got a 4,000,000-character result and lost a 9,000,000-character one after 2.7 MB. On Windows 11 the same proxy returned a 20,000,000-character result, and a 9,000,000-character one to a client that read nothing for 4 seconds, because that kernel took each write whole. A stdio upstream's response of this size is dropped instead (item 11). Workaround: keep tool results well under the bound, for example by reading large files in parts. An error returned at once is planned for the reviewed release.

Versioned package documentation. The README that npm shows for aga-mcp-server 3.6.2 lists known issues 1 to 3 only, and its packaged SECURITY.md promises acknowledgment within 48 hours. This page, the GitHub README and the GitHub SECURITY.md retain issues 1 to 13, their current 3.6.6 disposition and acknowledgment within a few business days. The npm README for aga-mcp-server 3.6.2 also describes the standard profile as "rate limits + blocks destructive operations", says a receipt is generated for every decision (its package description, also shown on npm, says it evaluates each call against the operator's policy and records each decision as a signed receipt), and says denial happens only under an allowlist-mode policy. Standard is an allowlist of ten generic example tool names that denies every other tool (see /evaluate), as the GitHub README now says. The npm README for aga-verify 2.2.2 says its chain check covers monotonic ids and that the envelope metadata matches the signed content. It checks timestamps and the hash chain, and binds only gateway_id, generated_at and merkle_root; bundle_id, schema_version, policy_reference and offline_capable are unchecked. Its PASS section omits known issue 5, and its quickstart pins the packaged sample to a key whose private seed (32 bytes of 0x07) is published in the source repository's conformance generator, so the provenance-verified result it prints shows the mechanics, not provenance. The PyPI page for aga-governance 0.3.2 omits known issue 5 and says it agrees with the other verifiers verdict-for-verdict. That holds on the conformance corpus as its harness feeds it, not on the literal bytes of the float-spelled leaf_index case (see /spec). The THREAT_BOUNDARY.md inside the 3.6.2 package says an uncanonicalizable call is never silently dropped, that every governed tools/call gets a receipt, and that the verifiers return byte-identical verdicts, and its DEPLOYMENT.md starts the filesystem server under --profile standard, which denies every tool of that server; the GitHub THREAT_BOUNDARY.md carries dated scope notes beside those sentences, and the GitHub DEPLOYMENT.md a corrected example. Published package pages change only with a new release.

Verifier differences outside the conformance corpus. Historical measurements from September 25, 2026, with the last three from September 26. The remediation note after this list identifies later changes; the current published-input check is above. In these measurements, each disagreement is fail-closed or integrity-only: one side of each disagreement reports FAILED, refuses the input or reports integrity only.

  • Given a 64-hex --pubkey that is a small-order point or a non-canonical encoding, the in-server engine of aga-mcp-server (its ./verify export, which verify_bundle_offline calls) and the reference verify-v2.go treat it as no pin and report VERIFIED, the engine with pinned false and verify-v2.go with integrity only (exit 0); both take a 64-hex value that is not a curve point as a pin, and the bundle fails. aga-verify, verify-sep.mjs, verify.py, verify.go, aga-governance and the verify.mjs in sample-bundle.zip report FAILED.
  • A --pubkey that is not 64 lowercase hex is a usage error (exit 2) in aga-verify, verify-sep.mjs, aga-governance and the verify.mjs in sample-bundle.zip. The reference verify.py, verify.go and verify-v2.go, and verify-sep.mjs in aga-conformance-vectors.zip (see /spec), ignore it and report integrity only.
  • --pubkey given with no value is treated as no pin (exit 0, integrity only) by aga-verify, verify-sep.mjs, verify.py, verify.go and verify-v2.go, and is a usage error in aga-governance and in the verify.mjs in sample-bundle.zip.
  • A proof leaf_index spelled as an integral float (1.0) reports FAILED in aga-governance 0.3.2 and VERIFIED in the others.
  • Object keys outside the Basic Multilingual Plane, possible only in a non-string field value that no shipped producer emits, sort differently in verify.py, verify.go, verify-v2.go and aga-governance than in the JavaScript verifiers, the verify.mjs in sample-bundle.zip among them. Such a bundle splits: VERIFIED in JavaScript, FAILED in Go and Python.
  • A receipt's own algorithm label is not checked by aga-verify, aga-proxy verify in aga-mcp-server, the reference verifiers (verify-sep.mjs, verify.py, verify.go and verify-v2.go), the /verify, /boundary and /attested-bench pages or the verify.mjs in sample-bundle.zip, which check the bundle's and the checkpoint's labels. aga-governance 0.3.2 checks the receipts' labels but not the checkpoint's, and accepts a checkpoint leaf_count of true for one receipt. Each case needs a bundle re-signed by the key's holder, so with the key pinned none lets anyone else produce a VERIFIED result. Outside the corpus aga-verify and aga-governance therefore differ in both directions.
  • The September 25 checks found that aga-verify 2.2.2 and the reference verifiers (verify-sep.mjs, verify.py, verify.go and verify-v2.go) ignored a pin written as --pubkey=KEY, or under a misspelled flag, when it follows the bundle path, and report integrity only with exit 0 when the bundle verifies. Before the bundle path, aga-verify, verify-sep.mjs, verify.go and verify-v2.go also ignore --pubkey=KEY and a misspelled flag written with an equals sign, while verify.py reads either as the file name and fails. The verify.mjs in sample-bundle.zip takes the last argument other than --pubkey and its value as the bundle path, so it does the reverse: it ignores --pubkey=KEY or a misspelled flag, with or without an equals sign, before the bundle path, reporting integrity only with exit 0, and fails when either follows it. aga-governance reads --pubkey=KEY as a pin. Measured on aga-verify 2.2.2: --pubkey KEY with a key that did not sign the bundle reported FAILED, and --pubkey=KEY with the same key, before or after the bundle path, reported VERIFIED, integrity only. Pass the key after a space, and in CI also check that the output says provenance verified.
  • aga-proxy verify --pin in aga-mcp-server treats a pin that is not a valid lowercase key, such as the right key in uppercase or 64 zeros, as no pin: integrity only, exit 0. Measured on 3.6.2.

Remediation on September 30: aga-verify 2.2.3 rejects unknown flags, including --pubkey=KEY and misspelled options, repeated options and a missing --pubkey value with exit 2. Its library rejects a malformed supplied expected key rather than disabling the check. Use the space-separated --pubkey KEY form. Runtime 3.6.5 separately adopted fail-closed expected-key handling in its embedded verifier; the frozen reference and static sample ZIP retain their own documented behavior.

Current policy boundary. The default permissive profile denies nothing on policy grounds. Policy denial requires --profile standard or restrictive, or an allowlist or denylist --policy configuration. Runtime 3.6.6 validates policies and holds an immutable snapshot; malformed policy files are not a supported way to configure permissive behavior. Only covered tools/call requests receive policy evaluation. Path prefixes and top-level argument patterns are lexical checks, not filesystem containment. Syntax and resource refusals can occur without a receipt, and a direct path to an upstream can bypass the proxy. The dated observations above retain the earlier policy and validation behavior.

Published remediation and remaining limits are identified above for aga-mcp-server 3.6.6 and aga-verify 2.2.3. Historical measurements remain available with their original version scope. To report a new issue, use the process below.

1. Scope

This policy covers security issues in:

  • attestedintelligence.com and its subpages
  • verifybundle.com, its offline verifier.html, and its /api/time endpoint
  • The demo verification gateway at aga-mcp-gateway.attested-intelligence.workers.dev
  • The published @attested-intelligence/aga-mcp-server and @attested-intelligence/aga-verify npm packages
  • The published aga-governance PyPI package

Out of scope: the third-party services we integrate (Formspree, Vercel, Cloudflare, ImprovMX, Resend, Microsoft) have their own security teams and disclosure channels; report issues in their infrastructure to them directly, not to us.

2. How to Report

Email admin@attestedintelligence.com with:

  • A description of the issue and the component or URL affected
  • Steps to reproduce it
  • What an attacker could do with it, to the best of your assessment
  • Whether you want credit and, if so, the name to use

3. What to Expect

Security reporting is founder-operated, not a staffed security operations center. We aim to acknowledge new reports within a few business days. We assess impact and available mitigations before proposing a fix timeline; no fixed resolution time or service-level agreement is promised. If a report is out of scope or not a vulnerability, we will explain why.

4. Safe Harbor

If your research stays within this policy’s scope, is conducted in good faith, avoids privacy violations and service disruption, and you report the issue to us promptly and do not disclose it publicly before we have had a reasonable chance to address it (see Section 5), we consider that research authorized. We will not pursue or support legal action against you for it, and we will work with you to understand and resolve the issue.

5. Coordinated Disclosure

Please give us 90 days from your report before any public disclosure, so we have time to ship a fix. We will work with you on timing if a fix needs longer, and we are glad to credit researchers who want credit once an issue is resolved.

6. Please Don’t

  • Run automated scanning that degrades service for other visitors
  • Access, modify, or delete data that is not yours, beyond what is needed to demonstrate the issue
  • Attempt social engineering against us, our contractors, or our service providers
  • Test for physical security or attempt to access accounts that are not yours (this site has no user accounts; see our Privacy Policy)

7. Contact

admin@attestedintelligence.com. For what we do and do not claim about the cryptography itself, see Trust and Scope.