Skip to content

usewombat/gateway

Repository files navigation

Wombat

CI npm version MIT License

Resource-level permissions for AI agents.

Wombat sits between your AI agent and its tools. Every tool call is checked against a permissions policy. Deny by default. Allow by declaration.


The problem

AI agents are powerful. They can push code, delete branches, read secrets. When you give an agent access to your GitHub org or production database, you're trusting it with a lot.

Current permission systems are blunt: you can allow or deny entire tools. But the same push_files call should be allowed on feature/x and denied on main. The same read_file call should work on your project but not on ~/.aws/credentials.

Wombat solves this. It's the Unix chmod for AI agents: rwxd on any resource, deny by default, most specific rule wins.


Try the live demo

Fork usewombat/gateway-demo, clone your fork, and run claude inside it. The repo is self-contained — it detects your fork automatically and wires up Wombat with no manual configuration.

You'll see the agent allowed to trigger a CI workflow and denied when it tries to push to main.


Install

npm install -g @usewombat/gateway

Requires Node.js 22+. If you use the github upstream, Docker must also be installed — it runs via ghcr.io/github/github-mcp-server.


Use as a library

Wombat's permission engine is published as a standalone library — use it in your own MCP proxy, hook server, or CLI:

npm install @usewombat/gateway
import { checkPermission, resolve, findGrant, parseManifest } from "@usewombat/gateway";

// Load or create a manifest
const manifest = parseManifest({
  version: "0.1",
  umask: "----",
  grants: [
    { resource: "github/org/repo", mode: "r---" },
  ],
  sudo: { enabled: false },
});

// Resolve a tool call to a resource path
const resolved = resolve({ tool: "github__push_files", params: { owner: "org", repo: "repo" } });
// → { resource: "github/org/repo", mode: "w" }

// Check against permissions
const result = checkPermission({ tool: "github__push_files", params: {} }, manifest);
// → { decision: "deny", matchedGrant: null, resource: "github/org/repo", mode: "w", umask: "----" }

The library has zero dependencies beyond TypeScript types — the permission engine is pure functions with no I/O.

What's exported

Import From Pure?
checkPermission() gateway.ts ✅ — deterministic, no side effects
resolve() resolver.ts ✅ — deterministic
findGrant() manifest.ts ✅ — specificity scoring
parseManifest() manifest.ts ✅ — Zod-validated
parseMode() / modeAllows() manifest.ts ✅ — mode helpers
buildDenyResponse() gateway.ts ✅ — builds MCP error shape
hashManifestFile() manifest.ts ✅ — sha256 of manifest file
AuditEnvelope types.ts Type — structured check result

Quick start

Wombat has two modes. Pick one.

Local — permissions.json on disk

1. Create a permissions manifest

cp $(wombat --example) ./permissions.json
# edit permissions.json

Example: a CI/deploy agent that can trigger workflows and push to feature branches, but can never touch main directly:

{
  "version": "0.1",
  "umask": "----",
  "grants": [
    { "resource": "github/my-org/my-repo", "mode": "r-x-", "comment": "Read repo, trigger CI workflows" },
    { "resource": "github/my-org/my-repo/main", "mode": "r---", "comment": "Main is read-only — no direct pushes" },
    { "resource": "github/my-org/my-repo/feature/*", "mode": "rw--", "comment": "Push to feature branches" }
  ],
  "sudo": { "enabled": false }
}

2. Add Wombat to your agent

For Claude Code:

claude mcp add wombat -- wombat \
  --manifest ~/my-project/permissions.json \
  --upstream github \
  --upstream filesystem

Or in .claude.json:

{
  "mcpServers": {
    "wombat": {
      "command": "wombat",
      "args": ["--manifest", "~/my-project/permissions.json", "--upstream", "github", "--dashboard", "7842"]
    }
  }
}

Avoid relative paths for --manifest. MCP servers are launched with an unpredictable working directory, so ./permissions.json will not resolve correctly. Use ~/... or a full absolute path instead.

For OpenCode, add to opencode.json:

