Where a drain may point
A destination URL is checked when the drain is created, and the refusal names the rule that fired:
The first four answer
422 DRAIN_ENDPOINT_INVALID; not_allowlisted answers 422 DRAIN_ENDPOINT_NOT_ALLOWED. The message reads endpoint refused by the '<reason>' rule: <detail>.
DNS availability is not a precondition for saving configuration. A host that resolves to nothing is normally accepted at create time and refused later, at delivery — so
unresolvable is the one word on that list you are unlikely to meet on a create. It is what a delivery failure looks like instead, as errorClass: "dns", and unlike every other refusal on this page it is retried: a name that does not resolve now may resolve in a minute, while a policy refusal will not become allowed.::ffff:10.0.0.1 is unwrapped before it is classified — that is the oldest way to smuggle a private address past a check that only looks at the v6 form.
The ranges refused include 0.0.0.0/8 and :: — “this network” and the unspecified address, which some stacks route to a local interface — alongside 10/8, 172.16/12, 192.168/16, 100.64/10, 127/8, 169.254/16, fc00::/7, fe80::/10, ::1, and the well-known instance-metadata addresses.
It is re-checked on every single delivery
Which destination types pin a host
Splunk and Loki are unpinned for the same reason Better Stack is: both are commonly self-hosted on a domain of yours, and an allow-list there would refuse every legitimate drain.
What “verified” actually proves
This is the distinction most worth reading twice.
Verification is limited to 5 attempts per drain per hour. Changing a drain’s endpoint sends it back to
pending_verification and stops delivery until it re-verifies; records buffer meanwhile.
What is masked before anything leaves
Every record body and every string attribute passes a scrubber on its way to the wire. It cannot be turned off — only extended. There is no flag, no drain field, and no argument that removes a built-in rule, and an empty rule set still runs every one of them. The mask is visible on purpose —[redacted:builtin], [redacted:lua-token], [redacted:vendor-key], or [redacted:org:<your rule id>] — so a value that was scrubbed is distinguishable from one that was never there, and the marker says which rule fired. Scrubbing an already-scrubbed string is a no-op and adds no hits.
What is not masked
- Message content as such. The scrubber masks credential shapes; it is not a PII engine. End-user text is governed by content sources and
includeContentinstead. - Attribute keys. Only values are scanned. Lua’s attribute keys are a closed set and none is secret-bearing.
- Numbers and booleans. No credential shape can be expressed in one.
- A credential with no recognisable shape. A password that looks like an ordinary word, printed on its own with no key name, cannot be told from prose. Add a rule of your own.
Scrubbing is not a substitute for not logging secrets. Anything your code prints with
console.log is stored as printed, and the scrubber only catches shapes it recognises.Your own rules
A drain’sscrubRules is a list of { id, pattern, flags? }, compiled once and run after the built-ins.
A rule that fails any of these answers
422 DRAIN_SCRUB_RULE_INVALID at save time. The same function validates and compiles, so a rule that saves is exactly a rule that runs.
The patterns that are refused
Four constructs are rejected, because each of them makes a regular expression exponential on the wrong input — and a drain runs your pattern against every string in every record.- Backreferences —
\1,\k<name>. - Lookbehind —
(?<=…),(?<!…). Named groups,(?<name>…), are fine. - Nested unbounded quantifiers — a quantified group whose body itself contains
*,+, or{n,}:(a+)+,(\s*\w+)*. A bounded outer quantifier is fine:(ab){2,4},(a+)?b. - Unbounded repetition of an alternation —
(a|a)+,(?:x|xy)*,([a-z]|\w){3,}. Repeating branches that can match the same text is the other exponential shape;(a|a)+$needs only 35 characters of input to hang a process for minutes.
How a destination credential is held
Three separate secrets, all write-only, all stored encrypted and bound to your organization:last4 is an empty string rather than a shortened value when the stored secret is under 8 characters.
A secret is never a command-line flag, because a shell history and a CI log both keep one. --header prompts; --header-from-env and --credentials-from-env name environment variables whose values never reach argv.
Rotating the signing secret
--finalize closes the window early; it closes on its own at 24 hours. A second rotation while a window is open answers 409 DRAIN_ROTATION_IN_PROGRESS. The full receiver-side recipe is on Verify signatures.
What a receiver must do
For a generic HTTPS drain, two obligations, and both matter:- Verify the signature before parsing the body, and compare in constant time. The recipe and four working implementations are on Verify signatures.
- Reject a timestamp more than 300 seconds from now, in either direction. That is the replay window, it is inclusive at exactly 300, and the platform never signs a batch it will not send inside it. Widening it past five minutes gives up the replay protection.
What Lua never stores
- No request body. A delivery row holds the status code, latency, byte counts, error class, and at most 1 KB of the destination’s response — scrubbed. It has no field that can hold what was sent.
- No ownership-challenge token. Only its hash. A read of an object-storage drain’s
challengereturnstokenHash: ""and akeyPatterntelling you where to look — never the token, because a control plane that could hand it back would make the challenge prove nothing. - No credential in an error message. Provider errors carry the vendor’s own error code, never the SDK’s message: an SDK message quotes the signed request, and a signed request carries a credential.
- No secret in a notice. A drain notice is built from an allow-list of fields, so a secret-bearing field added to a drain in future is absent from every notice until somebody deliberately adds it there.
Next steps
Verify signatures
The HMAC recipe, four implementations, and rotation.
Egress allow-listing
The address deliveries leave from.
Delivery guarantees
At-least-once, retry, and what a drop looks like.
Log drains REST
Scopes, refusal codes, and the read model.

