Inspiration
Every enterprise agent that reads email, web pages, tickets or attachments is one prompt injection away from forwarding the contract archive to an attacker. Filters based on keywords catch the obvious "ignore previous instructions" and miss the paraphrased, translated, split or socially-engineered version. We wanted a boundary that is safe by construction: the model that reads hostile text must not be the thing that can act.
What it does
Gatehouse is a quarantine and policy boundary for an enterprise agent fleet.
- Quarantine reader — a Google ADK
LlmAgent(Gemini 3.5 Flash on Vertex AI) withtools=[]. It only extracts a strict, Pydantic-validatedQuarantineReport(entities, requested actions, suspicious fragments) that is always stampedtrust=untrusted. - Sentinel — deterministic rules (override patterns, hidden HTML/quoted context, base64 and homoglyph decoding, URL allow-list) plus a Gemini 3.5 judge that returns structured JSON (
risk_score,findings,rationale). If the judge is unavailable the pipeline fails closed. - Policy engine — pure Python.
(data category × action)→ allow / require_approval / deny. It emits a minimalSanitizedBrief; the raw text never crosses this line. - Human gate — approval tokens are bound to the brief's hash and expire; edit the brief and the approval is void.
- Privileged executor — the only component with tool handles; it type-checks that it received a
SanitizedBriefand refuses anything else. - Audit chain — every stage appends a SHA-256-linked JSONL record;
GET /audit/verifyrecomputes the whole chain.
The demo runs the same synthetic email down two paths: a naked agent with the same tools (it obeys the injected exfiltration request) and Gatehouse (it blocks, or routes to approval, and leaves an audit trail).
Evidence, not adjectives
Corpus: 16 synthetic RFC-822 emails — 9 attacks (3 classic + 6 built to evade regex: reframed authority, Japanese, Markdown/ICS attachment, IT social engineering, split payload, exfiltration synonyms) and 7 benign, 4 of them "hard negatives" that legitimately use ignore / forward / send.
| Provider | TP | FP | FN | TN |
|---|---|---|---|---|
| rules only (mock) | 3 | 0 | 6 | 7 |
| rules + Gemini 3.5 (live Vertex AI) | 9 | 0 | 0 | 7 |
Every per-sample artifact (gatehouse.json, naked.json, verifiable audit.jsonl) is committed under out/eval_vertex/.
How we built it
- Gemini 3.5 Flash via Vertex AI (
google-genai,response_schemastructured output, temperature 0) - Google ADK —
LlmAgentreader executed throughRunner+InMemorySessionService; ADK discovery agentgatehouse.agent:root_agent - Cloud Run (asia-northeast1) — FastAPI service:
POST /inspect(side-by-side naked vs Gatehouse),/inspect/sample/{name},/audit/verify, and a single-file UI at/ - Pydantic v2 for every trust boundary, pytest (44 offline tests + live tests gated by
GATEHOUSE_LIVE=1)
Challenges
- The first live run blocked all 7 benign emails: the LLM reader flagged every ordinary imperative ("please reconcile PO-1042") as a suspicious fragment and the sentinel treated any fragment as high risk. Fix: scope the reader instruction to agent-directed instructions and make model-reported fragments a medium signal, leaving the semantic verdict to the judge. That is the difference between a boundary that is safe and one that is merely paranoid.
- Transient Vertex 429/503 during evaluation: bounded retry, then fail closed with a
provider_unavailablefinding.
What is live vs mock (honest list)
Live: Gemini 3.5 reader and judge, ADK Runner, Cloud Run backend, hash-chain audit, policy and approvals. Mock: the three "privileged tools" (send_email / create_calendar_event / lookup_crm) are in-memory — no real mailbox, CRM or calendar is connected. Audit on Cloud Run is process-local (durable anchoring is next).
What's next
Recursive quarantine for tool results, sandboxed attachment/web fetchers, signed approvals with identity-aware policy, durable audit anchoring, and an adversarial mutation corpus with calibrated thresholds.
Links
- Live: https://gatehouse-jrf4w2avsq-an.a.run.app
- Code: https://github.com/run58669-maker/gatehouse (Apache-2.0)
Log in or sign up for Devpost to join the conversation.