Module: Parse::Webhooks::ReplayProtection
- Defined in:
- lib/parse/webhooks/replay_protection.rb
Overview
NEW-EXT-4: webhook freshness and replay protection.
Parse Server’s default webhook delivery is authenticated only by the static X-Parse-Webhook-Key header. A captured POST is therefore indefinitely replayable – a Ruby-initiated save bearing an RB request id will continue to suppress server-side after_* callbacks every time it is replayed, and a generic trigger payload can be delivered repeatedly to fire double-charges or other side effects.
This module adds two layers on top of the existing static-key check:
-
**Always-on body+request-id dedup.** A bounded LRU records a SHA-256 of (request_id || “”) joined with the request body. A duplicate seen within
replay_window_secondsis rejected with “Webhook replay detected.”. Cooperation with Parse Server is not required; this protects against in-window replays only, but those are the cheapest attack to mount (proxy retries, captured fast loops, retransmits). -
**Opt-in HMAC freshness verification.** When a
signing_secretis configured (programmatically or viaPARSE_WEBHOOK_SIGNING_SECRET) the dispatcher requires two extra headers on every request:-
X-Parse-Webhook-Timestamp– decimal Unix epoch seconds. -
X-Parse-Webhook-Signature– hex-encoded HMAC-SHA256 of the bytes “#{timestamp}.#{body}” keyed with the signing secret.
Requests outside
signing_max_skew_seconds(default 300) or with an invalid signature are rejected. Once enabled, this gives full binding between the body and the time of delivery and closes the replay window beyond the freshness skew. -
Operators wanting layer 2 must arrange for Parse Server to add these headers. Parse Server does not natively sign webhook deliveries, so this is typically done with a thin Cloud Code wrapper or an egress proxy. Until enabled, layer 1 still applies.
Defined Under Namespace
Classes: LruCache
Class Attribute Summary collapse
-
.replay_cache_size ⇒ Object
Maximum number of entries retained in the dedup LRU.
-
.replay_window_seconds ⇒ Object
How long a (request_id, body) digest stays in the dedup cache.
-
.signing_max_skew_seconds ⇒ Object
Maximum allowed clock skew (in seconds) between the timestamp header and the receiver.
-
.signing_secret ⇒ Object
Shared HMAC secret used to verify
X-Parse-Webhook-Signature.
Class Attribute Details
.replay_cache_size ⇒ Object
Maximum number of entries retained in the dedup LRU. Older entries are evicted to keep memory bounded.
87 88 89 |
# File 'lib/parse/webhooks/replay_protection.rb', line 87 def replay_cache_size @replay_cache_size || DEFAULT_REPLAY_CACHE_SIZE end |
.replay_window_seconds ⇒ Object
How long a (request_id, body) digest stays in the dedup cache. Duplicates seen within this window are rejected.
81 82 83 |
# File 'lib/parse/webhooks/replay_protection.rb', line 81 def replay_window_seconds @replay_window_seconds || DEFAULT_REPLAY_WINDOW end |
.signing_max_skew_seconds ⇒ Object
Maximum allowed clock skew (in seconds) between the timestamp header and the receiver. Requests outside this window are rejected as stale when signing_secret is set.
75 76 77 |
# File 'lib/parse/webhooks/replay_protection.rb', line 75 def signing_max_skew_seconds @signing_max_skew_seconds || DEFAULT_MAX_SKEW end |
.signing_secret ⇒ Object
Shared HMAC secret used to verify X-Parse-Webhook-Signature. When nil/empty, signature verification is skipped (layer 1 still applies). Defaults to ENV.
67 68 69 70 |
# File 'lib/parse/webhooks/replay_protection.rb', line 67 def signing_secret return @signing_secret if defined?(@signing_secret) && !@signing_secret.nil? ENV["PARSE_WEBHOOK_SIGNING_SECRET"] end |