From 86df4e78b71aed0ece18a59f45fe209a8eb868bb Mon Sep 17 00:00:00 2001 From: Raghuvar Karthik Durga <177662415+drk-ram@users.noreply.github.com> Date: Thu, 5 Nov 2026 11:30:10 -0600 Subject: [PATCH] =?UTF-8?q?spec(ask):=20clarify=20approver=20authenticatio?= =?UTF-8?q?n=20across=20human,=20agent,=20and=20service=20approvers=20in?= =?UTF-8?q?=20=C2=A79?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Clarifies in §9 and §9.1 of the Instrument specification that approver authentication is required across all three approver types, distinguishes the optional `approver.auth` object (`method`: `bearer` or `mtls`) from out-of-band human approver verification, and requires a verified approver grant before extending `Intent.parsed`. Part of #51 Signed-off-by: Raghuvar Karthik Durga <177662415+drk-ram@users.noreply.github.com> --- docs/spec/instrument/specification.md | 8 ++++--- tests/test_approver_model.py | 30 +++++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 3 deletions(-) create mode 100644 tests/test_approver_model.py diff --git a/docs/spec/instrument/specification.md b/docs/spec/instrument/specification.md index 12e98bc8..dbb5c184 100644 --- a/docs/spec/instrument/specification.md +++ b/docs/spec/instrument/specification.md @@ -314,7 +314,9 @@ Tamper-evidence here is bounded by the signature in use. Under the HMAC baseline ## 9. Approver Model -ASK approvers MAY be human, agent, or service. `ask_details.approver = { type, id, endpoint }`. The Approver receives an ACS-shaped request and returns an ACS-shaped decision. Approver authentication is REQUIRED. Guardian MUST verify approver identity against policy. +ASK approvers MAY be human, agent, or service. `ask_details.approver = { type, id, endpoint, auth }`. The Approver receives an ACS-shaped request and returns an ACS-shaped decision. + +Approver authentication is REQUIRED across all three approver types (`human`, `agent`, `service`), and the Guardian MUST verify the identity of the party returning an approval decision against deployment policy before honoring it. The optional `approver.auth` object (`method`: `bearer` or `mtls`) describes authentication for a machine (`agent` or `service`) approver endpoint; its presence on the wire does not by itself establish that verification occurred. A deployment authenticates a `human` approver out of band through its trusted approval interface and conveys the verified identity to the Guardian; a claimed `approver.id` alone is insufficient. Single-hop only in v0.1. Approvers MUST NOT return ASK. Quorum and recursive ASK deferred to v0.2. @@ -322,10 +324,10 @@ Single-hop only in v0.1. Approvers MUST NOT return ASK. Quorum and recursive ASK When a Guardian raises ASK because a request is outside `Intent.parsed`, the approver's grant MAY include an `intent_extension` field (see [`ask-details.json`](https://genai-security-project.github.io/agent-control-standard/schema/v0.1.0/ask-details.json)) containing capabilities to add to `Intent.parsed`. The extension's `scope` selects between `this_request` (capabilities apply only to the in-flight request) and `session` (capabilities are appended to `Intent.parsed` for the remainder of the session). -On `scope: session`, the Guardian MUST: +On `scope: session`, when and only when a verified approver grants the request, the Guardian MUST: 1. Append the capabilities to `Intent.parsed`. -2. Write a ContextEntry with `step_type: "intent_extension"` recording the approver identity, the granted capabilities, and the originating ASK's `step_id`. +2. Write a ContextEntry with `step_type: "intent_extension"` recording the verified approver identity, the granted capabilities, and the originating ASK's `step_id`. 3. Carry the extension's `provenance` forward distinct from the original `parser_provenance` so audits can distinguish parser-derived capabilities from approver-extended ones. Intent extensions are subject to the session's `scope_mode`: a Guardian operating under `scope_mode: strict` MUST NOT honor extensions that would add capabilities the deployment policy forbids in strict mode. This mechanism is the only conformant path to mutate `Intent.parsed` after Intent is committed. diff --git a/tests/test_approver_model.py b/tests/test_approver_model.py new file mode 100644 index 00000000..f5ea6b28 --- /dev/null +++ b/tests/test_approver_model.py @@ -0,0 +1,30 @@ +"""Approver rules in Specification §9 and ask-details.json (#51).""" + +import json +from pathlib import Path + +ROOT = Path(__file__).resolve().parent.parent +SPEC = ROOT / "docs" / "spec" / "instrument" / "specification.md" +ASK = ROOT / "specification" / "v0.1.0" / "ask-details.json" + + +def approver_section(): + text = SPEC.read_text(encoding="utf-8") + return text.split("## 9. Approver Model", 1)[1].split("## 10.", 1)[0] + + +def test_every_approver_type_must_authenticate(): + section = approver_section() + assert "REQUIRED across all three approver types" in section + assert "the Guardian MUST verify" in section + assert "out of band" in section + assert "a claimed `approver.id` alone is insufficient" in section + assert "does not by itself establish that verification occurred" in section + assert "when and only when a verified approver grants" in section + + +def test_the_v0_1_0_wire_contract_is_unchanged(): + """A required `auth` breaks valid payloads, so issue #51 keeps it for a later spec version.""" + schema = json.loads(ASK.read_text(encoding="utf-8")) + assert schema["properties"]["approver"]["required"] == ["type", "id"] + assert schema["properties"]["timeout_disposition"]["default"] == "deny"