Skip to content

feat(agent-mesh): versioned action-bound webhook approval contract (ADR-0030 step 4) - #3097

Merged
Imran Siddique (imran-siddique) merged 1 commit into
microsoft:mainfrom
carloshvp:feat/versioned-webhook-contract
Jun 17, 2026
Merged

Imran Siddique (imran-siddique) merged 1 commit into
microsoft:mainfrom
carloshvp:feat/versioned-webhook-contract

Conversation

@carloshvp

Copy link
Copy Markdown
Contributor

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 an approver string 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's presented_canonical()), plus the input_digest.
  • parse_webhook_response(body, *, request, response_verifier=None): the response must echo approval_request_id and action_digest (binding mismatch denies); an approve is honoured only when the approver identity is verified by response_verifier; a deny is always honoured; anything malformed denies.
  • VersionedWebhookApproval: the protocol-native transport (it takes the protocol ApprovalRequest, because the legacy ApprovalHandler.request_approval signature is too thin to carry the action digest). POSTs with caller-supplied auth headers, reuses the existing _validate_webhook_url SSRF guard, and fails closed on timeout, transport error, malformed response, or binding mismatch. HTTP is injectable for tests.

Scope

  • Standalone and additive: no changes to govern, to the legacy WebhookApproval (kept as the "old payload behind a legacy adapter" the ADR allows), or to the approval_protocol foundation.
  • Independent of the step-3 PR (feat(agent-mesh): legacy ApprovalHandler to protocol compatibility adapter (ADR-0030 step 3) #3096): different files, so the two can review and merge in any order. Wiring the transport into the govern/bridge flow is a follow-up.
  • Auth depth (first cut): outbound auth via caller-supplied headers; inbound trust requires the response to echo the binding plus a verified principal via 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.

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
@github-actions

Copy link
Copy Markdown
🤖 AI Agent: breaking-change-detector — API Compatibility

AI-generated review output. Treat it as untrusted analysis and verify before acting.

API Compatibility

No breaking changes detected.

@github-actions

Copy link
Copy Markdown
🤖 AI Agent: security-scanner — View details

AI-generated review output. Treat it as untrusted analysis and verify before acting.

No security issues found.

@github-actions

Copy link
Copy Markdown
🤖 AI Agent: code-reviewer — Action items:

AI-generated review output. Treat it as untrusted analysis and verify before acting.

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.

# Sev Issue Where
1 Warn Missing tests for timeout and transport error handling in VersionedWebhookApproval. test_approval_webhook.py

Action items:

  1. Add tests for timeout and transport error handling in VersionedWebhookApproval to ensure fail-closed behavior.
Warnings: fine as follow-up PRs
Add tests for timeout and transport error handling in VersionedWebhookApproval.

@github-actions

Copy link
Copy Markdown
🤖 AI Agent: docs-sync-checker — Docs Sync

AI-generated review output. Treat it as untrusted analysis and verify before acting.

Docs Sync

Documentation is in sync.

@github-actions

Copy link
Copy Markdown
🤖 AI Agent: test-generator — `agentmesh/governance/approval_webhook.py`

AI-generated review output. Treat it as untrusted analysis and verify before acting.

agentmesh/governance/approval_webhook.py

  • VersionedWebhookApproval.request_decision -- Test for handling of invalid or missing headers during the HTTP POST request.
  • VersionedWebhookApproval.request_decision -- Test for behavior when the transport function raises an exception other than timeout or decode errors.
  • parse_webhook_response -- Test for behavior when the response_verifier raises an unexpected exception.
  • parse_webhook_response -- Test for cases where the approved field is missing or has an unexpected data type.
  • build_webhook_request -- Test for behavior when ApprovalRequest has missing or invalid fields.

@github-actions

Copy link
Copy Markdown

PR Review Summary

Check Status Details
🔍 Code Review ⚠️ Missing No current-run comment
🛡️ Security Scan ⚠️ Missing No current-run comment
🔄 Breaking Changes ⚠️ Missing No current-run comment
📝 Docs Sync ⚠️ Missing No current-run comment
🧪 Test Coverage ⚠️ Missing No current-run comment

Verdict: ⚠️ AI review incomplete; ready for human review

AI review comments are untrusted advisory output. The summary reports workflow-generated completion status only, not model-authored pass/fail claims.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agent-mesh agent-mesh package size/L Large PR (< 500 lines) tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants