{"slug": "your-ai-agent-has-sudo-i-built-a-tool-to-take-it-away", "title": "Your AI agent has sudo. I built a tool to take it away.", "summary": "A developer built AgentPerms, a CLI tool that enforces least-privilege permissions for AI agents using the Model Context Protocol (MCP). The tool records agent behavior, infers minimum required permissions, and enforces policies via a transparent proxy, blocking attacks like SSH-key exfiltration and destructive commands. AgentPerms aims to close the security loop for MCP agents used in tools like Claude Desktop and VS Code.", "body_md": "A few weeks ago I gave an AI agent access to my machine through MCP. It read files, opened PRs, queried a database. It was great — until I looked at *what it could have done* if a tool description had been poisoned, or a prompt injection had slipped through.\n\nThe answer was: anything. `~/.ssh/id_rsa`\n\n. `DROP TABLE users`\n\n. `rm -rf /`\n\n. The agent had sudo, and nobody had voted for that.\n\nSo I built ** AgentPerms** — a CLI that gives MCP agents least-privilege permissions the same way you'd lock down any other process: figure out the minimum it actually needs, pin it, prove it, and enforce it.\n\n```\npip install agentperms\n```\n\nMCP (the Model Context Protocol) is quietly becoming the USB-C of AI tooling. Claude Desktop, Cursor, VS Code, Windsurf, Gemini CLI — they all speak it. Which is wonderful, and also means your agent is one config file away from your filesystem, your repos, your inbox, and prod.\n\nThe existing tools each do *part* of the job:\n\nNeither closes the loop. What I wanted was the boring, proven security workflow we already use for everything else: **observe real behavior → derive least privilege → enforce it → keep it honest in CI.**\n\nThat's the whole thesis of AgentPerms, as a pipeline:\n\nrecord → infer → lock → replay → enforce\n\nAgentPerms ships with a deliberately over-privileged demo MCP server, so you can watch a real policy decision without wiring anything up:\n\n```\n# Flag risky config: a ~/.ssh mount and an unpinned npx server\nagentperms scan --path examples/vulnerable-mcp-demo\n\n# Replay a pack of canned attacks against an example policy\nagentperms replay --policy examples/policies/example.mcp.policy.yaml\n```\n\nOutput:\n\n```\n8/8 attacks blocked.\n```\n\nSSH-key exfiltration, `.env`\n\nreads, `rm -rf /`\n\n, unapproved email, force-push, repo deletion, destructive SQL — every one denied or routed to human approval *before it would ever reach a server*.\n\nHere's the part I'm proud of. AgentPerms doesn't ask your agent to cooperate, and it doesn't patch the client. It rewrites the MCP client's config so every server launches **through a transparent stdio proxy**:\n\n```\nAgent  →  AgentPerms proxy  →  MCP server\n              │\n              ├─ record:  log every tools/call, then forward\n              └─ enforce: allow / deny / require-approval before forwarding\n```\n\nThe proxy spawns the real server as a subprocess and pumps newline-delimited JSON-RPC both ways. It intercepts `tools/call`\n\nrequests and captures `tools/list`\n\nresponses. That's it. The agent has no idea it's there.\n\nA server entry goes from this:\n\n```\n{ \"command\": \"python3\", \"args\": [\"server.py\"] }\n```\n\nto this (original command preserved after `--`\n\n, with a `.agentperms.bak`\n\nso you can roll back):\n\n```\n{\n  \"command\": \"/usr/bin/python3\",\n  \"args\": [\"-m\", \"agentperms\", \"_proxy\",\n           \"--mode\", \"enforce\", \"--server\", \"demo\",\n           \"--policy\", \"/abs/path/mcp.policy.yaml\",\n           \"--\", \"python3\", \"server.py\"]\n}\n```\n\nIn **record** mode it logs and forwards. In **enforce** mode it evaluates *first* and, on a DENY, returns a synthetic JSON-RPC error to the client **without forwarding**. Denied calls never touch the server.\n\nYou don't write the policy. You run your agent normally for a while with recording on:\n\n```\nagentperms record --client cursor\n#   ... use your agent ...\nagentperms infer        # traces -> mcp.policy.yaml\n```\n\n`infer`\n\nis the killer command. It reads the traces and emits the *minimum* policy that still lets the agent do what it actually did:\n\n`allowed_tools`\n\n`allowed_paths`\n\n`denied_tools`\n\n/ human-approvalThe result reads like a security review wrote it for you:\n\nYour agent only used read-only GitHub calls and local`./src`\n\naccess. It does not need shell, home directory, secrets, Gmail send, or database write access.\n\nWhatever you do, there must be exactly **one** place that says allow/deny/approve — otherwise your offline tests and your live enforcement drift apart and you're testing a lie.\n\nIn AgentPerms that's a single `evaluate(policy, server, tool, args)`\n\nfunction, called by *both* the live proxy and offline `replay`\n\n. First-match-wins:\n\n`denied_tools`\n\n→ `denied_paths`\n\n/ `denied_patterns`\n\n→ `allowed_tools`\n\nset and tool not in it → `allowed_paths`\n\nset and a path falls outside it → An empty policy allows everything. The moment *any* server is constrained, unknown servers default-deny. What you test in `replay`\n\nis byte-for-byte what runs in production, because it's the same code path.\n\nThe policy itself stays small and reviewable:\n\n```\nversion: 1\nservers:\n  github:\n    allowed_tools: [list_repos, read_file, create_issue]\n    denied_tools:  [delete_repo, write_secret, force_push]\n  filesystem:\n    allowed_paths:    [./src, ./docs]\n    denied_paths:     [~/.ssh, ~/.env, /etc]\n    denied_patterns:  [\"*.pem\", \"*.key\"]\napprovals:\n  require_human_approval: [gmail.send_email, github.merge_pr, shell.exec]\nredaction: { secrets: true, emails: true, api_keys: true }\n```\n\nThere's a sneaky MCP attack class where a server silently changes a tool's *description* or *schema* after you've trusted it — the model re-reads it and gets quietly re-instructed. So AgentPerms also locks tool identity:\n\n```\nagentperms lock          # hash every tool's name/description/schema\nagentperms lock --check  # fail if any of them changed\n```\n\nDrop `lock --check`\n\nin CI and a poisoned tool fails the build instead of your users.\n\n```\nagentperms init   # scaffolds .github/workflows/agentperms.yml\n```\n\nOn every push/PR it runs:\n\n```\nagentperms scan --path .     # surface risky configs\nagentperms lock --check      # fail on tool poisoning\nagentperms replay            # fail if the policy stops blocking attacks\n```\n\nCommit `mcp.policy.yaml`\n\nand `mcp.lock`\n\n, and your agent's permissions become a reviewable, version-controlled, enforceable artifact — like any other part of your security posture.\n\nI'd rather be honest than oversell:\n\n```\npip install agentperms\nagentperms scan --path examples/vulnerable-mcp-demo\nagentperms replay --policy examples/policies/example.mcp.policy.yaml\n```\n\nIf you're running agents with real access to real systems, I'd genuinely love your feedback — especially on the policy model and what attack shapes you'd want in the replay pack. Issues and PRs welcome.\n\nYour agent doesn't need sudo. Let's take it away.", "url": "https://wpnews.pro/news/your-ai-agent-has-sudo-i-built-a-tool-to-take-it-away", "canonical_source": "https://dev.to/hasanmehmood/your-ai-agent-has-sudo-i-built-a-tool-to-take-it-away-46mk", "published_at": "2026-06-21 12:49:08+00:00", "updated_at": "2026-06-21 13:07:03.911093+00:00", "lang": "en", "topics": ["ai-agents", "ai-safety", "developer-tools", "ai-infrastructure"], "entities": ["AgentPerms", "MCP", "Claude Desktop", "Cursor", "VS Code", "Windsurf", "Gemini CLI"], "alternates": {"html": "https://wpnews.pro/news/your-ai-agent-has-sudo-i-built-a-tool-to-take-it-away", "markdown": "https://wpnews.pro/news/your-ai-agent-has-sudo-i-built-a-tool-to-take-it-away.md", "text": "https://wpnews.pro/news/your-ai-agent-has-sudo-i-built-a-tool-to-take-it-away.txt", "jsonld": "https://wpnews.pro/news/your-ai-agent-has-sudo-i-built-a-tool-to-take-it-away.jsonld"}}