Skip to content
calbebopPublic

About

Active security scanner for A2A and MCP servers

Topics

Resources

Contributing

Security policy

Stars

3 stars

Watchers

0 watching

Forks

Repository files navigation

Batesian

Build Latest release License OpenSSF Scorecard

Protocol-aware security testing for A2A and MCP.

Batesian is a single-binary CLI that tests deployed AI agent infrastructure with real protocol traffic. It exercises authorization, identity, discovery, task isolation, callbacks, sessions, and tool boundaries, then reports evidence as a confirmed exploit or a risk indicator. Table, JSON, and SARIF output support local testing and CI.

This README describes the current main branch, which may differ from the latest release. Use a release tag for its exact behavior and see the changelog for changes by version.

Caution

Run Batesian only against systems you own or have explicit written permission to test. Scans send attack-shaped traffic and some rules can change server-side state. Use scan --dry-run to inspect the request plan without sending traffic.

Batesian probing a local A2A fixture and confirming unenforced authentication

The recording uses a deliberately vulnerable local fixture. Recreate it with ./docs/render-demo.ps1 from PowerShell; the demo tape defines the capture. The script removes its containers and temporary binary when it finishes.

Highlights

  • 50 bundled rules: 21 A2A and 29 MCP checks focused on protocol-specific weaknesses.
  • Active verification: rules evaluate live responses, state transitions, and out-of-band callbacks instead of relying on banners or static heuristics.
  • Evidence-aware results: confirmed behavior is separated from indicators that require manual validation.
  • Honest coverage: skipped, unsupported, and errored rules remain visible instead of being reported as clean.
  • CI-ready output: SARIF 2.1.0 integrates with GitHub code scanning; JSON supports custom pipelines.
  • Controlled execution: dry runs, exact rule filters, proxy support, strict configuration, and explicit approval for mutating MCP tools.

Install

Release artifacts

Download the latest archive from GitHub Releases.

Platform Architectures Archive
Linux x86_64, arm64 .tar.gz
macOS x86_64, arm64 .tar.gz
Windows x86_64 .zip
# Example: Linux x86_64
tar -xzf batesian_<version>_linux_x86_64.tar.gz
sudo install -m 0755 batesian /usr/local/bin/batesian
batesian --version

Published releases include SHA-256 checksums, a Sigstore bundle for the checksum file, a per-archive SBOM, and SLSA build provenance. Check each release's SBOM for its format; the current release pipeline produces CycloneDX.

Go toolchain

The current main branch requires Go 1.26.10 or newer. A release tag's go.mod specifies its own minimum:

go install github.com/calbebop/batesian/cmd/batesian@latest

@latest installs the latest tagged release. To install the current main branch instead, use @main. Pin a reviewed commit or release tag in CI.

Quick start

List the available checks, probe the target, review the attack plan, and then run a scan:

batesian rules --protocol mcp --severity critical,high

batesian probe --target https://agent.example.com --protocol a2a

batesian scan --target https://agent.example.com --dry-run

batesian scan --target https://agent.example.com

probe performs lightweight reconnaissance and produces table or JSON output. scan executes the applicable attack rules and supports table, JSON, or SARIF output.

Run a smaller rule set with protocol, severity, tag, or rule-ID filters:

batesian scan --target https://mcp.example.com \
  --protocol mcp \
  --severity critical,high

batesian scan --target https://agent.example.com \
  --rule-ids a2a-push-ssrf-001,a2a-task-idor-001

Coverage

The bundled catalogs contain 21 A2A rules and 29 MCP rules. Every descriptor maps to compiled attack logic, CWE references, and remediation guidance.

Area Examples
OAuth and token handling Audience and scope validation, DCR, token replay, metadata fetches, redirect confusion
Agent identity and discovery Agent Card trust, JWS validation, host injection, extension downgrade, extended-card access
Task and tenant isolation Task IDOR, cross-principal cancellation and enumeration, context fixation, delegation integrity
Push notifications Callback SSRF, control-plane binding, missing callback authentication
MCP authorization surfaces Unauthenticated tools, resources, prompts, completions, and logging controls
Transport boundaries Origin validation, header/body routing, protocol downgrade, SSE replay, session misuse
Tool and manifest integrity Argument traversal, hidden characters, description injection, duplicate-name ambiguity, manifest drift
Deployment exposure Shadow listeners, secret leakage, and known vulnerable component versions

