barakoCMS has a public delivery API. A website frontend hits it to read published content, no login. Simple idea, and exactly the kind of simple idea that leaks data if you're not careful.
Content types in barakoCMS can mark fields as sensitive. A member's phone number, an internal note, a payment reference. The public API is supposed to strip those out before it ever answers.
My first version did the obvious thing. Take the whole content record, remove the fields marked sensitive, return the rest. A denylist. It worked in every test I wrote, because in every test the fields lined up perfectly with the schema.
Then I sat with it for a day and got nervous. What happens when a field gets renamed? The old value can sit in the stored data under the old key, with no matching field in the current schema to say it's sensitive. The denylist doesn't remove it, because it only removes what it recognizes as sensitive. Everything it doesn't recognize, it passes through. A denylist fails open. That's the whole problem with it. The one time it's wrong is the time it leaks.
So I flipped it. Now the public API starts with nothing and only adds back fields the current schema explicitly marks public. An allowlist. If a key doesn't match a known, public field, it's gone. A renamed field, an orphaned value, a differently-cased key, none of them get through, because none of them are on the list of things allowed out. An allowlist fails closed. The one time it's wrong, it hides something it shouldn't, and someone files a bug instead of a breach.
That's the rule I keep coming back to. When you decide what leaves your system to strangers, list what's allowed, not what's forbidden. You will always forget to forbid something. You are much less likely to accidentally allow something you never listed.
I wrote a test that seeds a value under a mis-cased key and a value under an orphaned field, then asserts neither shows up in the public response. It's a small test. It's also the one I'd point to if a client asked me to prove the API doesn't leak.