Webhooks: a delivery whose hostname briefly fails to resolve is given up for good, blamed on the private-address rule #549
Labels
No labels
accessibility
authentication
breaking change
bug
documentation
enhancement
interface
internationalisation
observability
performance
security
tier
1
tier
2
tier
3
tier/4
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
Postulo/postulo#549
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Found on 2026-09-29 in a read-only review of postulo at
95348908d, reading the webhook deliverer and the destination check, and callingcheck_destinationwith a resolver that raisesEAI_AGAIN.Related to #240.
Checked again on 2026-10-01 against
mainat0a5cad587: still present. The code is now atplugins/http.py:36-46(check_destination) and, since #248 moved the address check out ofplugins/fetching.py, atplugins/public_addresses.py:31-36(_addresses_for) andplugins/public_addresses.py:58-60(public_addresses_for), which is where the fix'sUnresolvablebelongs;notifications/webhooks.py:124-129is where it was. The same call repeated on today's code raised the sameDestinationRefusedwith the private-address sentence.deliver()treats everyDestinationRefusedfrom the pre-flight check as final, and the check turns a failed DNS lookup into aDestinationRefused. A resolver hiccup therefore ends the delivery after one attempt, instead of the eight attempts with back-off every other transient failure gets, and the recorded error tells the person to ask for private addresses to be allowed.notifications/webhooks.py:124-129(deliver:except DestinationRefused as refused: return fail(str(refused), final=True));plugins/http.py:34-44(check_destinationwraps everyUnsafeURL);plugins/fetching.py:98-103(_addresses_forraisesUnsafeURL("That hostname could not be resolved.")on anysocket.gaierror, temporary ones included) andplugins/fetching.py:125-127.POSTULO_CONNECTIONS_ALLOW_PRIVATEoff),getaddrinforaisingEAI_AGAINmakescheck_destinationraiseDestinationRefusedwith the message "That hostname could not be resolved. Connections may only reach private or local addresses when the operator sets POSTULO_CONNECTIONS_ALLOW_PRIVATE=true."deliverrecords the row asGIVEN_UPwithnext_attempt_at=Noneafter its first attempt. On an instance that allows private destinations the check returns early, the same outage surfaces insideplugin.postas an ordinary exception, and the row is retried. So the operator's address policy decides whether a DNS blip loses notifications.core/destinations.pyalready does withUnresolvableandRefused: raise a distinctUnresolvable(UnsafeURL)from_addresses_forandpublic_addresses_for, letcheck_destinationre-raise it without the private-address sentence, and indelivertreat it as a transient failure (fail(...)withoutfinal). Keepfinal=Truefor a real private-address refusal.Done when
tests/test_webhooks.pypatchessocket.getaddrinfoto raisegaierror(EAI_AGAIN), callsdeliveron a pending row, and asserts the row isFAILEDwith a laternext_attempt_atand an error that does not mentionPOSTULO_CONNECTIONS_ALLOW_PRIVATE.