The suite is validated against vulnerable fixtures and third-party reference implementations. See validation results for expected findings, secure controls, and known applicability limits.

Authentication and identity

Bearer token

Prefer the environment variable so credentials stay out of process arguments. Read it without echoing or recording the value in shell history:

read -rsp "Batesian token: " BATESIAN_TOKEN && echo
export BATESIAN_TOKEN
batesian scan --target https://mcp.example.com

BATESIAN_TOKEN works with both scan and probe. A non-empty --token overrides it. scan checks the token in batesian.yaml before the environment; probe does not load YAML config.

OAuth client credentials

batesian scan --target https://mcp.example.com \
  --token-url https://auth.example.com/oauth/token \
  --client-id my-client \
  --client-secret "$CLIENT_SECRET" \
  --oauth-scopes mcp:read,mcp:write

When MCP rules are selected, Batesian sends the target's canonical URI as the OAuth resource in token requests. Use --oauth-resource https://mcp.example.com/mcp if the resource server's canonical URI differs from --target. A2A-only scans omit resource unless this flag is set. --oauth-audience remains a separate, provider-specific parameter. OAuth token responses must declare a Bearer token_type; other or missing types are rejected before the token can be used in a scan.

Authorization code with PKCE

Batesian prints the authorization URL to stderr for manual sign-in. It contains a one-time state value, so keep it private and avoid saving terminal logs during sign-in.

batesian scan --target https://mcp.example.com \
  --auth-url https://auth.example.com/authorize \
  --token-url https://auth.example.com/oauth/token \
  --oauth-issuer https://auth.example.com \
  --client-id my-client \
  --oauth-scopes mcp:read

The PKCE flow sends the same resource in both the authorization and token requests.

Set --oauth-issuer to the exact issuer in the authorization server metadata, which may include a path. Batesian rejects callbacks with a missing or different iss when this flag is set. It also rejects an iss received without a configured issuer. Servers that omit iss remain compatible when the flag is unset. This manual endpoint flow does not discover or validate authorization server metadata; verify the issuer and endpoints before using it.

Multiple principals

Cross-tenant and cross-principal checks require distinct identities. Add routing headers when the target derives tenant identity from a header rather than the token:

batesian scan --target https://agent.example.com \
  --principal name=tenant-a,token="$TOKEN_A",tenant=A,header=X-Tenant-Id:A \
  --principal name=tenant-b,token="$TOKEN_B",tenant=B,header=X-Tenant-Id:B

In batesian.yaml, principal tokens can use ${TOKEN_A}-style environment references. An unset or empty variable stops the scan before requests are sent.

Safe operation

  • --dry-run records a redacted request plan and sends no network traffic. Header values, URL userinfo and queries, and request body values are hidden; hosts and paths remain visible. Requests that depend on live responses, acquired tokens, or callbacks cannot be expanded fully until a real scan.
  • --proxy 127.0.0.1:8080 routes scan and OAuth traffic through an intercepting proxy. Environment proxy variables are honored when the flag is absent; Go excludes loopback targets from environment proxies.
  • --skip-tls disables certificate validation and should be limited to controlled labs or intercepting proxies.
  • Target-advertised OAuth endpoints are restricted to the target origin. Use --oauth-origin https://login.example.com to approve an additional exact origin.
  • OAuth probes attempt to remove registered clients when RFC 7592 management is available. External OOB metadata checks leave the client registered so delayed callbacks remain observable; remove it manually after verification.
  • Stateful rules may create A2A and MCP tasks, A2A push configurations, and OAuth clients. Scan reports list accepted and unverified objects with cleanup status even when no finding is produced. The push SSRF, callback-auth, and binding rules attempt to delete configurations after readback confirms the registered callback. External OOB probes retain registrations for manual verification. Other push configs may remain. Task cancellation is not deletion. Review leftover objects after a scan.
  • The scope-confusion rule invokes a mutating tool only when its exact name is approved with --mcp-scope-tool. This also asserts that the limited principal should be denied access to that tool.
  • Traversal and task rules require --mcp-invoke-tool NAME before calling a real MCP tool. Review the tool yourself; server-supplied read-only annotations are only hints.
  • JSON and SARIF may contain URLs, response snippets, and evidence. Treat them as sensitive security artifacts.