{
  "mcp": {
    "wombat": {
      "type": "local",
      "command": ["wombat", "--manifest", "./permissions.json", "--upstream", "github"],
      "enabled": true
    }
  }
}

Cloud — permissions hosted on gateway.wombat.dev

Manage permissions from a dashboard instead of editing a local file. The gateway fetches a signed credential at startup and verifies it locally — no Wombat server is contacted during tool execution.

1. Register your gateway

wombat --keygen
# [wombat] Gateway did:key : did:key:z6Mk...
# [wombat] Key file        : ~/.config/wombat/gateway.key

Copy the did:key. You'll paste it into the dashboard when registering an agent.

2. Issue a token

Sign in at gateway.wombat.dev, register an agent with your did:key, define the permission grants, and click Sign & Issue Token.

The dashboard shows a ready-to-use CLI command:

wombat --token <agentId>.<token> --upstream github

The --token value is two UUIDs joined by a dot: the agent's database ID and the opaque credential token. Copy the full string — the gateway needs both parts to authenticate the fetch request.

3. Open the live dashboard

open http://localhost:7842

Live view of every tool call: allowed (green) or denied (red).


Permissions manifest

The manifest defines what your agent is allowed to do. In local mode it's permissions.json. In cloud mode the dashboard generates it — same format, same enforcement.

{
  "version": "0.1",
  "umask": "----",
  "grants": [
    { "resource": "github/my-org/my-repo", "mode": "rw--", "comment": "Full access to main repo" },
    { "resource": "github/my-org/my-repo/main", "mode": "r---", "comment": "Main is read-only" },
    { "resource": "filesystem/Users/yourname/Documents/project/**", "mode": "rw--", "comment": "Project files" },
    { "resource": "filesystem/Users/yourname/Documents/project/.env", "mode": "----", "comment": "Never touch .env" }
  ],
  "sudo": { "enabled": false }
}

