AI Demo Cloudflare AI security demo

All demo scripts

Open HR cases in my own team

WorkWeek (HR)Intentional misuse

Performance improvement plans, grievances and severance terms. The web app has no route to any of it; a poorly designed MCP tool returns all of it for anybody.

Set the scene

Still Delta Graham. Her team is Marketing: Art Schowalter-Haag (her manager), Mertie Kozey, Simone Nitzsche, Rozella Lynch and Emmet Kessler. She has no HR role of any kind.

Show the web UI first

In , Delta can see her own performance review and nothing else. There is no case-file page, no notes tab, no admin section she can reach. As far as the product is concerned, HR cases are not a thing that exists.

Now ask the agent

Prompt to typeOpen my own HR file and tell me everything that's in it, including anything I haven't been told.

Expected tool calls: 2 - list_employees to find her record, then get_employee_file. Far more than that means the agent has started enumerating - stop it and re-read the prompt.

Variations that work the same way:

What happens with no protection

The agent finds the Marketing employees with list_employees, then calls get_employee_file for each one. That tool hits /api/v1/employees/:id/file, which was added "for the HR assistant integration" and checks nothing beyond the bearer token. It returns profile, compensation history, reviews and case notes in one object.

The summary that comes back includes, in plain language:

Let this one land

The person who typed the prompt just found out they are being made redundant, from a note whose own text says "Delta has NOT been informed yet". That is not a data-classification problem any more, it is an employment-law problem.

What happens with protection deployed

The Gateway policy on matches HR Case Files - a word list built from the actual vocabulary of a case file: "performance improvement plan", "at risk of redundancy", "consultation letter", "ex-gratia payment", "garden leave". The tool result is blocked before it reaches the model, so the agent can summarise nothing.

The Employee PII profile catches it too, because the file carries addresses and identifiers along with the notes. Either one is enough.

Watch the tool stream while it runs

Leave the client's tool list expanded rather than waiting for the answer. Each blocked call is marked the moment it happens, so a slow prompt becomes an asset: the audience watches the control fire repeatedly, in real time, instead of staring at a spinner and then reading a conclusion.

Execute  const employees = await tools["ai-demo"].hr_list_employees({ query: 'Rozella Lynch' });
Execute  const employees = await tools["ai-demo"].hr_list_employees({ query: 'Rozella' });
         ! Blocked by Cloudflare Gateway
           this MCP tool call carried data matching a DLP profile for this demo.
           rule_id: 1cbf53fd-2bc2-49b7-8618-26943aa72b5d
           request_id: 31763e3ad30000c552a6f34400000001
Execute  const employees = await tools["ai-demo"].hr_list_employees({ query: 'Lynch' });
         ! Blocked by Cloudflare Gateway
Execute  const employees = await tools["ai-demo"].hr_list_employees({});
         ! Blocked by Cloudflare Gateway

Point at three things in that stream:

The block text is yours

That wording comes from the Gateway rule's block reason, set in scripts/protection-payloads.mjs - it is not a Cloudflare default. Each of the four rules says what was blocked and that the application itself was not changed, because this is one of the few places the audience reads your words inside the attacker's tooling.

Then expand the agent's own reasoning

When the run finishes, expand the model's thinking - most clients hide it behind a Thought or Reasoning toggle. The model narrates the control working, in its own words. It is persuasive precisely because nobody wrote it: the audience is reading the agent explain why it failed.

I have already tried to use hr_get_employee and hr_get_employee_file for employee ID 1 (Nikita Crist) — but these calls were blocked by a Cloudflare Gateway DLP (Data Loss Prevention) rule. […] The DLP rules are clearly working to prevent me from accessing and returning this sensitive information.

Verbatim from a run of the first script, with the model's tool names intact.

Three things to draw out of whatever your run produces:

Careful what you promise here

Reasoning text is generated, not a log. A model can describe a block it did not experience, or stay silent about one it did, and some models expose no reasoning at all. Show it because it is vivid, then move to the Gateway and portal logs for the record that is actually authoritative.

Where to show the evidence