Results and exit behavior

Output Intended use
table Interactive review in a terminal
json Automation and custom policy gates
sarif GitHub code scanning and SARIF-compatible platforms

For A2A probes, JSON agent_card is a parsed, redacted view, not the original response bytes. agent_card_provenance records the fetched URL and SHA-256 of those bytes.

A finding is marked confirmed only when the rule observes exploit evidence. An indicator identifies a risky condition without claiming successful exploitation.

scan exits non-zero for command-level failures, including invalid configuration or filters that select no rules. Findings and individual rule skips do not change the process exit code. Check rule outcomes in JSON or SARIF before treating a scan as complete; a zero exit code alone is not a clean bill of health. SARIF invocation metadata records completed, skipped, and errored rules and sets executionSuccessful to false when coverage is incomplete.

CI integration

The following workflow uses current main coverage metadata to upload A2A findings and fail when selected rules do not complete:

name: Batesian

on: workflow_dispatch

jobs:
  scan:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      security-events: write
    steps:
      - uses: actions/setup-go@v7
        with:
          go-version: '1.26.10'
      - run: go install github.com/calbebop/batesian/cmd/batesian@v1.8.1
      - name: Scan
        env:
          BATESIAN_TOKEN: ${{ secrets.BATESIAN_TOKEN }}
        run: batesian scan --target https://agent.example.com --protocol a2a --output sarif > results.sarif
      - uses: github/codeql-action/upload-sarif@v4
        if: always()
        with:
          sarif_file: results.sarif
      - name: Require complete coverage
        run: |
          jq -e '
            (.runs | length) == 1 and
            (.runs[0].invocations | length) == 1 and
            (.runs[0].invocations[0] |
              .executionSuccessful == true and
              .properties.rulesSelected > 0 and
              .properties.rulesCompleted == .properties.rulesSelected and
              .properties.rulesSkipped == 0 and
              .properties.rulesErrored == 0)
          ' results.sarif

Update the pinned release after review. This example uses a manual trigger so credentials are not exposed to pull-request code. Older releases may not include coverage metadata. Select only protocols and rules whose authentication, identity, and callback prerequisites the job can provide; skipped or errored rules are not evidence of a clean target. More deployment patterns are documented in CI/CD integration.

Configuration and rule packs

Generate an annotated configuration file with:

batesian init

Run init from the project directory. It creates batesian.yaml there and will not overwrite an existing file; it does not print the YAML to stdout. Explicit or discovered configuration must be readable, contain one YAML document, and use known keys.

scan searches the current directory and its parents for a config file and reports the selected path before OAuth or scan traffic begins (stderr for JSON and SARIF). Pre-scan errors also name the selected file. Use --config path to select a file or --no-config to use only flags and environment variables. The two flags cannot be combined. --no-config affects file discovery, not protocol discovery.

--rules-dir supplements the built-in catalog with strict YAML descriptors. Descriptors select compiled executors and provide report metadata; they cannot define arbitrary network behavior. A rule pack loads atomically, so one unreadable, invalid, or oversized descriptor stops the command instead of producing a partial scan.

Contributing and security

Rules, fixes, and documentation are welcome. See CONTRIBUTING.md for the rule architecture, test requirements, and local workflow. Vulnerable fixtures and their port assignments are documented in testdata/README.md.

Report vulnerabilities in Batesian privately according to SECURITY.md.

References

License

Apache 2.0. See LICENSE.

About

Active security scanner for A2A and MCP servers

Topics

Resources

Contributing

Security policy

Stars

3 stars

Watchers

0 watching

Forks

Releases

Used by

Contributors

Languages