Filesystem resources use the full absolute path (without the leading /). Agents always pass absolute paths to filesystem tools, so filesystem/project will not match — use filesystem/Users/yourname/Documents/project/** instead.

Mode strings

Four characters: rwxd. Use - to deny that position.

Mode Meaning
r--- read only
rw-- read + write
r-x- read + execute (e.g. trigger CI, no write)
rwxd full access
---- explicit deny

Specificity

More specific paths win. This means you can be permissive at the top and restrictive deeper:

github/org/repo/main   ← wins (most specific)
github/org/repo        ← second
github/org/*           ← third
github/**              ← least specific

Built-in servers

Wombat ships with resolver tables for popular MCP servers:

wombat --upstream github       # GitHub
wombat --upstream filesystem   # Local filesystem
wombat --upstream slack        # Slack
wombat --upstream postgres     # PostgreSQL
wombat --upstream brave        # Brave Search
wombat --upstream puppeteer    # Browser automation
wombat --upstream memory       # Knowledge graph
wombat --upstream fetch        # HTTP requests

wombat --list   # See all available servers

Community plugins

Install and Wombat auto-discovers them:

npm install @wombat-plugin/notion
npm install @wombat-plugin/linear
wombat --upstream notion --upstream linear

Local plugins

For internal servers, create wombat.plugins.js in your project root:

export default [{
  name: "mycompany",
  tools: {
    mycompany__get_customer:    ["r", (p) => `mycompany/customer/${p.id}`],
    mycompany__update_customer: ["w", (p) => `mycompany/customer/${p.id}`],
  }
}];

No publishing required. Loaded automatically.


Security

Local mode — manifest integrity

Wombat hashes permissions.json at startup. If the file is modified while running — including by the agent — all calls are denied until you restart.

# Store the manifest outside the agent's reach
wombat --manifest ~/wombat/permissions.json --upstream github

Even if the agent can't modify the manifest, it can still read it and use that knowledge to work around the rules. For Claude Code, add a permissions block to .claude.json to deny direct access:

{
  "permissions": {
    "deny": [
      "Read(permissions.json)",
      "Grep(permissions.json)"
    ]
  }
}

Cloud mode — credential integrity

When using --token, the permissions are a BBS+ Verifiable Credential signed by your passkey in the browser. Wombat verifies the cryptographic proof independently at startup — no Wombat server is in the verification path.

  • The credential cannot be forged. It is signed by your root did:key, which never leaves your device.
  • The agent cannot edit its own permissions. Changing the credential requires forging a BBS+ proof — cryptographically infeasible.
  • TTL bounds the blast radius. If a token is compromised, it expires at the stated time. Revoke early from the dashboard — the next gateway fetch returns 404.
  • Store the gateway key outside the agent's reach. The default location (~/.config/wombat/gateway.key) is outside any project directory.

Audit logging

Every call is logged to wombat-audit.jsonl in both modes:

{"ts":"2026-05-29T10:23:01Z","session":"sess_abc123","agent":"claude","tool":"filesystem__write_file","server":"filesystem","resource":"filesystem/Users/me/project/secret/keys.json","mode":"w","decision":"deny","ms":2,"manifest_hash":"abc...","matched_rule_resource":"filesystem/Users/me/project/secret/**","matched_rule_mode":"----"}

Every denied entry includes the matched grant (resource + mode) or indicates the umask was used, so you can trace exactly which rule blocked the call and where to widen scope.

In dry-run mode, entries also include "dry_run":true.

Never logged: parameter values, file contents, API responses.


Architecture

Wombat is an MCP proxy. Your agent talks to Wombat via stdio; Wombat forwards allowed calls to upstream servers.

Agent (Claude Code, OpenCode, etc.)
    │
    ▼
┌────────────────────────────────────────────────┐
│  Wombat                                        │
│  1. Load manifest                              │
│     ├─ local: permissions.json (hash-locked)   │
│     └─ cloud: BBS+ VC fetched via --token      │
│  2. Resolve tool call → resource               │
│  3. Check against manifest                     │
│  4. Allow or deny                              │
└────────────────────────────────────────────────┘
    │
    ├── allowed → upstream server
    └── denied → error to agent

Every tool is annotated with its required mode ([read], [write], [exec], [delete]) in the tool list, so the agent can see the full permission map at startup. In dry-run mode (--dry-run), non-read calls are logged with a [DRY-RUN] marker and return an informative response instead of being forwarded — letting the agent discover what it would be able to do without side effects.


CLI reference

# Manifest source (exactly one required)
wombat --manifest <path>   # Local permissions.json. Supports ~/ expansion.
wombat --token <token>     # Cloud credential token from gateway.wombat.dev
                           # Format: <agentId>.<opaqueToken> — copy from the dashboard

# Cloud credential setup
wombat --keygen            # Print this gateway's did:key and exit
wombat --key <path>        # Gateway keypair path (default: ~/.config/wombat/gateway.key)
wombat --api-url <url>     # API base URL (default: https://api.gateway.wombat.dev)

# Upstreams and tools
wombat --upstream <name>   # MCP server to proxy (repeat for multiple)
wombat --list              # List available servers
wombat --example           # Show example permissions.json path

# Runtime
wombat --dashboard <port>  # Dashboard port (default: 7842)
wombat --agent auto        # Detect agent (claude-code or opencode)
wombat --audit <path>      # Audit log path
wombat --live-reload       # Dev mode: reload manifest on changes (incompatible with --token)
wombat --dry-run           # Log non-read calls without forwarding them

Comparison

Other tools Wombat
Scope Allow/deny entire tools r--- on main, rw-- on feature/*
Per-resource No Yes
Audit log Sometimes Always
Integrity check No Yes — file hash (local) or BBS+ proof (cloud)
Cloud-hosted policy No Yes — via --token

No other tool lets you say: "Allow push_files to feature/*, deny it to main, and log every attempt."


License

MIT — LICENSE

Security issues: SECURITY.md

About

Resource-level permissions for MCP agents: rwxd on any resource, deny by default

Topics

Resources

License

Contributing

Security policy

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors