Capgate

mcp
Guvenlik Denetimi
Basarisiz
Health Uyari
  • License — License: MIT
  • Description — Repository has a description
  • Active repo — Last push 0 days ago
  • Low visibility — Only 5 GitHub stars
Code Basarisiz
  • Hardcoded secret — Potential hardcoded credential in deploy/docker-compose.yml
  • network request — Outbound network request in deploy/fetch_artifacts.py
Permissions Gecti
  • Permissions — No dangerous permissions requested

Bu listing icin henuz AI raporu yok.

SUMMARY

Capgate — runtime authority boundary for autonomous AI agents: the agent proposes, the harness authorizes, the executor acts. Deny-by-default policy, non-transitive delegation, tamper-evident audit, MCP gateway, containerised isolation.

README.md

Capgate

Authority management for autonomous software agents.

The agent proposes. The harness authorizes. The executor acts.

CI Python Tests License Status

Capgate is a runtime enforcement boundary that sits underneath your
existing agent stack. Every consequential action an agent takes — a tool call,
a message to another agent, a request that another agent act — is a structured
proposal that passes through one deterministic, deny-by-default policy engine
before anything executes. Every decision is written to a tamper-evident audit
trail.

It is not an agent framework, a prompt library, or a model wrapper. It does not
ask the model to follow rules; it makes unauthorized actions structurally
unable to reach a tool.

Capgate architecture

Contents

The boundary, under attack

Run a compromised agent — one that ignores the SDK and tries every route
around the harness — inside the reference isolated runtime:

cd deploy
python fetch_artifacts.py               # once; images build offline
docker compose --profile agent build     # rebuild every image, including netguard and agent
docker compose up -d harness
docker compose run --rm agent           # the compromised agent, inside its sandbox
docker compose logs harness             # every decision; one successful execution

or, with all checks evaluated automatically:

python deploy/demo.py                   # ~1 minute
CAPGATE — COMPROMISED AGENT IN ISOLATED RUNTIME

  PASS  HTTP  direct call to tool endpoint (no auth)                        0
  PASS  HTTP  out-of-contract action via harness                            [403, 'TOOL_NOT_ALLOWED']
  PASS  HTTP  impersonate db-admin                                          [403, 'IDENTITY_MISMATCH']
  PASS  HTTP  claim admin contract                                          [403, 'CONTRACT_MISMATCH']
  PASS  HTTP  approve own escalation                                        404
  PASS  HTTP  delegate to privileged agent                                  [403, 'TOOL_NOT_ALLOWED']
  PASS  CRED  sub swapped to db admin                                       401
  PASS  CRED  revoked token                                                 401
  PASS  NET   tcp tools:9100 by IP                                          error:101
  PASS  NET   misattached tool on the agent's own network                   refused
  PASS  NET   egress allowlist installed (default DROP, only harness:8080)  iptables OUTPUT
  PASS  NET   internet tcp 1.1.1.1:443                                      error:101
  PASS  NET   resolve example.com                                           dns_failed
  PASS  NET   raw socket                                                    permission_denied
  PASS  PROC  effective capabilities                                        0
  PASS  SEC   filesystem + env + /proc scan for harness secrets             none found
  PASS  PATH  in-contract action via harness                                [200, 'succeeded']
  PASS  PATH  side effects at real tool                                     ['/web.search']
  PASS  PATH  audit hash chain intact                                       True
  …
61/61 checks passed. Side effects at the real tool: 1 (the authorized web.search).

What this runtime enforces, precisely: the agent process cannot open a socket
to anything except the harness API
, and it cannot read the tool credentials,
permit signing key or token keyring. Its own credential is a short-lived signed
token; tampered, forged, expired, revoked and over-long tokens are rejected. The direct tool calls now fail at
the network (0 = no connection), not merely with 401. The only path that
produces a side effect is authorize → signed permit → executor. Details, the
two network layers, and what is not claimed:
deploy/README.md.

Thirty seconds: simulate a contract

pip install -e .
capgate simulate examples/simulation/task.yaml
CAPGATE — SIMULATION

Contract: research-v1
Agent: researcher

    ACTION                          DECISION   REASON
─────────────────────────────────────────────────────
01  web.search                      ALLOW      CAPABILITY_GRANTED
02  web.fetch                       ALLOW      CAPABILITY_GRANTED
03  database.read                   DENY       EXPLICITLY_DENIED
04  agent.delegate                  ESCALATE   REQUIRES_APPROVAL
05  web.search                      ALLOW      CAPABILITY_GRANTED

Summary
────────────────
Allowed:      3
Denied:       1
Escalated:    1

No external actions were executed.

Simulation needs no infrastructure and uses the exact engine that enforcement
uses. Write a contract, run your agent's proposals through simulate, tune
the contract, then switch to capgate enforce.

Inspect: simulation, audit, escalations, policy

