Last week I was fixing a soft-delete bug in our own platform.
The bug was a small logic error in how deletions were being propagated through our data layer. Nothing dramatic. I paired with my AI coding assistant to think through the fix.
It came back with a clean, plausible proposal. Remove the delete trigger from the base table, add a different kind of trigger on the view that sits in front of it, route everything through a helper function. Clean architecture. Elegant. Would have fixed the bug.
Something felt off. I asked one specific question: "Would this change affect row-level security?"
The assistant thought about it. Then it replied:
"Good catch. My earlier proposal would have bypassed row-level security - the helper function runs with elevated privileges, so it would have executed without the invoker's security context. Here's the corrected plan..."
So: the fix would have worked for the bug. It would have also silently disabled access control on every delete in our database. The safeguard that stops a user from deleting a record they shouldn't touch - gone. Not visibly. Nothing in the UI would have changed. Our integration tests would have caught it eventually - but "eventually, after a PR is merged" is a very different safety net from "structurally impossible to ship".
The only reason I spotted it in real time is that I knew enough about our security model to ask the one question that mattered.
The non-expert doesn't ask the question
If I'd been new to the codebase, or a business analyst assembling an app with AI help, or an engineer trained on a different stack - I would have accepted that proposal. It looks reasonable. It fixes the bug. There's no warning sign.
The AI wasn't wrong about how to fix the bug. It just wasn't thinking about the thing it wasn't asked about. That's the failure mode of every AI coding assistant I've used - not a specific vendor, not a specific tool. They optimise for the local problem. Cross-cutting guarantees live outside their attention unless you raise them.
This is fine for a weekend project. It is structurally dangerous for anything that holds data your business cares about.
The usual response isn't good enough
"So review more carefully. Write more tests."
Yes. And - that's an answer that only works on teams with deep expertise in the specific platform they're working on. Enterprise software gets built by teams that rotate, get distracted, include junior folks, come and go. "Be more careful" isn't a design principle. It's a hope.
The real fix is to build your platform so security isn't something a code change can accidentally turn off.
- If your permissions are scattered across application code, any refactor that touches that code can silently rewrite who can see what.
- If your audit trail is a logging decorator on some methods, deleting the decorator deletes the audit trail.
- If your multi-tenancy is a
WHERE tenant_id = ?in your queries, any query missing that clause is a tenant leak. - If your row-level security is a pair of triggers, swapping one for an "equivalent" function - as the AI suggested to me - can remove the protection without anyone writing the words "remove security".
In each case, security lives at the layer where code changes happen. So code changes can - and eventually will - disable it.
The alternative is to move security below application code. Into structure. Into configuration. Into database policies the AI can see the shape of but can't meaningfully rewrite without visibly overriding the platform itself.
How xMS is built around this
That's the whole design principle.
- Permissions aren't code - they're a matrix. The matrix compiles down into database policies, but the matrix is the source of truth. An AI helping build your app can't refactor away your delete security, because there isn't a piece of code to refactor. There's a cell in a matrix, and that cell is what's enforced.
- Audit trails aren't a logging decorator - they're structural, generated on every row change automatically. You can't accidentally remove them; there's no "remove them" code path to accidentally touch.
- Multi-tenancy isn't a
WHEREclause - it's a PostgreSQL row-level security policy applied to every query at the database layer. Not forgettable, not bypassable by a query that forgot to include a filter.
We catch the near-misses in our own codebase because we wrote the platform and we know where the tripwires are. Most teams don't have that privilege. xMS moves the tripwires into the platform so that our customers can't trip over them, regardless of who - or what - is writing the code.
Why this matters if you're vibe coding for enterprise
Vibe coding is a real productivity multiplier. We use AI assistants constantly; most of this blog is written with one at my elbow. We're not in the business of telling you to stop.
We're in the business of saying: when the thing you're building has to be secure - when regulators will inspect it, when users' personal data lives in it, when one wrong delete breaks your audit trail - the right layer to put the guardrails is below the code the AI is writing.
Vibe coding gives you speed.
Vibe coding with guardrails gives you speed and the security model you thought you were deploying.
One question from one experienced engineer caught the near-miss in our codebase.
One question from one engineer is not a security model. The platform is.