Repository navigation
feat(agent-mesh): versioned action-bound webhook approval contract (ADR-0030 step 4) - #3097
Conversation
Step 4 of the ADR-0030 migration (section 5). The legacy WebhookApproval sends a thin payload with no request id, action digest, version, or expiry and trusts a body-supplied approver string. Add governance/approval_webhook.py with the versioned, action-bound contract: build_webhook_request() emits a schema-versioned payload carrying the request id, action digest, policy version, chain version, and expiry; parse_webhook_response() requires the response to echo the binding and honours an approve only when the approver identity is verified (deny is always honoured, malformed denies); VersionedWebhookApproval is the protocol-native transport (POSTs with caller-supplied auth headers, reuses the SSRF guard, fails closed on timeout/transport/malformed/binding-mismatch). Standalone and additive: no changes to govern, the legacy WebhookApproval (kept as the old-payload legacy path), or the approval_protocol foundation. Independent of the step-3 PR (microsoft#3096); wiring the transport into the govern/bridge flow is a follow-up. 11 tests, no network (HTTP is injectable). Refs microsoft#2478
🤖 AI Agent: breaking-change-detector — API Compatibility
API CompatibilityNo breaking changes detected. |
🤖 AI Agent: security-scanner — View details
No security issues found. |
🤖 AI Agent: code-reviewer — Action items:
TL;DR: 0 blockers, 1 warning. The PR introduces a robust and secure implementation of a versioned, action-bound webhook approval contract, but lacks tests for timeout and transport error handling.
Action items:
|
🤖 AI Agent: docs-sync-checker — Docs Sync
Docs SyncDocumentation is in sync. |
🤖 AI Agent: test-generator — `agentmesh/governance/approval_webhook.py`
|
PR Review Summary
Verdict: AI review comments are untrusted advisory output. The summary reports workflow-generated completion status only, not model-authored pass/fail claims. |
Imran Siddique (imran-siddique)
left a comment
There was a problem hiding this comment.
Well-designed versioned webhook contract. parse_webhook_response correctly echoes both approval_request_id and action_digest before trusting any decision, and the approve path only honours a verified principal from response_verifier (denies if verifier returns None). SSRF guard via _validate_webhook_url is in place. 163-line test file covers binding mismatch, unverified identity, timeout, and deny paths. LGTM.
c26409e
into
microsoft:main
Summary
Step 4 of the ADR-0030 migration (section 5): a versioned, action-bound webhook approval contract.
The legacy
WebhookApproval(approval.py) sends a thin payload with no request id, action digest, policy/chain version, or expiry, and trusts anapproverstring supplied in the response body. ADR-0030 section 5 supersedes that: a webhook is a transport, not an approver identity type, and the contract must carry the binding and refuse body-supplied identities that are not backed by a verified assertion.What changed
New
governance/approval_webhook.py(additive):build_webhook_request(request, *, schema_version="1.0"): schema-versioned, action-bound payload carrying the request id, policy decision id, action digest, policy version, chain version, and expiry (reuses the protocol request'spresented_canonical()), plus theinput_digest.parse_webhook_response(body, *, request, response_verifier=None): the response must echoapproval_request_idandaction_digest(binding mismatch denies); an approve is honoured only when the approver identity is verified byresponse_verifier; a deny is always honoured; anything malformed denies.VersionedWebhookApproval: the protocol-native transport (it takes the protocolApprovalRequest, because the legacyApprovalHandler.request_approvalsignature is too thin to carry the action digest). POSTs with caller-supplied auth headers, reuses the existing_validate_webhook_urlSSRF guard, and fails closed on timeout, transport error, malformed response, or binding mismatch. HTTP is injectable for tests.Scope
govern, to the legacyWebhookApproval(kept as the "old payload behind a legacy adapter" the ADR allows), or to theapproval_protocolfoundation.response_verifier, denying otherwise. Mutual TLS or a built-in signature scheme is later hardening.Tests
tests/test_approval_webhook.py(11 tests, no network): payload field coverage; verified approve; request-id and action-digest mismatch deny; approve without a verifier denies; approve with an unverifiable identity denies; explicit deny needs no verifier; malformed denies; transport posts the versioned payload with auth headers; transport error fails closed; bad URL rejected.Refs #2478. ADR-0030 step 4.