Serguey Asael Shinder: An allowlist is a snapshot of what somebody had heard of, and it keeps enforcing it
A denylist is honest about its weakness: everybody knows it only blocks what somebody thought of. An allowlist is sold as the safe alternative, and it has exactly the same weakness pointing the other way — it only permits what somebody thought of, and it goes on permitting precisely that set long after the set stopped describing reality.
The failure mode is not a crash. It is a list that keeps answering with full confidence while the thing it describes moves out from under it.
Where I have watched it happen
File types. An upload endpoint accepts a list of extensions decided in one afternoon. Two years later a team integrates a partner who sends the same data in a newer container format, and the fix goes into a ticket queue because nobody owns the list.
Currencies and country codes. A list frozen at the time of writing. Countries split, codes get added, and the application quietly cannot represent a customer it already has.
Feature flags as an allowlist of environments. if (env in ["dev", "staging", "prod"]) — then someone adds a fourth environment for a regulator and half the features are silently off there, which nobody notices until an audit.
Event types in a consumer. A switch over known types with a default that logs and continues. A producer adds a type, and the consumer ignores it correctly, completely, and forever.

What actually helps
I have never had luck with "remember to update the list". Three things do work:
- Make the unknown case loud. The rule is not "reject the unknown", it is "never let the unknown pass silently". A rejected request with a clear reason is a support conversation; an ignored one is a mystery six weeks later.
- Put the list where the thing it describes lives. A list of event types belongs next to the schema that defines them, not in a service three repositories away. The distance between the list and its subject is a good predictor of how stale it is.
- Count the rejections. One metric,
unknown_value_total, labelled by what was unknown. It costs an afternoon and turns "the list is fine" from a belief into a number you can look at.
The part I keep having to relearn
The instinct when an allowlist blocks something legitimate is to add the missing value and move on. That closes the incident and leaves the mechanism intact.
The better question is why we found out this way — because that answer is the same for the next value, and the one after that. In my experience the answer is nearly always that the unknown case was silent, and making it loud costs one line.