Pattern 03 · Toxic data joins
Neither table was sensitive. The join was.
Every access decision was individually correct. An agent had approved access to one table and approved access to another. Combining them produced something nobody would have approved — and nothing in the path was evaluating the combination.
The risk lives between the tables.
An agent is granted access to an HR table — names, departments, reporting lines — because that is reasonable for the task it was built to do. Separately, it can reach a table carrying personal identifiers or compensation, also for a defensible reason. Then someone asks a question in plain language that requires both, and the agent writes the join.
The output is a dataset nobody authorized, assembled entirely from access that was authorized. No rule was broken. That is what makes this pattern hard to catch and easy to repeat.
Approved plus approved
Both grants pass an access review. Reviewed separately, which is how access reviews work, neither looks wrong.
One step, not a project
What used to require a determined analyst and deliberate effort is now one question and one generated statement.
Best at finding, worst at flagging
Discovering non-obvious relationships is what these systems are good at. Recognizing that one is sensitive is what they are bad at.
Access control evaluates one table at a time.
Almost every access model in an enterprise asks "may this caller read this table." None of them ask "may this caller read these two together." The question does not exist in the model, so the answer cannot either.
Governance that stops at the tool name is worse still: it sees a permitted query tool being called by a permitted caller, and has no visibility into what the statement actually reaches.
Evaluate the statement, not the tool.
This is the pattern FaburAI addresses most directly, because the decision point reads the query rather than routing it.
The statement is parsed
Every table and column the query touches is extracted — through joins, common table expressions, nested subqueries and union arms — not just the tool being called.
Each target is evaluated
Policy runs against every target independently. The permitted table returns allow; the restricted one returns deny.
The results are combined
Deny takes precedence over warn, audit and allow. One denied target denies the entire statement — most restrictive wins.
# One rule, on one table. Three different outcomes.
SELECT * FROM employees → allow
SELECT * FROM compensation → deny
SELECT e.id FROM employees e
JOIN compensation c ON c.emp_id = e.id → denyThe third case is the one that matters. The agent never called a denied tool — it called a permitted one, with a statement that happened to reach a restricted table.
Columns are governable targets
Policy reaches individual fields, not just tables, so a rule can permit a join on employee identifiers while denying the salary column inside the same statement.
Sensitivity is a condition
Labels applied at discovery — PII, PHI, GDPR, SOX, HIPAA — can be used directly, so a rule can deny any statement touching personal data without enumerating every column it might mean.
Where this control ends.
- We govern the tool call. An analyst who exports two individually permitted extracts and joins them in a notebook has not passed through a governed call, and we do not see it.
- We do not classify your data for you. Sensitivity is inferred at discovery and is a starting point, not a substitute for your own classification. Where you have existing labels, we would rather read them than re-derive them.
- We do not judge intent. A rule denies a combination because someone decided it should be denied. Deciding which combinations are toxic remains a human judgment about your data.
Bring two tables that should never meet.
This is the fastest capability to evaluate concretely — a rule on one table, and a join that tries to route around it.

