AgentWard — Permission Enforcement for AI Agents

AgentWard

Give your agents boundaries, not blindfolds.

OPEN SOURCE · APACHE 2.0

# Know what your AI agent's tools can actually access

Scan MCP servers, OpenClaw skills, and Python SDK tools. Enforce policies at runtime — in code, outside the LLM context window.

GitHub Repo

THE PROBLEM

26% of 31,000 AI agent skills contain vulnerabilities. 230+ malicious skills found on ClawHub. OpenClaw has 140K+ GitHub stars and gives agents full computer control — but zero permission governance. Every existing tool only scans before installation, then walks away.

AgentWard does all four — across MCP servers, OpenClaw skills, and Python SDK tools.

"Telling an agent 'don't touch the stove' is a natural-language guardrail that can be circumvented. AgentWard puts a physical lock on the stove — code-level enforcement that prompt injection can't override."

## Four stages. One command at a time.

$ ▊

## What does overprivileged(unsafe) actually look like?

Real examples from production agent setups — individually and in combination

OVERPRIVILEGED(UNSAFE) SKILLS

shell_executor — run arbitrary commands

Agents use this to run scripts, install packages, move files. The skill itself is the problem — a single misdirected instruction can wipe a directory, exfiltrate environment variables, or open a reverse shell.

CRITICAL shell_executor.run_command
rm -rf ~/Documents/
unrestricted shell access

filesystem — write + delete access

Read-only filesystem access is usually fine. But write and delete permissions mean a confused model can overwrite config files or delete things it was only asked to "clean up."

HIGH filesystem.write_file
path: ~/.ssh/authorized_keys
write + delete enabled

email-manager — send + delete access

A skill that can both read and delete emails can silently erase inbound messages — including password resets or fraud alerts — without the user ever seeing them.

HIGH email_manager.delete_message
from: security[at]bank.com
send + delete enabled

UNSAFE IN COMBINATION

web-browser + email-manager

individually: LOW · LOW → combined: HIGH

Neither skill is alarming alone. But together an agent can be tricked into exfiltrating data — read a private doc, summarize it, send the summary to an external address.

HIGH web_browser.navigate → email_manager.send
content routed to external address
data exfiltration path detected

calendar-reader + slack-poster

individually: LOW · LOW → combined: MEDIUM

Reading someone's calendar is low-risk. Posting to Slack is low-risk. Together, an agent can leak when executives are traveling or who is on PTO — to any Slack channel it can reach.

MEDIUM calendar.read_events → slack.post_message
PII in calendar data detected
combined exposure: schedule + broadcast

code-executor + github-writer

individually: MEDIUM · MEDIUM → combined: CRITICAL

A coding agent that can run code locally and push to GitHub can introduce malicious changes, exfiltrate secrets found during execution, or overwrite CI config to persist access.

CRITICAL code_executor → github.push_commit
secret found in execution env
combined: exec + write + persist

## Human-readable policies

YAML you can read, diff, and version control. Auto-generated from scan results.

    # agentward.yaml — auto-generated by agentward configure
    version: "1.0"

    skills:
      email-manager:
        gmail:
          read: true
          send: false         # 🚫 blocked — requires approval
          delete: false
        google_calendar:
          denied: true        # email skill has zero calendar access

      finance-tracker:
        gmail:
          read: true
          filters:
            only_from: ["chase.com", "amex.com"]
        network:
          outbound: false     # financial data NEVER leaves machine

      web-researcher:
        browser: { allowed: true }
        gmail:  { denied: true }
        filesystem: { denied: true }

    skill_chaining:
      - email-manager cannot trigger web-researcher
      - finance-tracker cannot trigger any other skill
      - web-researcher cannot trigger shell-executor

    require_approval:
      - send_email
      - delete_file
      - outbound_network_with_pii
      - shell_command_with_sudo

## Running OpenClaw? Start here.

Production-ready hardened profile — 34 skills pre-configured with approval gates, chaining rules, and data boundaries.

$ pip install agentward
$ git clone https://github.com/agentward-ai/agentward
$ cd agentward/examples/openclaw-hardened
$ chmod +x setup.sh && ./setup.sh
✓ Found agentward 0.2.0
✓ Policy validated: 34 skill(s), 7 chain rule(s), 7 approval gate(s)
✓ Copied policy and MCP config to ~/.clawdbot/
What's enforced:
✗ Shell execution blocked
✗ Voice calls blocked
⚠ Email send requires approval
⚠ iMessage send requires approval
⚠ Code execution requires approval
✓ Browsing content cannot leak into email
✓ Credentials isolated from all other skills
✓ Max chain depth: 2 handoffs
Done. Run agentward map --policy agentward.yaml to visualize.

