The AI that walked around your row-level security
The database enforced who could see which rows. The AI feature queried through a service account that could see all of them, and the row-level rules never got a chance to run.
The AI that walked around your row-level security
A database engineer at a multi-tenant company told me about a control they trusted that turned out to have a hole in it. Their database enforced row-level security, so each user could only see the rows they were entitled to, even if a query tried to reach further. That control was solid, and it had protected them for years. Then the AI feature came along and queried the database through a service account, because that was the straightforward way to build it. The service account was not subject to the same per-user rules. It could see every row. So every question that went through the AI feature ran with access to the entire table, and the row-level security that guarded everything else simply never got a chance to apply.
Row-level security is powerful and it has one requirement. The query has to run as the user whose access should be enforced. The moment a query runs as a broad service identity instead, the row-level rules are evaluated against that broad identity, which can see everything, and the protection evaporates. The control did not fail. It was bypassed by running the query as the wrong actor.
A control only works where the request flows through it
Access controls are placed on a path. Data goes through a checkpoint, and the checkpoint decides what the caller may see. Row-level security is a checkpoint at the database. It works beautifully as long as requests arrive carrying the identity it is meant to check.
AI features tend to arrive carrying a different identity. To keep the build simple, the feature queries through one service account with broad access, and the user’s identity gets left at the door. The database dutifully applies the row-level rules to the service account, which is allowed to see everything, and returns everything. The AI feature then answers using rows the actual user was never entitled to, and no rule was broken along the way, because the rule was checked against the wrong identity.
This is the same shape as an over-privileged assistant, seen from the data layer. The permission system is intact. The request just went around it by wearing a badge with far more access than the person behind it.
Keep the user’s identity in the query path
The fix is to make sure the access check runs against the person actually asking, all the way down to where the data lives.
In practice:
- Query as the user, not as a blanket service account. Carry the caller’s identity into the database so row-level security evaluates against their real permissions.
- If you must use a service account, filter to the caller first. Apply the user’s access constraints in the query itself, so the broad identity only ever returns what the user could see.
- Do not enforce data access in the prompt. The rule that a user may not see certain rows has to run at the data layer, not as an instruction to the model that a clever request could sidestep.
- Test that the boundary holds through the AI path. Have a limited user ask the feature for data they should not have, and confirm they cannot get it.
The teams that get this right make sure their existing access controls stay on the path the AI feature uses, rather than letting the feature take a shortcut around them. The controls you already built are good. They only protect you where the request still flows through them.
How we approach it at Density Labs
In the AI Opportunity Assessment, our fixed two-week, $2,500 engagement, we check whether the AI feature preserves your existing access controls or routes around them. When a feature queries through a broad service identity, we map what row-level and tenant boundaries stop being enforced, and we design the fix before it ships. A strong access control that the AI feature bypasses is a strong access control you no longer have.
Row-level security only protects you when the query runs as the user. Keep the caller’s identity in the path, or your AI feature becomes the way around every rule you built.