deploy: optional notify_webhook URL fires HTTP POST on deploy terminal state#62
Merged
Merged
Conversation
…l state
Adds a new POST /deploy/new field that lets callers subscribe to deploy
terminal-state events instead of polling GET /deploy/:id. When the deploy
reaches 'healthy' or 'failed', a worker job (separate PR) will POST a
payload to the supplied URL, optionally signed with HMAC-SHA256.
Migration 026 adds four columns to deployments: notify_webhook,
notify_webhook_secret (AES-256-GCM at rest), notify_state ('unset' /
'pending' / 'sent' / 'failed'), notify_attempts. A partial index on
(notify_state, status) WHERE notify_state='pending' keeps the worker
scan cheap.
SSRF gate enforces https-only scheme and rejects hostnames resolving to
loopback / RFC1918 / link-local (incl. AWS/GCP metadata 169.254.169.254)
/ CGNAT / multicast / IPv6 unique-local. Mixed-record DNS attacks are
caught: if ANY resolved IP is in a blocked range, the URL is rejected.
This PR only persists the fields; the worker-side dispatcher lives in
the worker repo and is a separate follow-up.
…-notify-fresh # Conflicts: # internal/handlers/agent_action_contract_test.go
4 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds an optional
notify_webhookfield toPOST /deploy/newso callers can subscribe to deploy terminal-state events instead of polling. The agent supplies an https URL and (optionally) an HMAC signing secret; when the deploy reacheshealthyorfailed, the worker (separate PR) POSTs a payload there.026_deploy_webhook.sql): addsnotify_webhook,notify_webhook_secret,notify_state(unset/pending/sent/failed),notify_attemptscolumns + partial index on(notify_state, status) WHERE notify_state='pending'POST /deploy/new): two new multipart fields parsed, SSRF-gated, secret AES-256-GCM encrypted at rest; new fields surfaced in the responseagent_action_contract_test.goextended withAgentActionNotifyWebhookInvalidSSRF protection — which ranges we reject
The validator (
validateNotifyWebhookURL) refuses any URL whose hostname resolves to (or whose literal host is in) any of:127.0.0.0/810.0.0.0/8,172.16.0.0/12,192.168.0.0/16169.254.0.0/16(covers AWS/GCP metadata169.254.169.254)100.64.0.0/10224.0.0.0/4, broadcast255.255.255.255, unspecified0.0.0.0::1, unspecified::, link-localfe80::/10, unique-localfc00::/7, multicastff00::/8::ffff:127.0.0.1) — re-checked as v4localhost(and*.localhost) before any DNS lookupThe mixed-record DNS dodge is caught: a hostname resolving to
[8.8.8.8, 10.0.0.5]is rejected because ANY resolved IP being blocked rejects the whole URL.Worker dispatcher is a separate PR
This PR only persists the fields. The worker job that scans
WHERE notify_state='pending' AND status IN ('healthy','failed')and POSTs to the URL lives in the worker repo and is a follow-up — its contract:notify_state='sent'notify_state='failed'(no retry — user URL is broken)notify_state='pending', bumpnotify_attempts, give up after 3notify_webhook_secretis set, decrypt it and includeX-InstaNode-Signature: sha256=<hex(hmac(secret, body))>{event: 'deploy.healthy' | 'deploy.failed', deploy_id, app_id, url, commit_id, build_time, duration_s, error_message?}Test plan
make test-unitgreen (entire workspace; pre-existingTestAdminList_AdminUserSees200flake is unrelated and reproduces on master)TestAgentActionContractgreen — newAgentActionNotifyWebhookInvalidsatisfies the U3 contract (227 chars, < 280)validateNotifyWebhookURLand the HTTP handler:notify_state='pending'notify_state='unset'(backward compat)🤖 Generated with Claude Code