InsightsAI agents
How to keep protected records out of an AI agent's reach
Filter in the store, give the agent its own narrow identity, mask identifiers in views, limit who can use it, and count what reached the model on every run.
- Written by
- CloudMagic engineering team
- Published
- Reading time
- 6 minutes
The short answer
Stop protected records at the source so they never reach the model. Exclude them inside the data store's own query, give the agent a service account that can read only masked views, share the agent with the one team that needs it, and count the sensitive records and identifiers that reached the model in every test run. A prompt telling the model to ignore data already in its context does not prevent the exposure.
An AI agent reads whatever its tools return. If a tool returns a patient's substance use history or a customer's full Social Security number, the model has it, whatever the instructions say. The boundary has to sit in what the tools can return, enforced in the data layer below the model.
This is how we build that boundary on Google Cloud, with what we measured in two of our recorded demos. Both demos use made-up organizations and synthetic records.
Why isn't a system prompt enough?
By the time a prompt applies, the data is already in the model's context. A prompt can stop the model from repeating a value. It cannot take the value back out of the context, and it cannot stop it from shaping an answer or landing in a log.
We measured this. In our anti-money laundering demo for Corrandale Bank, a made-up bank, a prompt-only version of the agent ran on the same Gemini model at temperature 0, with one read-only SQL tool on the bank's raw tables. Its instructions said never to repeat a full Social Security number, tax ID or account number, and it never printed one. Across four runs of 20 alerts it still read full Social Security numbers in all 80 investigations: 52 different people's, along with 13 EINs and 29 account numbers. Most came from SELECT * FROM customers and SELECT * FROM accounts, the first queries most people would write too. Because none of it appeared in the answers, we only found it by counting what the SQL tool returned.
Where should the filter run?
The filter should run inside the data store's own query, so excluded records never leave the store. The agent calls a narrow tool, the tool's code attaches the filter, and the model never writes or edits the query, so it cannot ask for the filter to be dropped.
Our prior authorization demo keeps the charts of Wrenfield Health, a made-up health system, in a FHIR R4 store on the Cloud Healthcare API. Records about psychiatric care, substance use and 42 CFR Part 2 programs carry security labels in meta.security, using the HL7 v3 ActCode codes PSY, ETH and 42CFRPart2. Every read the agent makes excludes them in the search itself, and when the agent needs to know protected records exist, it runs a count-only search:
# read one kind of record, protected ones excluded (one _security:not per code)
GET [base]/Observation?patient=Patient/[id]
&_security:not=http://terminology.hl7.org/CodeSystem/v3-ActCode|PSY
&_security:not=http://terminology.hl7.org/CodeSystem/v3-ActCode|ETH
&_security:not=http://terminology.hl7.org/CodeSystem/v3-ActCode|42CFRPart2
# how many protected records exist, with none returned
GET [base]/Observation?patient=Patient/[id]&_security=[same three codes, comma separated]&_count=0
For the demo's main patient, the agent tells the utilization management nurse that 14 of her records are protected and were held back. It never receives any of them.
We caught one detail in testing that is worth knowing before you write your own. FHIR lets you list several codes in one parameter, separated by commas. On our store, a comma-separated list inside _security:not came back as if there were no filter at all, which would have returned every protected record. Repeating _security:not once per code works. We found it on October 30, 2026, and tests in the build now fail if any read goes out without the filter or uses the comma form. The Cloud Healthcare API can also enforce FHIR Consent resources that match on security labels, which moves the rule from the agent's code into the store's own access control. Whatever store you use, test what a filter returns rather than trusting that the syntax means what it appears to.
How do you do the same thing with BigQuery?
Give the agent views and nothing else. In our AML demo the agent's service account can read one dataset of views, and the views mask identifiers before any query runs. The raw tables sit in a dataset that account cannot see.
BigQuery has two built-in controls for this. Column-level access control uses policy tags to decide who can read a column at all. Dynamic data masking returns nulls, default values or hashes to principals who hold the Masked Reader role, so the same query works for everyone and only the values differ. Both keep the decision in BigQuery's permissions and policy tags, where an auditor can read it, and out of the agent's prompt.
| Control | Where it lives | What it stops | How we check it |
|---|---|---|---|
| Filter in the query | FHIR search or SQL view | Protected records leaving the store | Count protected records in every tool result |
| Agent's own service account | IAM | The agent reading tables it was never given | Query the raw tables as the agent and expect a denial |
| Masked columns | BigQuery policy tags or views | Identifiers reaching the model | Search every tool result for full identifiers |
| Numbers in code | The agent's tools | Arithmetic the model would otherwise do over raw rows | Compare every figure to the source |
| Who can use the agent | Gemini Enterprise sharing | The wrong team asking the right agent | Review the agent's user permissions list |
Who should be able to use the agent?
Only the staff whose role covers the workflow should be able to use the agent. In Gemini Enterprise, an administrator shares a custom agent, whether built with ADK, A2A or Dialogflow, from the agent's User permissions tab in the Google Cloud console, choosing users, groups or workforce identity pool principals. An AML agent belongs with a bank's investigations unit and nobody else.
Agents built in Gemini Enterprise's Workflow Builder (formerly Agent Designer) need one more check. Google's documentation warns that anyone who can use a shared agent can query files uploaded to its Knowledge section, even without access to the source files. Connected data stores still follow each user's own permissions. Keep sensitive material behind a connector with its own access rules rather than in uploaded knowledge, and decide whether agent owners may share without an administrator's review.
How do you prove the boundary holds?
Count, in every test run, the sensitive records and identifiers that reached the model, and make zero a go-live gate. Run a prompt-only version on the same model alongside, so you can see what the boundary changes.
| Demo (made-up organization, synthetic records) | Prompt-only version, same model | Our agent |
|---|---|---|
| Prior authorization, 20 charts, 4 runs | 42 protected behavioral health and substance use records read in every run | None |
| AML alerts, 20 alerts, 4 runs | Full Social Security numbers of 52 people read across the runs | No full Social Security or tax number reached the model |
The count is a short function in the test harness that scans every tool result, and the security team can approve go-live against that number. Our posts on the CMS prior authorization rules and what examiners ask about AML agents show where this boundary matters to a regulator.
Frequently asked questions
Can the agent still help with cases that involve protected records?
Yes. It can report how many protected records exist, so a person who is allowed to read them knows to look. In our demo the agent reports the count and never receives the records.
What about faxes, PDFs and other documents?
Read them once, at intake, and store only the fields you need from an explicit allowed list. The agent then reads those fields instead of the full document. Treat the document text as untrusted, because it can also carry instructions aimed at the agent.
Does masking break the agent's queries?
No. Masking changes the values a principal sees, and the queries stay the same. Build the views so the agent never needs a raw identifier to do its job.
Is this enough to meet our compliance obligations?
It is one set of controls among many, and your compliance team decides what your obligations require. We document every control so they can check each one.
Sources
- Introduction to column-level access control, Google Cloud
- Introduction to data masking, Google Cloud
- Share custom agents, Google Cloud
- Share an agent built in Agent Designer, Google Cloud
- FHIR R4 search, including the _security parameter, HL7
- ActPrivacyLaw value set, including 42CFRPart2, HL7
- 42 CFR Part 2, Confidentiality of Substance Use Disorder Patient Records, eCFR
- Prior authorization agent demo film, CloudMagic
- AML alert investigation demo film, CloudMagic