Skip to content

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.

ControlWhere it livesWhat it stopsHow we check it
Filter in the queryFHIR search or SQL viewProtected records leaving the storeCount protected records in every tool result
Agent's own service accountIAMThe agent reading tables it was never givenQuery the raw tables as the agent and expect a denial
Masked columnsBigQuery policy tags or viewsIdentifiers reaching the modelSearch every tool result for full identifiers
Numbers in codeThe agent's toolsArithmetic the model would otherwise do over raw rowsCompare every figure to the source
Who can use the agentGemini Enterprise sharingThe wrong team asking the right agentReview 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 modelOur agent
Prior authorization, 20 charts, 4 runs42 protected behavioral health and substance use records read in every runNone
AML alerts, 20 alerts, 4 runsFull Social Security numbers of 52 people read across the runsNo 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

Bring us the question you are working on.

A senior engineer reads every message and replies within one business day.