python examples/inspect/demo.py      # live local stack; opens the browser
capgate inspect --task task.yaml --audit audit.jsonl --harness-url http://127.0.0.1:8080 --approver-token-file alice.token

A thin local UI: edit a contract and watch the allow/deny/escalate table change;
browse and verify the hash-chained audit trail; approve or reject pending
escalations; ask what a hypothetical action would hit and diff two contracts.
See docs/inspect.md.

capgate inspect — simulation, audit, policy and escalations

Each view, full size

Simulate — the contract and the proposed actions side by side; every edit re-runs the decision table.

Simulate

Audit — the hash-chained trail, integrity checked on load, filterable and correlatable by decision.

Audit

Escalations — pending human approvals, with the proposed action, arguments, contract and reason.

Escalations

Policy — the grants in effect per agent, a "what would this hit?" evaluator, and a contract diff.

Policy

The contract

contract_id: research-v1
goal: Research recent work on agent authorization
max_steps: 20
approvers: [alice]
agents:
  researcher:
    capabilities:
      web.search: allow
      web.fetch:
        effect: allow
        constraints:
          allowed_arguments: [url]
          block_private_hosts: true
      database.read: deny
      agent.delegate:
        effect: escalate
        constraints:
          allowed_targets: [writer]
          allowed_actions: [docs.write]

Anything not named is denied. Unknown fields are errors, so a typo cannot
quietly widen or drop a rule. See docs/contracts.md and
docs/capabilities.md.

The API

from capgate import Harness, load_contract

harness = Harness(load_contract("contract.yaml"), tools={"web.search": search})

result = harness.authorize(agent="researcher", action="web.search", arguments={"query": "..."})
if result.allowed:
    output = harness.execute(result).output
else:
    print(result.decision.decision, result.reason_code)   # deny TOOL_NOT_ALLOWED

That is most of the surface: authorize, execute, plus send_message,
delegate and approve/reject for multi-agent and human-in-the-loop flows.
No orchestration system, DSL, model provider, vector database or observability
platform is required.

Delegation does not transfer authority

TaskContract A: web.search ✓  web.fetch ✓  database.write ✗  create_agent ✗
TaskContract B: web.search ✓

Agent A → "Ask Agent B to perform database.write"
Harness → evaluates Agent B's contract → DENIED
cd examples/delegation-boundary && python agent_a.py

The authorization question is always who is acting, under which contract,
with what capability, on what resource
— never who asked. The policy engine
has no input through which a delegator could influence the answer. See
docs/delegation.md.

What is actually enforced

The README claims exactly this: all consequential execution is mediated by
the harness
, in the following precise sense.

