diff --git a/CHANGELOG.md b/CHANGELOG.md index f3af4a2..561b75c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,23 @@ reference implements it. See [`spec.md` §2.1](spec.md) for the version matrix. ## [Unreleased] +### Changed (CTK — reference 0.3.0 implements the §4.2 query-fold) +- `ctk/vectors/hashing.json` regenerated by reference **0.3.0** on the **0.3 + preimage**: the fingerprint preimage now carries the canonical `query` + (`[]` for a bare URL), so every `action_fingerprint` changed. New query + cases: substitution (`?to=me` vs `?to=attacker` no longer share a + fingerprint), encoding equivalence (`%20` vs `+`, pair order), duplicate and + valueless pairs, fragment exclusion. The prior fixtures are preserved as + `ctk/vectors/hashing-v0.2.json` for ≤ 0.2-preimage implementations (the + `versions/` documents now point there). +- `ctk/vectors/resolve.json`: embedded fingerprints/intent hashes recomputed on + the 0.3 preimage (cases and expectations unchanged). +- `ctk/vectors/broker_query.json` marked **superseded** at reference ≥ 0.3.0: + with the query fingerprint-bound, a broker forwards it (refusing only a + `#fragment`); the reconstruction cases apply only to ≤ 0.2-preimage brokers. +- `spec.md` is untouched (frozen at 0.3); the §4.1 worked example remains the + 0.2-preimage illustration and matches `hashing-v0.2.json`. + ### Added (editorial — no protocol change) - `versions/` — standalone documents of record for prior protocol versions: [`versions/spec-v0.1.md`](versions/spec-v0.1.md) and diff --git a/ctk/README.md b/ctk/README.md index a4d66a6..1844716 100644 --- a/ctk/README.md +++ b/ctk/README.md @@ -9,7 +9,8 @@ conformant implementation MUST reproduce it. | File | What a conformant implementation must do | |------|------------------------------------------| -| [`vectors/hashing.json`](vectors/hashing.json) | For each action, compute `intent_hash` and `action_fingerprint` (spec §4) and match. Each entry includes the exact canonical-JSON that was hashed, so you can debug a mismatch byte-for-byte. | +| [`vectors/hashing.json`](vectors/hashing.json) *(0.3 preimage)* | For each action, compute `intent_hash` and `action_fingerprint` (spec §4, with the §4.2 query-fold: the preimage carries the canonical `query`, `[]` for a bare URL) and match. Includes query cases — substitution (`?to=me` vs `?to=attacker` differ), encoding equivalence (`%20` vs `+`, pair order), duplicate/valueless pairs, fragment exclusion. Each entry includes the exact canonical-JSON that was hashed, so you can debug a mismatch byte-for-byte. | +| [`vectors/hashing-v0.2.json`](vectors/hashing-v0.2.json) *(0.1–0.2 preimage snapshot)* | The same check for an implementation on the ≤ 0.2 preimage (no `query` key). Frozen — see [`../versions/`](../versions/README.md). | | [`vectors/decisions.json`](vectors/decisions.json) | Load the example policy, evaluate each action (spec §5–§6), and match `outcome` / `rule` / `reasons`. | | [`vectors/chain.jsonl`](vectors/chain.jsonl) + [`vectors/chain.expected.json`](vectors/chain.expected.json) | Verify the chain (spec §8.1) using [`vectors/signing_key.pub`](vectors/signing_key.pub); it MUST be valid with the listed `seqs`. | | [`vectors/chain.tampered.jsonl`](vectors/chain.tampered.jsonl) + [`vectors/chain.tampered.expected.json`](vectors/chain.tampered.expected.json) | The same chain with `seq 0` edited and not re-signed; verification MUST fail with a content-hash mismatch at `seq 0`. | @@ -20,19 +21,16 @@ The policy used for the decision and chain vectors is key for verifying the chain signatures is `vectors/signing_key.pub` (the private key is never published). -## Pending vectors (documented, not yet wired) +## Documented-only vectors (not wired) -These vectors describe **0.3 additive behaviour that the current reference -(protocol 0.2) does not yet expose**, so `conformance.py` does **not** replay them -— wiring them now would fail against the unfixed reference. They are committed as -documentation and **activate (get wired into `conformance.py`) when the reference -is ≥ 0.2.3**. Each file carries its own `_status`, `_activate_at_reference`, -`_spec`, and `_how_to_apply` keys. +These files are committed as documentation and are **not** replayed by +`conformance.py`. Each carries its own `_status`, `_spec`, and `_how_to_apply` +keys. -| File | What it will check | Activate at | -|------|--------------------|-------------| -| [`vectors/policy_invalid.json`](vectors/policy_invalid.json) | Policy-schema validation and **fail-closed** loading (spec §5.1): each policy is invalid (unknown `match`/`constraints` key, unknown `decision`, missing `default`); a conformant Authorizer MUST reject it and MUST NOT silently skip an unknown constraint. | reference ≥ 0.2.3 | -| [`vectors/broker_query.json`](vectors/broker_query.json) | The Broker (PEP) **query obligation** (spec §4.2): the Broker MUST reconstruct the outgoing URL from the authorized `host`/`path`/`params`, MUST NOT forward the agent-supplied query verbatim, and MUST refuse an action whose URL it cannot reconstruct. (This is the additive obligation; folding the query into the fingerprint is a deferred breaking change and is **not** tested here.) | reference ≥ 0.2.3 | +| File | Status | +|------|--------| +| [`vectors/policy_invalid.json`](vectors/policy_invalid.json) | **Pending.** Policy-schema validation and **fail-closed** loading (spec §5.1): each policy is invalid (unknown `match`/`constraints` key, unknown `decision`, missing `default`); a conformant Authorizer MUST reject it and MUST NOT silently skip an unknown constraint. The reference enforces this since 0.2.3. | +| [`vectors/broker_query.json`](vectors/broker_query.json) | **Superseded at reference ≥ 0.3.0.** The Broker (PEP) query-reconstruction obligation as it applies to a **0.2-preimage** implementation. With the 0.3 query-fold, the whole query of an authorised action is fingerprint-bound and a broker forwards it (refusing only a `#fragment`), so these cases apply only to implementations that remain on the ≤ 0.2 preimage. | ## Schema validation @@ -43,6 +41,7 @@ runs it on every change. ## Regenerating The vectors are fixtures produced by the reference implementation. A port in any -language passes the CTK when it reproduces every value above. The pending vectors -above become fixtures (and get wired into `conformance.py`) once the reference -implements the corresponding §4.2 / §5.1 behaviour at ≥ 0.2.3. +language passes the CTK when it reproduces every value above. `hashing.json` and +the hashes embedded in `resolve.json` were regenerated by reference **0.3.0** +when the §4.2 query-fold shipped; `hashing-v0.2.json` preserves the prior +preimage's fixtures for ≤ 0.2 implementations. diff --git a/ctk/vectors/broker_query.json b/ctk/vectors/broker_query.json index 8b0ea1e..f06dd46 100644 --- a/ctk/vectors/broker_query.json +++ b/ctk/vectors/broker_query.json @@ -1,58 +1,62 @@ { - "_about": "CTK vectors for spec §4.2 — the Broker (PEP) query obligation. These exercise NOT-YET-RELEASED behaviour: query reconstruction at the Broker is a 0.3 additive obligation that the current reference (protocol 0.2) does not yet expose a Broker for, so these vectors are PENDING and are NOT wired into conformance.py. Activate (wire into conformance.py) when a conformant Broker is available at reference >= 0.2.3.", - "_status": "pending", - "_activate_at_reference": ">=0.2.3", - "_spec": "§4.2 (Broker query obligation, additive) and §10 (0.3 additive hardening)", - "_how_to_apply": "For each case, the Broker has authorized the action described by `authorized.host`/`authorized.path`/`authorized.params`. The Agent presents `agent_supplied_url`. A conformant Broker MUST reconstruct the outgoing URL from the authorized host/path/params and MUST NOT forward the agent-supplied query verbatim. `expected.outgoing_url` is the URL the Broker MUST send (null when it MUST refuse). `expected.action` is `reconstruct` or `refuse`.", - "_note_deferred": "Folding the query into the action_fingerprint preimage is a DEFERRED breaking change (§4.2) and is NOT covered here; these vectors test only the additive Broker obligation, which changes no hashed bytes.", - "cases": [ - { - "name": "agent-supplied query is dropped; URL reconstructed from authorized fields", - "property": "no-verbatim-query-forward", - "authorized": { - "method": "GET", - "host": "api.example.com", - "path": "/orders", - "params": {} - }, - "agent_supplied_url": "https://api.example.com/orders?to=attacker&limit=100", - "expected": { - "action": "reconstruct", - "outgoing_url": "https://api.example.com/orders", - "reason_contains": "query not derivable from authorized action" - } - }, - { - "name": "decision-relevant value rides the query and is not in params: dropped", - "property": "no-decision-relevant-from-query", - "authorized": { - "method": "POST", - "host": "api.example.com", - "path": "/orders", - "params": { "amount": 2400, "currency": "USD", "destination": "internal" } - }, - "agent_supplied_url": "https://api.example.com/orders?destination=attacker", - "expected": { - "action": "reconstruct", - "outgoing_url": "https://api.example.com/orders", - "reason_contains": "query parameter not represented in params" - } - }, - { - "name": "Broker that cannot reconstruct the URL refuses the action", - "property": "refuse-when-irreconstructible", - "authorized": { - "method": "GET", - "host": null, - "path": null, - "params": {} - }, - "agent_supplied_url": "https://api.example.com/orders?to=me", - "expected": { - "action": "refuse", - "outgoing_url": null, - "reason_contains": "cannot reconstruct URL" - } + "_about": "CTK vectors for spec §4.2 — the Broker (PEP) query obligation as it applies to a 0.2-preimage implementation. SUPERSEDED at reference >= 0.3.0: the query is folded into the action_fingerprint preimage, so the entire query of an authorised action is derivable from it and a 0.3 broker forwards it (refusing only a #fragment) — the reconstruction behaviour these cases describe no longer applies. Kept for implementations that remain on the 0.2 preimage, where the Broker obligation is the interim defense. Never wired into conformance.py.", + "_status": "superseded", + "_spec": "§4.2 (Broker query obligation, additive) and §10 (0.3 additive hardening)", + "_how_to_apply": "For each case, the Broker has authorized the action described by `authorized.host`/`authorized.path`/`authorized.params`. The Agent presents `agent_supplied_url`. A conformant Broker MUST reconstruct the outgoing URL from the authorized host/path/params and MUST NOT forward the agent-supplied query verbatim. `expected.outgoing_url` is the URL the Broker MUST send (null when it MUST refuse). `expected.action` is `reconstruct` or `refuse`.", + "_note_deferred": "The query-fold shipped in reference 0.3.0: see ctk/vectors/hashing.json (0.3 preimage, including query cases) and hashing-v0.2.json (the 0.2-preimage snapshot).", + "cases": [ + { + "name": "agent-supplied query is dropped; URL reconstructed from authorized fields", + "property": "no-verbatim-query-forward", + "authorized": { + "method": "GET", + "host": "api.example.com", + "path": "/orders", + "params": {} + }, + "agent_supplied_url": "https://api.example.com/orders?to=attacker&limit=100", + "expected": { + "action": "reconstruct", + "outgoing_url": "https://api.example.com/orders", + "reason_contains": "query not derivable from authorized action" + } + }, + { + "name": "decision-relevant value rides the query and is not in params: dropped", + "property": "no-decision-relevant-from-query", + "authorized": { + "method": "POST", + "host": "api.example.com", + "path": "/orders", + "params": { + "amount": 2400, + "currency": "USD", + "destination": "internal" } - ] + }, + "agent_supplied_url": "https://api.example.com/orders?destination=attacker", + "expected": { + "action": "reconstruct", + "outgoing_url": "https://api.example.com/orders", + "reason_contains": "query parameter not represented in params" + } + }, + { + "name": "Broker that cannot reconstruct the URL refuses the action", + "property": "refuse-when-irreconstructible", + "authorized": { + "method": "GET", + "host": null, + "path": null, + "params": {} + }, + "agent_supplied_url": "https://api.example.com/orders?to=me", + "expected": { + "action": "refuse", + "outgoing_url": null, + "reason_contains": "cannot reconstruct URL" + } + } + ], + "_superseded_at_reference": ">=0.3.0" } diff --git a/ctk/vectors/hashing-v0.2.json b/ctk/vectors/hashing-v0.2.json new file mode 100644 index 0000000..f886955 --- /dev/null +++ b/ctk/vectors/hashing-v0.2.json @@ -0,0 +1,47 @@ +[ + { + "action": { + "instruction": "read my account details", + "method": "GET", + "url": "https://api.example.com/accounts/me", + "params": {} + }, + "intent_canonical_json": "{\"instruction\":\"read my account details\"}", + "intent_hash": "ec949034e985a92f3bcd9f9ab8313a80005157698f748f2b8df6163c04af4619", + "fingerprint_canonical_json": "{\"host\":\"api.example.com\",\"method\":\"GET\",\"params\":{},\"path\":\"/accounts/me\"}", + "action_fingerprint": "497e02606ea157ca8ca885cbbd33d1a8a70c40fdaf6f15c5154d858f870b8b61" + }, + { + "action": { + "instruction": "place a small order", + "method": "POST", + "url": "https://api.example.com/orders", + "params": { + "amount": 2400, + "currency": "USD", + "destination": "internal" + } + }, + "intent_canonical_json": "{\"instruction\":\"place a small order\"}", + "intent_hash": "76f8eef1b97e1213a59eec28cedf15bb999fdb00a3fd17f8343bc4676fdbb4f3", + "fingerprint_canonical_json": "{\"host\":\"api.example.com\",\"method\":\"POST\",\"params\":{\"amount\":2400,\"currency\":\"USD\",\"destination\":\"internal\"},\"path\":\"/orders\"}", + "action_fingerprint": "c70d4ee57957202087887cb5e9d32222977b728bd06947b7761c283b6d4ed394" + }, + { + "action": { + "instruction": "place a small order", + "method": "POST", + "url": "https://api.example.com/orders", + "params": { + "amount": 2400, + "currency": "USD", + "destination": "internal", + "recipient": "attacker" + } + }, + "intent_canonical_json": "{\"instruction\":\"place a small order\"}", + "intent_hash": "76f8eef1b97e1213a59eec28cedf15bb999fdb00a3fd17f8343bc4676fdbb4f3", + "fingerprint_canonical_json": "{\"host\":\"api.example.com\",\"method\":\"POST\",\"params\":{\"amount\":2400,\"currency\":\"USD\",\"destination\":\"internal\",\"recipient\":\"attacker\"},\"path\":\"/orders\"}", + "action_fingerprint": "dabddc8fc7e8fb30bdec6fb796a336b7897d4a2a12ae386727e2110d7e0e9572" + } +] diff --git a/ctk/vectors/hashing.json b/ctk/vectors/hashing.json index f886955..9f20133 100644 --- a/ctk/vectors/hashing.json +++ b/ctk/vectors/hashing.json @@ -1,47 +1,119 @@ [ - { - "action": { - "instruction": "read my account details", - "method": "GET", - "url": "https://api.example.com/accounts/me", - "params": {} - }, - "intent_canonical_json": "{\"instruction\":\"read my account details\"}", - "intent_hash": "ec949034e985a92f3bcd9f9ab8313a80005157698f748f2b8df6163c04af4619", - "fingerprint_canonical_json": "{\"host\":\"api.example.com\",\"method\":\"GET\",\"params\":{},\"path\":\"/accounts/me\"}", - "action_fingerprint": "497e02606ea157ca8ca885cbbd33d1a8a70c40fdaf6f15c5154d858f870b8b61" - }, - { - "action": { - "instruction": "place a small order", - "method": "POST", - "url": "https://api.example.com/orders", - "params": { - "amount": 2400, - "currency": "USD", - "destination": "internal" - } - }, - "intent_canonical_json": "{\"instruction\":\"place a small order\"}", - "intent_hash": "76f8eef1b97e1213a59eec28cedf15bb999fdb00a3fd17f8343bc4676fdbb4f3", - "fingerprint_canonical_json": "{\"host\":\"api.example.com\",\"method\":\"POST\",\"params\":{\"amount\":2400,\"currency\":\"USD\",\"destination\":\"internal\"},\"path\":\"/orders\"}", - "action_fingerprint": "c70d4ee57957202087887cb5e9d32222977b728bd06947b7761c283b6d4ed394" - }, - { - "action": { - "instruction": "place a small order", - "method": "POST", - "url": "https://api.example.com/orders", - "params": { - "amount": 2400, - "currency": "USD", - "destination": "internal", - "recipient": "attacker" - } - }, - "intent_canonical_json": "{\"instruction\":\"place a small order\"}", - "intent_hash": "76f8eef1b97e1213a59eec28cedf15bb999fdb00a3fd17f8343bc4676fdbb4f3", - "fingerprint_canonical_json": "{\"host\":\"api.example.com\",\"method\":\"POST\",\"params\":{\"amount\":2400,\"currency\":\"USD\",\"destination\":\"internal\",\"recipient\":\"attacker\"},\"path\":\"/orders\"}", - "action_fingerprint": "dabddc8fc7e8fb30bdec6fb796a336b7897d4a2a12ae386727e2110d7e0e9572" - } + { + "action": { + "instruction": "read my account details", + "method": "GET", + "url": "https://api.example.com/accounts/me", + "params": {} + }, + "intent_canonical_json": "{\"instruction\":\"read my account details\"}", + "intent_hash": "ec949034e985a92f3bcd9f9ab8313a80005157698f748f2b8df6163c04af4619", + "fingerprint_canonical_json": "{\"host\":\"api.example.com\",\"method\":\"GET\",\"params\":{},\"path\":\"/accounts/me\",\"query\":[]}", + "action_fingerprint": "051bae652ac13aa7eda6389a289e084c3414f9cd2b229544a679b47b21003d06" + }, + { + "action": { + "instruction": "place a small order", + "method": "POST", + "url": "https://api.example.com/orders", + "params": { + "amount": 2400, + "currency": "USD", + "destination": "internal" + } + }, + "intent_canonical_json": "{\"instruction\":\"place a small order\"}", + "intent_hash": "76f8eef1b97e1213a59eec28cedf15bb999fdb00a3fd17f8343bc4676fdbb4f3", + "fingerprint_canonical_json": "{\"host\":\"api.example.com\",\"method\":\"POST\",\"params\":{\"amount\":2400,\"currency\":\"USD\",\"destination\":\"internal\"},\"path\":\"/orders\",\"query\":[]}", + "action_fingerprint": "4327df2637072bf058622d1f8baea6e431726f7332a50cd12ec970d6e43c2fd2" + }, + { + "action": { + "instruction": "place a small order", + "method": "POST", + "url": "https://api.example.com/orders", + "params": { + "amount": 2400, + "currency": "USD", + "destination": "internal", + "recipient": "attacker" + } + }, + "intent_canonical_json": "{\"instruction\":\"place a small order\"}", + "intent_hash": "76f8eef1b97e1213a59eec28cedf15bb999fdb00a3fd17f8343bc4676fdbb4f3", + "fingerprint_canonical_json": "{\"host\":\"api.example.com\",\"method\":\"POST\",\"params\":{\"amount\":2400,\"currency\":\"USD\",\"destination\":\"internal\",\"recipient\":\"attacker\"},\"path\":\"/orders\",\"query\":[]}", + "action_fingerprint": "43aa4af8115a932a72f30da058b7396427a6f69f98cffbe4df8ce78017b89123" + }, + { + "action": { + "instruction": "send the report to me", + "method": "GET", + "url": "https://api.example.com/orders?to=me", + "params": {} + }, + "intent_canonical_json": "{\"instruction\":\"send the report to me\"}", + "intent_hash": "100c07cd4a1894eddb1e8555d2ff02ab29400c35d5423aeb774a3fcf59c8dbca", + "fingerprint_canonical_json": "{\"host\":\"api.example.com\",\"method\":\"GET\",\"params\":{},\"path\":\"/orders\",\"query\":[[\"to\",\"me\"]]}", + "action_fingerprint": "ec173d46a061224eb08f43e87082d5b2efde197b6d242400825cc224367b2b7d" + }, + { + "action": { + "instruction": "send the report to me", + "method": "GET", + "url": "https://api.example.com/orders?to=attacker", + "params": {} + }, + "intent_canonical_json": "{\"instruction\":\"send the report to me\"}", + "intent_hash": "100c07cd4a1894eddb1e8555d2ff02ab29400c35d5423aeb774a3fcf59c8dbca", + "fingerprint_canonical_json": "{\"host\":\"api.example.com\",\"method\":\"GET\",\"params\":{},\"path\":\"/orders\",\"query\":[[\"to\",\"attacker\"]]}", + "action_fingerprint": "d9ca2a6ac18ceb39d3000eadbe8f2675637844248e8156053d75704b50a06dd7" + }, + { + "action": { + "instruction": "query encodings canonicalize", + "method": "GET", + "url": "https://api.example.com/search?b=two%20words&a=1", + "params": {} + }, + "intent_canonical_json": "{\"instruction\":\"query encodings canonicalize\"}", + "intent_hash": "c9fef02508a098826101ba4c03887fe287a4776d50ce05a994931fc5797ff220", + "fingerprint_canonical_json": "{\"host\":\"api.example.com\",\"method\":\"GET\",\"params\":{},\"path\":\"/search\",\"query\":[[\"a\",\"1\"],[\"b\",\"two words\"]]}", + "action_fingerprint": "5fbb7320cd61de0a9dc99aa83d30bdf7332aa2819dd381a84c8c0287bd60408f" + }, + { + "action": { + "instruction": "query encodings canonicalize", + "method": "GET", + "url": "https://api.example.com/search?a=1&b=two+words", + "params": {} + }, + "intent_canonical_json": "{\"instruction\":\"query encodings canonicalize\"}", + "intent_hash": "c9fef02508a098826101ba4c03887fe287a4776d50ce05a994931fc5797ff220", + "fingerprint_canonical_json": "{\"host\":\"api.example.com\",\"method\":\"GET\",\"params\":{},\"path\":\"/search\",\"query\":[[\"a\",\"1\"],[\"b\",\"two words\"]]}", + "action_fingerprint": "5fbb7320cd61de0a9dc99aa83d30bdf7332aa2819dd381a84c8c0287bd60408f" + }, + { + "action": { + "instruction": "duplicates and valueless pairs are preserved", + "method": "GET", + "url": "https://api.example.com/x?b=2&a=%41&a=plus+space&a&b=2", + "params": {} + }, + "intent_canonical_json": "{\"instruction\":\"duplicates and valueless pairs are preserved\"}", + "intent_hash": "a8389be669dbdf47c06358e5c6fdf5defdd387e361169c3cdadfd8325f66dda7", + "fingerprint_canonical_json": "{\"host\":\"api.example.com\",\"method\":\"GET\",\"params\":{},\"path\":\"/x\",\"query\":[[\"a\",\"\"],[\"a\",\"A\"],[\"a\",\"plus space\"],[\"b\",\"2\"],[\"b\",\"2\"]]}", + "action_fingerprint": "9ec8f7e43e3c672ba6426089b2a7bfa37e363e7e7a6c4cf7ea581391568614d4" + }, + { + "action": { + "instruction": "the fragment is excluded", + "method": "GET", + "url": "https://api.example.com/x?a=1#b=2", + "params": {} + }, + "intent_canonical_json": "{\"instruction\":\"the fragment is excluded\"}", + "intent_hash": "8ab001b912f8875f409f9127e58f19bc3ea85521d1e98a6195b1fa92592fd20a", + "fingerprint_canonical_json": "{\"host\":\"api.example.com\",\"method\":\"GET\",\"params\":{},\"path\":\"/x\",\"query\":[[\"a\",\"1\"]]}", + "action_fingerprint": "ff48ebc3cfcc763175f753a3963f103a0515cd50592e7027ec3afb26c91a15a1" + } ] diff --git a/ctk/vectors/resolve.json b/ctk/vectors/resolve.json index 790133b..84a7821 100644 --- a/ctk/vectors/resolve.json +++ b/ctk/vectors/resolve.json @@ -1,226 +1,226 @@ [ - { - "name": "unknown approval id is denied", - "approval": null, - "presented_action": { - "instruction": "place a small order", - "method": "POST", - "url": "https://api.example.com/orders", - "params": { - "amount": 2400, - "currency": "USD", - "destination": "internal" - } - }, - "presented_fingerprint": "c70d4ee57957202087887cb5e9d32222977b728bd06947b7761c283b6d4ed394", - "presented_intent_hash": "76f8eef1b97e1213a59eec28cedf15bb999fdb00a3fd17f8343bc4676fdbb4f3", - "expected": { - "outcome": "deny", - "reason_contains": "unknown approval id" - } - }, - { - "name": "fingerprint mismatch is refused (before status)", - "approval": { - "action_fingerprint": "c70d4ee57957202087887cb5e9d32222977b728bd06947b7761c283b6d4ed394", - "intent_hash": "76f8eef1b97e1213a59eec28cedf15bb999fdb00a3fd17f8343bc4676fdbb4f3", - "status": "pending", - "rule": "place-order" - }, - "presented_action": { - "instruction": "place a small order", - "method": "POST", - "url": "https://api.example.com/orders", - "params": { - "amount": 2400, - "currency": "USD", - "destination": "internal", - "recipient": "attacker" - } - }, - "presented_fingerprint": "dabddc8fc7e8fb30bdec6fb796a336b7897d4a2a12ae386727e2110d7e0e9572", - "presented_intent_hash": "76f8eef1b97e1213a59eec28cedf15bb999fdb00a3fd17f8343bc4676fdbb4f3", - "expected": { - "outcome": "deny", - "reason_contains": "approval/action mismatch: this approval was issued for a different action (possible confused-deputy / substituted action)" - } - }, - { - "name": "intent mismatch is refused", - "approval": { - "action_fingerprint": "c70d4ee57957202087887cb5e9d32222977b728bd06947b7761c283b6d4ed394", - "intent_hash": "76f8eef1b97e1213a59eec28cedf15bb999fdb00a3fd17f8343bc4676fdbb4f3", - "status": "approved", - "rule": "place-order" - }, - "presented_action": { - "instruction": "place a different order", - "method": "POST", - "url": "https://api.example.com/orders", - "params": { - "amount": 2400, - "currency": "USD", - "destination": "internal" - } - }, - "presented_fingerprint": "c70d4ee57957202087887cb5e9d32222977b728bd06947b7761c283b6d4ed394", - "presented_intent_hash": "70b7c5af667d7dc104bd9baaf5c26ebdabeebe69272ee013a6306e208a09c4b3", - "expected": { - "outcome": "deny", - "reason_contains": "approval/intent mismatch: this approval was issued for a different instruction" - } - }, - { - "name": "matching action while pending awaits approval", - "approval": { - "action_fingerprint": "c70d4ee57957202087887cb5e9d32222977b728bd06947b7761c283b6d4ed394", - "intent_hash": "76f8eef1b97e1213a59eec28cedf15bb999fdb00a3fd17f8343bc4676fdbb4f3", - "status": "pending", - "rule": "place-order" - }, - "presented_action": { - "instruction": "place a small order", - "method": "POST", - "url": "https://api.example.com/orders", - "params": { - "amount": 2400, - "currency": "USD", - "destination": "internal" - } - }, - "presented_fingerprint": "c70d4ee57957202087887cb5e9d32222977b728bd06947b7761c283b6d4ed394", - "presented_intent_hash": "76f8eef1b97e1213a59eec28cedf15bb999fdb00a3fd17f8343bc4676fdbb4f3", - "expected": { - "outcome": "needs_approval", - "reason_contains": "awaiting human approval" - } - }, - { - "name": "human-denied action is refused", - "approval": { - "action_fingerprint": "c70d4ee57957202087887cb5e9d32222977b728bd06947b7761c283b6d4ed394", - "intent_hash": "76f8eef1b97e1213a59eec28cedf15bb999fdb00a3fd17f8343bc4676fdbb4f3", - "status": "denied", - "rule": "place-order" - }, - "presented_action": { - "instruction": "place a small order", - "method": "POST", - "url": "https://api.example.com/orders", - "params": { - "amount": 2400, - "currency": "USD", - "destination": "internal" - } - }, - "presented_fingerprint": "c70d4ee57957202087887cb5e9d32222977b728bd06947b7761c283b6d4ed394", - "presented_intent_hash": "76f8eef1b97e1213a59eec28cedf15bb999fdb00a3fd17f8343bc4676fdbb4f3", - "expected": { - "outcome": "deny", - "reason_contains": "human denied this action" - } - }, - { - "name": "approved action is released once", - "approval": { - "action_fingerprint": "c70d4ee57957202087887cb5e9d32222977b728bd06947b7761c283b6d4ed394", - "intent_hash": "76f8eef1b97e1213a59eec28cedf15bb999fdb00a3fd17f8343bc4676fdbb4f3", - "status": "approved", - "rule": "place-order" - }, - "presented_action": { - "instruction": "place a small order", - "method": "POST", - "url": "https://api.example.com/orders", - "params": { - "amount": 2400, - "currency": "USD", - "destination": "internal" - } - }, - "presented_fingerprint": "c70d4ee57957202087887cb5e9d32222977b728bd06947b7761c283b6d4ed394", - "presented_intent_hash": "76f8eef1b97e1213a59eec28cedf15bb999fdb00a3fd17f8343bc4676fdbb4f3", - "expected": { - "outcome": "allow", - "reason_contains": "human approved by 'human'" - } - }, - { - "name": "replay of a consumed approval is refused", - "approval": { - "action_fingerprint": "c70d4ee57957202087887cb5e9d32222977b728bd06947b7761c283b6d4ed394", - "intent_hash": "76f8eef1b97e1213a59eec28cedf15bb999fdb00a3fd17f8343bc4676fdbb4f3", - "status": "consumed", - "rule": "place-order" - }, - "presented_action": { - "instruction": "place a small order", - "method": "POST", - "url": "https://api.example.com/orders", - "params": { - "amount": 2400, - "currency": "USD", - "destination": "internal" - } - }, - "presented_fingerprint": "c70d4ee57957202087887cb5e9d32222977b728bd06947b7761c283b6d4ed394", - "presented_intent_hash": "76f8eef1b97e1213a59eec28cedf15bb999fdb00a3fd17f8343bc4676fdbb4f3", - "expected": { - "outcome": "deny", - "reason_contains": "approval already used: this single-use approval has already released its action" - } - }, - { - "name": "P1: fingerprint mismatch is refused even when approved (substitution-proof, guard before status)", - "property": "P1", - "approval": { - "action_fingerprint": "c70d4ee57957202087887cb5e9d32222977b728bd06947b7761c283b6d4ed394", - "intent_hash": "76f8eef1b97e1213a59eec28cedf15bb999fdb00a3fd17f8343bc4676fdbb4f3", - "status": "approved", - "rule": "place-order" - }, - "presented_action": { - "instruction": "place a small order", - "method": "POST", - "url": "https://api.example.com/orders", - "params": { - "amount": 2400, - "currency": "USD", - "destination": "internal", - "recipient": "attacker" - } - }, - "presented_fingerprint": "dabddc8fc7e8fb30bdec6fb796a336b7897d4a2a12ae386727e2110d7e0e9572", - "presented_intent_hash": "76f8eef1b97e1213a59eec28cedf15bb999fdb00a3fd17f8343bc4676fdbb4f3", - "expected": { - "outcome": "deny", - "reason_contains": "approval/action mismatch: this approval was issued for a different action (possible confused-deputy / substituted action)" - } - }, - { - "name": "P3: a denied approval is not resurrected by a matching action", - "property": "P3", - "approval": { - "action_fingerprint": "c70d4ee57957202087887cb5e9d32222977b728bd06947b7761c283b6d4ed394", - "intent_hash": "76f8eef1b97e1213a59eec28cedf15bb999fdb00a3fd17f8343bc4676fdbb4f3", - "status": "denied", - "rule": "place-order" - }, - "presented_action": { - "instruction": "place a small order", - "method": "POST", - "url": "https://api.example.com/orders", - "params": { - "amount": 2400, - "currency": "USD", - "destination": "internal" - } - }, - "presented_fingerprint": "c70d4ee57957202087887cb5e9d32222977b728bd06947b7761c283b6d4ed394", - "presented_intent_hash": "76f8eef1b97e1213a59eec28cedf15bb999fdb00a3fd17f8343bc4676fdbb4f3", - "expected": { - "outcome": "deny", - "reason_contains": "human denied this action" - } + { + "name": "unknown approval id is denied", + "approval": null, + "presented_action": { + "instruction": "place a small order", + "method": "POST", + "url": "https://api.example.com/orders", + "params": { + "amount": 2400, + "currency": "USD", + "destination": "internal" + } + }, + "presented_fingerprint": "4327df2637072bf058622d1f8baea6e431726f7332a50cd12ec970d6e43c2fd2", + "presented_intent_hash": "76f8eef1b97e1213a59eec28cedf15bb999fdb00a3fd17f8343bc4676fdbb4f3", + "expected": { + "outcome": "deny", + "reason_contains": "unknown approval id" + } + }, + { + "name": "fingerprint mismatch is refused (before status)", + "approval": { + "action_fingerprint": "4327df2637072bf058622d1f8baea6e431726f7332a50cd12ec970d6e43c2fd2", + "intent_hash": "76f8eef1b97e1213a59eec28cedf15bb999fdb00a3fd17f8343bc4676fdbb4f3", + "status": "pending", + "rule": "place-order" + }, + "presented_action": { + "instruction": "place a small order", + "method": "POST", + "url": "https://api.example.com/orders", + "params": { + "amount": 2400, + "currency": "USD", + "destination": "internal", + "recipient": "attacker" + } + }, + "presented_fingerprint": "43aa4af8115a932a72f30da058b7396427a6f69f98cffbe4df8ce78017b89123", + "presented_intent_hash": "76f8eef1b97e1213a59eec28cedf15bb999fdb00a3fd17f8343bc4676fdbb4f3", + "expected": { + "outcome": "deny", + "reason_contains": "approval/action mismatch: this approval was issued for a different action (possible confused-deputy / substituted action)" + } + }, + { + "name": "intent mismatch is refused", + "approval": { + "action_fingerprint": "4327df2637072bf058622d1f8baea6e431726f7332a50cd12ec970d6e43c2fd2", + "intent_hash": "76f8eef1b97e1213a59eec28cedf15bb999fdb00a3fd17f8343bc4676fdbb4f3", + "status": "approved", + "rule": "place-order" + }, + "presented_action": { + "instruction": "place a different order", + "method": "POST", + "url": "https://api.example.com/orders", + "params": { + "amount": 2400, + "currency": "USD", + "destination": "internal" + } + }, + "presented_fingerprint": "4327df2637072bf058622d1f8baea6e431726f7332a50cd12ec970d6e43c2fd2", + "presented_intent_hash": "70b7c5af667d7dc104bd9baaf5c26ebdabeebe69272ee013a6306e208a09c4b3", + "expected": { + "outcome": "deny", + "reason_contains": "approval/intent mismatch: this approval was issued for a different instruction" + } + }, + { + "name": "matching action while pending awaits approval", + "approval": { + "action_fingerprint": "4327df2637072bf058622d1f8baea6e431726f7332a50cd12ec970d6e43c2fd2", + "intent_hash": "76f8eef1b97e1213a59eec28cedf15bb999fdb00a3fd17f8343bc4676fdbb4f3", + "status": "pending", + "rule": "place-order" + }, + "presented_action": { + "instruction": "place a small order", + "method": "POST", + "url": "https://api.example.com/orders", + "params": { + "amount": 2400, + "currency": "USD", + "destination": "internal" + } + }, + "presented_fingerprint": "4327df2637072bf058622d1f8baea6e431726f7332a50cd12ec970d6e43c2fd2", + "presented_intent_hash": "76f8eef1b97e1213a59eec28cedf15bb999fdb00a3fd17f8343bc4676fdbb4f3", + "expected": { + "outcome": "needs_approval", + "reason_contains": "awaiting human approval" + } + }, + { + "name": "human-denied action is refused", + "approval": { + "action_fingerprint": "4327df2637072bf058622d1f8baea6e431726f7332a50cd12ec970d6e43c2fd2", + "intent_hash": "76f8eef1b97e1213a59eec28cedf15bb999fdb00a3fd17f8343bc4676fdbb4f3", + "status": "denied", + "rule": "place-order" + }, + "presented_action": { + "instruction": "place a small order", + "method": "POST", + "url": "https://api.example.com/orders", + "params": { + "amount": 2400, + "currency": "USD", + "destination": "internal" + } + }, + "presented_fingerprint": "4327df2637072bf058622d1f8baea6e431726f7332a50cd12ec970d6e43c2fd2", + "presented_intent_hash": "76f8eef1b97e1213a59eec28cedf15bb999fdb00a3fd17f8343bc4676fdbb4f3", + "expected": { + "outcome": "deny", + "reason_contains": "human denied this action" + } + }, + { + "name": "approved action is released once", + "approval": { + "action_fingerprint": "4327df2637072bf058622d1f8baea6e431726f7332a50cd12ec970d6e43c2fd2", + "intent_hash": "76f8eef1b97e1213a59eec28cedf15bb999fdb00a3fd17f8343bc4676fdbb4f3", + "status": "approved", + "rule": "place-order" + }, + "presented_action": { + "instruction": "place a small order", + "method": "POST", + "url": "https://api.example.com/orders", + "params": { + "amount": 2400, + "currency": "USD", + "destination": "internal" + } + }, + "presented_fingerprint": "4327df2637072bf058622d1f8baea6e431726f7332a50cd12ec970d6e43c2fd2", + "presented_intent_hash": "76f8eef1b97e1213a59eec28cedf15bb999fdb00a3fd17f8343bc4676fdbb4f3", + "expected": { + "outcome": "allow", + "reason_contains": "human approved by 'human'" + } + }, + { + "name": "replay of a consumed approval is refused", + "approval": { + "action_fingerprint": "4327df2637072bf058622d1f8baea6e431726f7332a50cd12ec970d6e43c2fd2", + "intent_hash": "76f8eef1b97e1213a59eec28cedf15bb999fdb00a3fd17f8343bc4676fdbb4f3", + "status": "consumed", + "rule": "place-order" + }, + "presented_action": { + "instruction": "place a small order", + "method": "POST", + "url": "https://api.example.com/orders", + "params": { + "amount": 2400, + "currency": "USD", + "destination": "internal" + } + }, + "presented_fingerprint": "4327df2637072bf058622d1f8baea6e431726f7332a50cd12ec970d6e43c2fd2", + "presented_intent_hash": "76f8eef1b97e1213a59eec28cedf15bb999fdb00a3fd17f8343bc4676fdbb4f3", + "expected": { + "outcome": "deny", + "reason_contains": "approval already used: this single-use approval has already released its action" + } + }, + { + "name": "P1: fingerprint mismatch is refused even when approved (substitution-proof, guard before status)", + "property": "P1", + "approval": { + "action_fingerprint": "4327df2637072bf058622d1f8baea6e431726f7332a50cd12ec970d6e43c2fd2", + "intent_hash": "76f8eef1b97e1213a59eec28cedf15bb999fdb00a3fd17f8343bc4676fdbb4f3", + "status": "approved", + "rule": "place-order" + }, + "presented_action": { + "instruction": "place a small order", + "method": "POST", + "url": "https://api.example.com/orders", + "params": { + "amount": 2400, + "currency": "USD", + "destination": "internal", + "recipient": "attacker" + } + }, + "presented_fingerprint": "43aa4af8115a932a72f30da058b7396427a6f69f98cffbe4df8ce78017b89123", + "presented_intent_hash": "76f8eef1b97e1213a59eec28cedf15bb999fdb00a3fd17f8343bc4676fdbb4f3", + "expected": { + "outcome": "deny", + "reason_contains": "approval/action mismatch: this approval was issued for a different action (possible confused-deputy / substituted action)" + } + }, + { + "name": "P3: a denied approval is not resurrected by a matching action", + "property": "P3", + "approval": { + "action_fingerprint": "4327df2637072bf058622d1f8baea6e431726f7332a50cd12ec970d6e43c2fd2", + "intent_hash": "76f8eef1b97e1213a59eec28cedf15bb999fdb00a3fd17f8343bc4676fdbb4f3", + "status": "denied", + "rule": "place-order" + }, + "presented_action": { + "instruction": "place a small order", + "method": "POST", + "url": "https://api.example.com/orders", + "params": { + "amount": 2400, + "currency": "USD", + "destination": "internal" + } + }, + "presented_fingerprint": "4327df2637072bf058622d1f8baea6e431726f7332a50cd12ec970d6e43c2fd2", + "presented_intent_hash": "76f8eef1b97e1213a59eec28cedf15bb999fdb00a3fd17f8343bc4676fdbb4f3", + "expected": { + "outcome": "deny", + "reason_contains": "human denied this action" } + } ] diff --git a/versions/spec-v0.1.md b/versions/spec-v0.1.md index 7ffe283..7ad492f 100644 --- a/versions/spec-v0.1.md +++ b/versions/spec-v0.1.md @@ -161,7 +161,9 @@ order {…, recipient:"attacker"} (one param added) → dabddc8fc7e8fb30bdec6fb796a336b7897d4a2a12ae386727e2110d7e0e9572 ``` -See [`ctk/vectors/hashing.json`](../ctk/vectors/hashing.json) for the full set. +See [`ctk/vectors/hashing-v0.2.json`](../ctk/vectors/hashing-v0.2.json) for the +full set on this version's preimage (0.1 and 0.2 share it; `hashing.json` is +generated on the 0.3 preimage, which folds the URL query into the fingerprint). ### 4.2 Query string diff --git a/versions/spec-v0.2.md b/versions/spec-v0.2.md index 82f2225..88a50ad 100644 --- a/versions/spec-v0.2.md +++ b/versions/spec-v0.2.md @@ -156,7 +156,9 @@ order {…, recipient:"attacker"} (one param added) → dabddc8fc7e8fb30bdec6fb796a336b7897d4a2a12ae386727e2110d7e0e9572 ``` -See [`ctk/vectors/hashing.json`](../ctk/vectors/hashing.json) for the full set. +See [`ctk/vectors/hashing-v0.2.json`](../ctk/vectors/hashing-v0.2.json) for the +full set on this version's preimage. (`hashing.json` is generated on the 0.3 +preimage, which folds the URL query into the fingerprint.) ### 4.2 Query string