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

SEE IT IN ACTION

An AI agent attempts to delete files. AgentWard blocks it — in code, not prompts.

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."
AgentWard — Architecture AGENT HOSTS Claude Desktop Claude Code Cursor Windsurf VS Code OpenClaw UI API Clients JSON-RPC tools/call POST /tools-invoke AGENTWARD — PERMISSION ENFORCEMENT LAYER Stdio Proxy MCP JSON-RPC 2.0 over stdio Subprocess management HTTP Reverse Proxy Gateway interception + WebSocket passthrough for UI agentward.yaml Declarative policy rules Policy Engine ALLOW BLOCK APPROVE LOG REDACT Audit Logger Structured JSON Lines · Rich stderr output Every decision logged with full context ✓ ALLOW ✓ ALLOW ✗ BLOCK Call never reaches tool servers TOOL SERVERS MCP Servers filesystem · GitHub · database · Slack OpenClaw Gateway email · calendar · shell · web browser stdio HTTP / WebSocket Code-level enforcement outside the LLM context window — prompt injection can't override structural policies

One command to lock it down

Scans your environment, generates a policy, wires enforcement, and starts the proxy — all in one step.

Or go step by step

Five stages. Each command does one thing well.

$

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

Want a pre-built policy? Start here

Hardened profile for 6 popular MCP servers — filesystem, GitHub, Slack, memory, Brave Search, and Puppeteer.

$ pip install agentward
$ git clone https://github.com/agentward-ai/agentward
$ cd agentward/examples/hardened-mcp
$ chmod +x setup.sh && ./setup.sh
✓ Found agentward 0.2.2
✓ Policy validated: 6 skill(s), 3 chain rule(s), 2 approval gate(s)
✓ Installing into: Claude Desktop
What's enforced:
File writes blocked (approval required)
GitHub merges and deletes blocked
Slack sends require approval
Filesystem access requires approval
Browser content cannot leak into Slack
File content cannot trigger Slack sends
Browser cannot trigger GitHub operations
Done. Restart Claude Desktop to activate.

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.

POLICY & RUNTIME

Tool still appears "blocked" after re-enabling it in the policy

Symptom: You blocked a tool (e.g., browser: denied: true), then removed the block from agentward.yaml and restarted the proxy. The agent still avoids using the tool, or says something like "that tool is blocked."

Root cause: This is not AgentWard blocking the tool. When a tool is blocked, AgentWard injects a message like [AgentWard: blocked tool 'browser'] into the LLM response. The LLM sees this in its conversation history and "remembers" the restriction — so even after you re-enable the tool, the LLM avoids using it because it thinks it's still blocked.

Fix: Start a new chat session after changing your policy. A fresh conversation has no memory of the previous block. You can confirm AgentWard is no longer blocking by checking the proxy output — if you see ALLOW (or no BLOCK message), the tool is allowed through.

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]