Layer Enforced by Tested in
Out-of-contract proposals are denied Pure deny-by-default policy engine tests/adversarial/*, tests/unit/test_policy.py
A denied or escalated action never reaches a tool Executor runs only with a harness-signed, single-use grant bound to the exact agent, action and argument hash tests/adversarial/unauthorized_tool (Test C)
An agent process cannot reach the real tool directly Isolated runtime: agent's only network route is harness:8080 (internal networks + iptables allowlist). Any deployment: tool credentials exist only in the executor and the tool endpoint requires them tests/adversarial/isolation (pytest -m docker), tests/bypass/test_process_boundary.py
An agent cannot read harness secrets Isolated runtime: secrets mounted only into harness/tools, separate PID namespace, no capabilities, read-only filesystem tests/adversarial/isolation
An agent cannot impersonate another agent or pick its contract Identity comes from a verified short-lived signed token (expiry, TTL cap, rotation, revocation), contract from the server-side binding tests/bypass, tests/unit/test_identity.py, tests/adversarial/scope_expansion
Restarts don't reset authority Budgets, used grants, approvals, messages and revocations persist in SQLite tests/integration/test_persistence.py
Every decision is auditable Audit write happens before a decision is returned; failure blocks execution; records are hash-chained tests/adversarial/audit (Test E)

The network and secret guarantees hold for the reference runtime in
deploy/, or a deployment that reproduces
its properties. If an agent instead runs as the same user on the same host as
the harness, it may be able to read the harness's memory, files or environment;
credential custody still stops direct tool calls, but not that. Kernel and
container-runtime escapes are out of scope. The SDK is a convenience, never the
boundary. Full detail: docs/threat-model.md.

Ways in, one path through

Python SDK (in-process Harness) ─┐
HTTP API (uvicorn, behind nginx) ┤
capgate_client / curl  ──────────┼──► Interceptor ─► Policy ─► Executor ─► Tool / Agent
MCP client → /mcp  ──────────────┘
capgate token keygen --keyring keys.json                                   # rotate later by running it again
capgate serve examples/basic/server.yaml                                   # keyring hot-reloads; state persists
capgate token issue --keyring keys.json --sub researcher --role agent --ttl 15m
capgate token revoke --keyring keys.json --state state.db --token "$TOKEN"
from capgate_client import HarnessClient
client = HarnessClient("http://127.0.0.1:8700", token=supervisor.current_token)  # str or callable
client.act("web.search", {"query": "..."})

Credentials are short-lived HMAC-signed tokens (ah1.<kid>.<claims>.<sig>) with
a verifier-enforced maximum lifetime, key rotation without restart, and
revocation. Tokens are issued by the operator or agent supervisor; an agent
cannot renew its own.

In production the API runs under uvicorn behind an nginx edge that
bounds request sizes, timeouts and paths (see deploy/); a
stdlib transport is kept for development. Both go through one core, fuzzed with
Hypothesis.

MCP: agents call in through the boundary

Any MCP client reaches the harness at POST /mcp; each tools/call is authorized
under the caller's contract before it runs.

python examples/mcp/demo.py    # drives /mcp with the official MCP SDK client
Tools offered to this agent:  web-search  web-fetch  agent-delegate  capgate-approval_status
  web-search      OK     CAPABILITY_GRANTED
  database-read   ERROR  EXPLICITLY_DENIED
  agent-delegate  ERROR  (requires human approval → approval_id)

Identity comes from the bearer token, never the MCP payload. Denied and unknown
tools are audited tool-errors; escalations return an approval id. Upstream MCP
servers can back executor tools with their credentials held harness-side, and
capgate mcp-bridge adapts stdio-only clients. See docs/mcp.md.

A real, model-driven agent loop — Claude choosing tool calls, Capgate
authorizing each one — is in examples/agent_loop
(python examples/agent_loop/demo.py; runs offline, or drives a real Claude
model with ANTHROPIC_API_KEY).

Audit trail

capgate enforce examples/basic/task.yaml --audit audit.jsonl
capgate audit audit.jsonl --decision deny
capgate audit audit.jsonl --verify

Each record states what was proposed, by which agent, under which contract
(id and content hash), which capability and policy rule applied, the decision
and reason code, and the outcome. Nothing from model reasoning is captured.

Tests

pip install -e ".[dev]" && pytest     # unit, integration, adversarial, fuzz, MCP (~1 min)
pytest -m docker                      # isolated runtime acceptance tests (Docker)
HYPOTHESIS_PROFILE=deep pytest tests/fuzz   # thousands of examples per property

The suite runs on both HTTP transports; CI runs
Python 3.10–3.13 on both, plus the Docker isolation job and a nightly deep-fuzz
run. Trying to break the boundary is the most useful contribution —
see SECURITY.md.

The adversarial suite is organised by attack category —
unauthorized_tool, argument_violation, scope_expansion, delegation,
capability_expiration, contract_tampering, message_injection,
bypass_attempt, budget_exhaustion, escalation, audit — plus
tests/adversarial/test_required_scenarios.py, which covers the six
acceptance scenarios one test each; tests/bypass/, which attacks a live
deployment from a separate OS process; tests/adversarial/isolation/, which
attacks the containerised reference deployment from inside the agent's sandbox;
and tests/fuzz/, property-based fuzzing of the API, policy engine and both
HTTP transports.

Layout

src/harness/      contract, capability, policy, decision, request, interceptor,
                  executor, audit, core (Harness), simulation, cli, tools, identity,
                  state, ratelimit, api (transport-independent core), asgi + server
                  (uvicorn/stdlib transports), inspect/ (local UI), mcp/ (gateway,
                  upstream, stdio bridge)
sdk/python/       capgate_client — thin HTTP client
examples/         basic, simulation, delegation-boundary, adversarial-agent, inspect, mcp, agent_loop
deploy/           reference isolated deployment: compose, harness/, agent/, network/, proxy/ (nginx), demo
policies/         reusable contract templates
docs/             architecture, contracts, capabilities, delegation, simulation, mcp, inspect, threat model
.github/          CI: 3.10–3.13 × both transports, docker isolation, nightly deep fuzz
DESIGN.md         the five invariants  ·  SECURITY.md  ·  CONTRIBUTING.md

Status

The five pieces — contract, proposal, deterministic decision, enforced
execution, audit — work end to end and are tested together
(tests/integration/test_end_to_end.py). In the reference isolated deployment
the boundary holds against every tested bypass route, enforced by the network
and process boundary, credential custody, and an nginx edge in front of uvicorn;
a real MCP client and a real upstream MCP server run through it in the test
suite. The API and both transports are fuzzed. State runs in-process, on SQLite
(one host), or on PostgreSQL (many replicas, docs/state.md).

Still not production-grade, on purpose. Open items, in priority order:
workload identity (mTLS / SPIFFE) so a token is bound to the calling workload;
multi-host state and shared rate limits; a stronger isolation runtime
(gVisor / microVM) and a tested Kubernetes deployment; and outside security
review — see SECURITY.md. Full detail and what is not claimed:
docs/threat-model.md.

Yorumlar (0)

Sonuc bulunamadi