Your agent's permissions drift silently. A PR adds an MCP server with write scope, or allow: Bash(*) lands in .claude/settings.json while the runbook says "a human approves". Tests don't catch it. Linters don't catch it. Here's a 5-minute gate that does.
Step 1: declare the promise
Create agent-assurance.yaml in your repo — what the agent may do, in plain config:
# agent-assurance.yaml (simplified)
capabilities: [read] # no write, no execute, no external send
data: [internal] # no customer data, no credentials
autonomy: L2 # a human approves actions
Step 2: scan locally
pipx install agent-assurance
agent-assurance scan .
You get the observed blast radius plus a "declared vs observed" verdict. Now break it on purpose — add an MCP server with write scope — and watch it point at the file and line.
Step 3: gate the PR
# .github/workflows/agent-assurance.yml
permissions: { contents: read, pull-requests: write }
steps:
- uses: actions/checkout@v4
- uses: kunko-ai-labs/agent-assurance@v0.5
with:
mode: diff
manifest: agent-assurance.yaml
Every PR now gets one comment, updated on every push, plus a red check when the promise breaks. On push/release, switch to mode: scan for SARIF findings in the Security tab and in-toto attestations.
Why this shape
Two checks: AA-001 blast radius (if it misbehaves, how much breaks?) and AA-002 declared vs observed. Unknown servers come out UNKNOWN — scored conservatively, never a silent pass. An inferred guess (e.g. a tool named billing.issue_refund → financial) only ever triggers review, never fails a build.
It's deterministic — no LLM in the verdict — makes no network calls, and never reads a secret's value. Every release ships Sigstore-signed SLSA provenance, and the whole thing is Apache-2.0: https://github.com/kunko-ai-labs/agent-assurance
Try breaking it. That's the point. 🧪
Top comments (2)
The declared vs observed distinction is the part I’d pay the most attention to. A permission file tells you what someone intended to authorize; inspecting the actual MCP/tool configuration tells you what the agent can really reach. Those are two different security properties.
I also like treating
UNKNOWNas a first-class state rather than silently converting uncertainty into a pass. That pattern matters well beyond MCP especially for agent systems where new tools and capabilities can appear through configuration changes.At IT Path Solutions, this same separation is useful when hardening production agent workflows: keep the policy deterministic and outside the model, then make CI continuously verify that the executable capability surface still matches the declared contract.
The nice part is that this turns permission drift into something engineering teams can catch before deployment, rather than discovering it through an incident.
Thanks Mateo — "two different security properties" puts it better than anything in the post. Stealing that!
Curious: what's been the hardest capability surface to observe on your side? For me it's inferred tools — a tool named
billing.issue_refundthat probably touches money but declares nothing. That's exactly why UNKNOWN exists as a state rather than a guess.