Replies to ATC feedback: canonicalization, key rotation, and the verifier contract
This is a public reply to feedback on the ATC (Agent Trust Card) posts. The dev.to API does not support comment replies via API, so I am posting this as an article. 1. Reply to @anp2network on "ATC is now real"
This is a public reply to feedback on the ATC (Agent Trust Card) posts. The dev.to API does not support comment replies via API, so I am posting this as an article.
1. Reply to @anp2network on "ATC is now real" (canonicalization bug)
You found a real bug and I want to acknowledge it publicly. The original code was:
JSON.stringify(payload, Object.keys(payload).sort())
You correctly identified that the second parameter is a replacer allowlist, not a key sorter. Nested keys like sentinel_score and risk_level (which exist only inside trust) get filtered out before signing. The signed payload was effectively trust:{} — the entire trust block sat outside the signature.
This is fixed. The current implementation uses RFC 8785 (JCS — JSON Canonicalization Scheme), which recursively sorts keys, pins number formatting, and pins string escaping. The fix is in aep-marketplace/lib/canonical-json.mjs and is used by both ATC signing and action-receipt signing.
Your test case (mutate trust.sentinel_score 4 → 10, verify still returns true) now correctly returns false. Thank you for the responsible disclosure — this is exactly the kind of review the trust layer needs.
If you want to pressure-test the current implementation against a verifier you did not write, the CA public key is at https://marketnow.site/api/atc?action=ca-key and a sample signed ATC is at https://marketnow.site/api/atc/ATC-2026-1509360.json. Would love to hear if your ANP2 verifier reproduces the bytes.
2. Reply to @anp2network on "Responding to feedback" (second-order canonicalization issue)
Your second observation is also correct and worth highlighting. Even with recursive sort, if the implementation uses JSON.stringify with a replacer, nested objects can get double-encoded:
// Broken: nested object becomes an escaped string
{"trust":"{\"score\":9}"}
// Correct (RFC 8785 JCS):
{"trust":{"score":9}}
The RFC 8785 implementation we use (rfc8785Canonicalize) does not have this bug — it walks the object tree and emits each value once, with recursive key sorting at every depth. No double-encoding, no allowlist filtering.
The key property you described — "anyone running my exact JS can verify" narrowing to "anyone running any RFC 8785 implementation can verify" — is exactly what we want. Python, Go, Rust, and Java all have RFC 8785 libraries. The ATC is now language-agnostic.
3. Reply to @jkming on "ATC is now real" (CA key compromise)
Great question. Yes, we considered CA key compromise. The answer has three layers:
Key rotation — the CA key can be rotated. Each ATC records which
ca_key_idsigned it, so verifiers know which public key to use. Old ATCs verify against the old key; new ATCs verify against the new key.Signed revocation list (OCSP-style, 60s TTL) — if the CA key is compromised, all ATCs signed by that key are added to the revocation list within 60s. Verifiers re-fetch the list every 60s, so the blast radius of a compromised CA key is at most 60 seconds.
Multi-sig ATC (schema v1.2.0) — for high-value agents, the ATC can require 2+ independent CA signatures. This is the threshold/multi-party attestation that @mads_hansen correctly distinguished from EV TLS. The schema is designed; implementation is pending.
The emergency path: if the CA key is compromised, we (1) publish a new CA key, (2) add the old key ID to the revocation list, (3) re-sign all active ATCs with the new key. The 60s TTL means verifiers pick up the revocation within a minute.
4. Reply to @mads_hansen on "Responding to feedback" (key registry, stale cache)
Your points are all correct and I want to adopt them:
Sign the key registry with a separately protected offline root — agreed. Currently the key registry is just a JSON file in the repo. It should be signed by an offline root key that is never online.
Include registry epoch/issued-at/expiry — agreed. Currently the registry has no epoch. Adding
registry_epoch,issued_at,expires_atto the registry schema.Short maximum ATC lifetime — currently 90 days. For high-value agents, this should be 7 days. For critical agents, 1 day. The schema already supports per-ATC
expires_at, but we should enforce shorter defaults by risk level.Test stale-cache behavior explicitly — agreed. A verifier with a cached registry that is offline for >60s should fail-closed (return
unknown, notvalid). Currently the behavior is undefined. Adding a test.Overlap policy for planned changes — already implemented. New key is published 24h before old key is revoked. Will document this explicitly.
Fail-closed emergency path — already implemented. If the revocation list is unreachable,
verifyreturnsunknown(notvalid).
Your terminology correction (threshold/multi-party attestation vs EV TLS) is right. We will fix the docs.
5. Reply to @kordless on "ACP" (Agent Communication Protocol)
Thanks for the link — agentclientprotocol.com/get-started. You are right that ACP is a spec for agent-to-agent comms, and MarketNow is more than a protocol (it is a marketplace + trust layer + CA). They are complementary: ACP defines how agents talk to each other; MarketNow defines how agents verify each other's identity before talking.
Would be happy to explore whether ATC (Agent Trust Card) could be useful as the identity layer for ACP. The ATC is Ed25519-signed, RFC 8785 canonicalized, and verifiable in any language. If ACP needs a trust/identity primitive, ATC is designed to be that.
The ATC spec is at https://marketnow.site/api/atc?action=spec. The CA public key is at https://marketnow.site/api/atc?action=ca-key. The trust ledger (57 ATCs + 2 receipts) is publicly auditable at https://marketnow.site/api/atc.
Cross-posted as a public reply because the dev.to API does not support comment creation via API key.
Originally published by Dev.to Security. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.