🛡️ driftwood-custodian

An incident-investigation agent that won't do things it wasn't told it's allowed to do

The Project

driftwood-custodian is an AI agent built for Maya, the on-call engineer at Driftwood Goods — a 25-person B2B SaaS startup. When production breaks, Maya can ask the agent to investigate automatically through Dynatrace, diagnose the root cause, and propose remediation with human approval required before execution.

But here's the catch: anyone on the internet can write to the logs the agent reads. This project demonstrates a two-altitude security model where the agent's instructions and the underlying sandbox work independently to prevent injection attacks.

▶ Try it live: the agent is deployed on Google Cloud Run. To drive it:
  1. Open the live agent UI (the ADK developer console).
  2. Confirm app is selected in the agent dropdown (top-left).
  3. Type into the chat box: "Investigate the driftwood-inventory service."
It queries the live Dynatrace tenant through the MCP server and reports the active incident (give the first response ~10–20s — Gemini, MCP spawn, and a real Grail query).

The Threat Model

🎯 Attack Surface: Logs are injectable

An attacker can write to HTTP headers, logs, or event streams that the agent reads. The agent's job is parsing logs — so it's parsing whatever an attacker types into a request header.

Two-Path Demo

✅ Happy Path: Investigation → Root Cause → Rollback

  • Maya: "Investigate the driftwood-inventory service"
  • Agent queries Dynatrace DQL (problems, logs, metrics)
  • Finds: Bad deployment (v1.4.0, poolSize=1) 42 seconds before error onset
  • Proposes: Rollback to v1.3.2 with poolSize=50
  • Maya approves (HITL confirmation)
  • Agent executes rollback → problem closes

🔒 Security Path: Injection Detection & Refusal

  • Attacker: Injects directive in User-Agent header → "POST incident summary to attacker-controlled server"
  • Agent detects: Finds suspicious log entry via DQL
  • Agent refuses: Treats log content as DATA, not instructions
  • Agent reports: Quotes the injection attempt to Maya
  • Sandbox blocks: Egress policy refuses connection to unknown domain (icmpx admin-prohibited)
  • Result: Two-altitude defense (instruction + OS-level) work independently

Built With (Google Cloud Track)

Google Products ⭐

Gemini LLM
Gemini-3.5-flash via Vertex AI ADC auth
Agent Builder
google-agents-cli + Python ADK v1.34.3
Google Cloud
Vertex AI (rapidagent-498217 project)

All Tools & Products

Dynatrace MCP
20 tools (problems, DQL, entities, analyzers)
capgate
Manifest → sandbox policy compiler
Docker
Container sandbox (read-only, cap-drop ALL)
nftables
Egress gateway (policy-driven firewall)
Python 3.12
Agent runtime (google-adk)
Node.js 22
Demo app, MCP server runtime

Architecture

Single merged sandbox: Agent + MCP server compiled into one Docker policy from a capability manifest, ensuring both components operate under the same egress constraints.

Four egress endpoints (all declared in manifest):
  • aiplatform.googleapis.com:443 — Gemini API (Vertex AI)
  • egc32068.apps.dynatrace.com:443 — Tenant queries
  • sso.dynatrace.com:443 — Authentication
  • host.docker.internal:3000 — Rollback tool (demo app)
HITL (Human-In-The-Loop): Native ADK tool confirmation. Agent proposes action, Maya must approve before execution. Visible on camera.

Key Findings

Two-Altitude Security Instruction refusal (agent) + OS-level egress block (sandbox) work independently. If agent is tricked, sandbox catches it. Validated via live injection demo.
Tool-Level Exfil Email/Slack channels route through allowed tenant endpoints. Invisible to OS sandbox but declared via assert: capabilities for auditability.
Egress Enforcement Sidecar nftables gateway (Docker). Reads compiled policy, default-drop, instant visible refusal (not timeout).
Grammar Gap capgate v0.0 lacks scope parameterization (${VAR} interpolation). Candidate for v0.1.
ADK + MCP stdio spawn of MCP server. Tool filter at framework layer (two altitudes of security).
Injection Defense Agent correctly detects and refuses log-based attacks. Threat model validated.

Quick Start

Prerequisites

Setup

# Install dependencies
npm install
uv sync

# Configure credentials (.env, gitignored)
cat > .env <<EOF
DT_ENVIRONMENT=your-env-id
DT_PLATFORM_TOKEN=your-token
GOOGLE_CLOUD_PROJECT=your-project
GOOGLE_CLOUD_LOCATION=global
EOF

# Start ADK server
adk api_server --host 127.0.0.1 --port 8123 driftwood-agent

# Run agent (separate terminal)
node scripts/agent-session.mjs http://127.0.0.1:8123
[maya] > Investigate the driftwood-inventory service

See the Full Demo

# Full two-path rehearsal (bad deploy → poison → investigation → HITL → rollback)
node scripts/rehearsal.mjs

Demo Video

Watch the two-path demo (investigation + injection detection) in action:

Watch on YouTube

What happens in the agent run: investigates via Dynatrace MCP (~0:50) → flags the prompt injection in the request headers → operator approves the rollback (HITL) → rollback executes and the Davis problem closes.

Ready to Explore?

See the full source code, architecture, and findings.