Signed once, processed twice
A fictional billing webhook accepts a tampered payload and applies a valid event twice. Repair the verification contract and explain how durable processing would differ.
AppSec · about 75 minutes. Fictional evidence; Python 3.10+ for local coding.
Prerequisites
Evidence
delivery.log
10:00:00 event=evt-42 signature=valid decision=accepted credits=1
10:00:03 event=evt-42 signature=valid decision=accepted credits=2
10:00:05 event=evt-43 body_changed=true signature=unchecked decision=accepted
These are synthetic observations, not a real payment provider log.verification-contract.txt
Fictional training contract, not vendor-specific:
HMAC-SHA256(secret, ASCII(timestamp) + b"." + exact_raw_body_bytes)
Compare the hexadecimal digest with a constant-time comparison.
Reject timestamps more than 300 seconds away from the trusted current time.
Verify integrity and freshness before parsing or recording an event.
Event id must be a non-empty string of at most 64 characters.
A repeated verified event is acknowledged as duplicate with no second effect.
Rejected messages must not consume an event id.worker-design.txt
Current demo: JSON parse -> credit account -> remember event id.
Two workers can read "not seen" at the same time.
Required production design: durable event identity and atomic state transition.
For a database-local credit, a unique inbox row and credit update can share one transaction.
For an external side effect, design an outbox/idempotent consumer and recovery path.
A Python set demonstrates only serial deduplication, not concurrency safety.Tasks and expected evidence
- Explain why valid signatures did not stop the first two deliveries. Identify the evidence for a separate integrity failure.
- Edit webhook_event in the exercise. Authenticate the exact raw bytes, enforce freshness, validate the event ID, and suppress serial duplicates.
- Run the webhook suite without --reference. Document the changed-body, wrong-signature, stale, future, malformed, and replay results.
- Sketch a durable inbox/outbox or transaction design. Describe a two-worker race and a crash between committing a result and acknowledging delivery.
Run the coding exercise
Download the Python exercise. It uses only the standard library and no network.
python expert_security_lab.py --lab webhook
python expert_security_lab.py --lab webhook --referenceStarter mode initially fails. Edit the matching function and rerun without --reference. Reference mode tests supplied code, not your own fix.
Limits
The reference is a serial, in-memory model. It does not guarantee concurrent or durable exactly-once delivery. Real integrations must follow their provider’s signature and retry contract.