Skip to contentAgent? Read agent.txt
All posts
Engineering

Scanning MCP servers in CI: the shift-left pattern for agent security

Pull request scanning catches poisoned tool descriptions before a developer installs them. Here's the GitHub Action that makes it work.

DecoyEngineering

Security that only runs on a security team's laptop is security that misses three weeks per hire. If you care about MCP security, the fastest way to raise the floor is to scan every PR that adds or changes an MCP server.

The action

# .github/workflows/mcp-security.yml
name: MCP security
on: [push, pull_request]

jobs:
  scan:
    runs-on: ubuntu-latest
    permissions:
      security-events: write
    steps:
      - uses: actions/checkout@v4
      - uses: decoy-run/decoy-scan@v1
        with:
          policy: no-critical,no-poisoning,no-toxic-flows

Drop that in .github/workflows/mcp-security.yml and every PR gets decoy-scan run against any MCP server configs touched. The default policy fails the build on critical tools, poisoned tool descriptions, and toxic data-flow chains. SARIF output lands in the PR's Security tab.

What actually runs

decoy-scan is static analysis, not a runtime probe. It reads every .mcp.json it can find, classifies each tool's risk from its name and description, and layers on analyzers:

  • Tool risk classification — critical (code exec, file write, secrets), high (network, read), medium, low
  • Prompt-injection detection — 39 patterns across instruction override, role hijack, concealment, coercive execution, cross-tool reference
  • Toxic flow analysis — TF001 data leak (untrusted input → private data → public sink) and TF002 destructive (untrusted input → irreversible op), computed across every server in the config
  • Manifest hashing — fingerprint each tool set and diff against the last scan. New tools, removed tools, silently-edited descriptions all surface
  • Skill scanning (--skills) — prompt injection, hardcoded secrets, suspicious URLs inside Claude Code skills
  • Supply-chain advisories — 40+ known-vulnerable MCP packages via Decoy's advisory database
  • Transport, env, and sanitization checks — HTTP without TLS, wildcard CORS, unconstrained params, missing maxLength

Every finding maps to the OWASP Top 10 for Agentic Applications (ASI01–ASI05).

Why no-critical,no-poisoning,no-toxic-flows is the right default

Every scanner has a false-positive ceiling. We tune the critical bucket to mean don't merge this: code-executing tools, tools with poisoned descriptions, and cross-server chains that exfiltrate. Everything else ships to the Security tab for review but doesn't block the merge.

Tighten later with max-critical=0, max-high=0, or require-tripwires. Business-tier teams can ship custom detection rules that enforce org-specific policies (banned capabilities, required descriptions, allowed transports).

What this buys you

A shift-left loop for agent security. Your developers learn what a poisoned tool description looks like because GitHub explained it to them on PR #247, not because a CISO emailed them three weeks later. That's the only way this scales.

Full docs: decoy.run/docs/scan/overview.