30 seconds from clone to fully enforced. Customize the YAML to fit your workflow.

## Beyond scanning. Beyond prompts.

†Snyk mcp-scan proxy: guardrails via Invariant Labs API; PII detection in proxy mode.
‡SecureClaw: code-level plugin (51 audit checks) + behavioral skill (15 rules in LLM context, bypassable via prompt injection).

WORKS WITH

## Troubleshooting

Common issues and fixes

OPENCLAW GATEWAY SETUP

What's the correct command to restart OpenClaw?

OpenClaw does not support `openclaw stop`, `openclaw start`, or `openclaw restart` as standalone commands. The correct command is:

    openclaw gateway restart

Also avoid `openclaw reset` — that cancels the current operation, it does not restart the gateway.

"Address already in use" — OSError Errno 48 on port 18789

Symptom: AgentWard proxy fails to start with `OSError: [Errno 48] address already in use` on port 18789.

Root cause: OpenClaw is still listening on the original port (18789) because it wasn't restarted after `agentward setup --gateway openclaw` swapped the port.

Fix:

    1. Run: agentward setup --gateway openclaw
    2. Run: openclaw gateway restart
    3. Verify: lsof -i :18790   # should show node
               lsof -i :18789   # should be empty
    4. Run: agentward inspect --gateway openclaw --policy agentward.yaml

OpenClaw still binds to the old port even after restart

Symptom: After editing `clawdbot.json` to change the port, OpenClaw still binds to the old port after restart.

Root cause: OpenClaw uses a macOS LaunchAgent plist at `~/Library/LaunchAgents/com.clawdbot.gateway.plist` that hardcodes the port in two places — `ProgramArguments` and `EnvironmentVariables`. OpenClaw reads from the plist at launch, not from `clawdbot.json`. Both must be updated.

Fix: Run `agentward setup --gateway openclaw` — it patches both files automatically. If debugging manually, check both `clawdbot.json` and the plist.

Proxy starts but forwards to itself / setup says already wrapped

Symptom: Proxy starts but creates an infinite loop, or `agentward setup` reports the gateway is already wrapped but ports look wrong.

Root cause: A stale `.agentward-gateway.json` sidecar file in `~/.clawdbot/` from a previous run.

Fix:

    rm ~/.clawdbot/.agentward-gateway.json
    agentward setup --gateway openclaw
    openclaw gateway restart
    agentward inspect --gateway openclaw --policy agentward.yaml

OpenClaw UI shows blank page or connection error through proxy

Symptom: OpenClaw UI at `http://127.0.0.1:18789/` shows blank page or connection error when accessed through the AgentWard proxy.

Root cause (historical): The HTTP proxy was not forwarding WebSocket upgrade requests. OpenClaw's React SPA communicates with the gateway over WebSocket.

Current state: This is fixed. The proxy now detects WebSocket upgrade headers and proxies traffic bidirectionally. No action needed unless this reappears after code changes.

Policy enforcement isn't triggering on tool calls

OpenClaw's tool invocation endpoint is `POST /tools-invoke` (with a hyphen), not `/tools/invoke` (with a slash). If policy enforcement isn't working, verify the proxy is intercepting the correct path.

HTTP PROXY ISSUES

"Error handling request from 127.0.0.1"

Root cause (historical): Two bugs in the HTTP proxy:

- Double body read: `request.json()` consumed the body, then `request.read()` returned empty bytes when forwarding.
- StreamResponse + Content-Length mismatch: Using `web.StreamResponse` and copying the backend's `Content-Length` header while streaming caused mismatches.

Current state: Fixed. The proxy now reads raw body once with `request.read()`, parses with `json.loads()`, and passes cached `raw_body` to `_forward_request()`. Uses `web.Response` (not `StreamResponse`) to avoid header issues.

MCP STDIO PROXY ISSUES

Server subprocess exits immediately

Check that the server command works standalone first:

    npx -y @modelcontextprotocol/server-filesystem /tmp

If the server requires specific env vars or paths, pass them through the agentward command.

## Stop YOLOing your agent permissions.

Start verifying.

5 seconds to see what your AI agent's tools can actually do.

GitHub →

Apache 2.0 · Python 3.11+ · No API key · Everything runs locally

[email protected]