Contribution Date
Contribution Project
Contribution Details
A signature a customer can actually check, and the timestamp that stops a replay
dam-connect has been a one-line doc comment since the repository started. This is
the sending half of the outbox: sign, post, and classify the answer.
The signed string is timestamp.body, not body. A signature over the body alone is
valid forever, so anybody who ever saw one delivery can send it again — and for a
CMS consuming asset.expired that means replaying a withdrawal, or replaying the
asset.published that came before it and un-withdrawing an asset. Stripe's scheme,
for that reason.
That delimiter is only safe because a decimal integer cannot contain a dot, which
is a property of the field rather than of the format — so it is asserted, along
with the collision it would allow: 12 . "34.body" must not sign the same as
1234 . "body". dam_core::signed_url uses length prefixes precisely because its
fields carry no such guarantee.
verify() ships as part of the crate rather than living in the test, so the scheme
has one implementation and the tests check the thing a customer will write instead
of a second copy that happens to agree. Constant-time, because a comparison that
returns early leaks the prefix of a valid signature, and with an oracle that is a
forgery.
What counts as delivered is any 2xx, not 200 alone: a receiver that queues the
event and answers 202 has accepted it, and one that answers 204 has done the work
and has nothing to say. A 3xx is not success — a redirect from a webhook endpoint
is a misconfiguration, and following it would post a customer's data to a host
they never nominated.
Which failures are retried: anything that might be temporary, including a 429,
which is the receiver asking for less rather than saying no. A 4xx is rejected but
still spends the attempt, and that trade is written down rather than assumed:
retrying a permanent failure wastes a bounded number of tries, while abandoning a
temporary one on the first stray 404 from a load balancer loses an event.
The delivery id is stable across attempts. That is what lets a receiver drop a
duplicate after timing out behind the work rather than in front of it; a fresh id
per attempt would make every retry look like a new event.
11 cases, and the HTTP ones go over a real socket to a real axum receiver — a
mocked client would test this code's opinion of itself while leaving untested
whether the headers arrive, whether the body is the bytes that were signed, and
whether a receiver can verify a delivery from the documented scheme alone.
Contribution Issue Link
Files count
0
Patches count
1