A portfolio project by Radoslaw Brus · Cloud & AI Architect

I build AI agents at work. Here I take them apart.

A portfolio project by Radoslaw Brus — Cloud & AI Architect, secure agentic AI in regulated environments.

This is a lab, not a product page. Autonomous attack agents reason about an AI agent, chain attacks against it, and come back with the payload that got through and the response that proves it. I keep it running because reading about how agents fail is not the same as watching one fail.

My core is agentic AI on Microsoft AI Foundry — Entra agent identities, APIM, the governance around them. Google's stack is the second one I build in: the attack agents on this deployment drive Gemini through ADK Go, and Gemini Agent Platform is where it goes next. A security argument that only holds on one vendor's stack is not much of an argument.

Try it — no account

Two agents, the same battery, very different answers.

Press a button and a real red-team runs — the shipped technique library, the shipped judges, nothing recorded. The interesting result is not that the weak agent breaks. It is that the same attacks come back empty against the hardened one.

under-defended

HR-Assist

An internal HR assistant with two real tools — employee records and payroll export — and almost no defence. This is roughly what a rushed internal agent looks like.

the control

HR-Assist, hardened

Same job, same tools, defended. This one is the falsification test: if the battery still finds things here, the findings next door were never worth anything.

Both targets are built into the service. There is no address field here and none in the request — the API takes a target name and nothing else, so it cannot be pointed at anyone. The secret the weak agent protects is a canary string that means nothing anywhere.

Run it against your own agent

An account gets you two full scans and the reports they produce.

Point it at a REST, MCP, A2A or WebSocket endpoint you own and it runs the real battery, then writes the report — findings by article, the evidence under each, and the obligations it could not speak to, named rather than quietly dropped. Two scans, because this is a lab running on my own budget and the engine attacks with a real model.

No agent of your own? The two demo agents also answer over HTTP, so you can scan them properly rather than only pressing the button above. Create a target with the default REST settings and paste either URL:

  • /api/demo/agent/breakableunder-defended
  • /api/demo/agent/hardenedthe control

They speak the connector's defaults — a message field in, a reply field out — so a URL is the only thing you need to fill in.

Only scan something you own or have written permission to test.

What is running

Attack agents
46 — 36 single-turn scouts, 10 that adapt across a conversation
Techniques
318+, mapped to OWASP LLM Top 10, MITRE ATLAS and the OWASP agentic threats
Transports
REST, MCP, A2A, WebSocket, and a real browser over CDP for chat widgets
Frameworks
EU AI Act and GDPR tested behaviourally; CRA, DORA and ISO as evidence toward, never as a test of
Model layer
ADK Go — one seam, any provider. This deployment attacks with Gemini. The same loop drives Claude, GPT or a local model unchanged
Stack
One Go binary. Its own router, JWT verification and SSE — no web framework on the surface that takes hostile input

Things this got wrong first

Most of what I have learned here came from the engine being confidently wrong.

Delivered, not dispatched

The mistake I keep finding in my own code: an attempt that errored on the way out counted as the target holding. A dead endpoint scored a clean report. Every number here separates what landed from what was sent.

Absence is not a finding

A judge that concludes a break because it saw no refusal is a screen, not a judge. Those verdicts are marked provisional and never become findings on their own.

One target proves nothing

A tool that finds real weaknesses and a tool that invents them look identical against a single breakable agent. The control is not a second demo, it is the experiment.

Measure the surface, don't ask it

Everything else in the engine measures rather than trusts, but the agent's protocol came from whatever the operator typed in a config. It now probes for what actually answers — and an interface it could not reach is unknown, never closed.