{"id":349096,"date":"2026-08-07T12:21:47","date_gmt":"2026-08-07T12:21:47","guid":{"rendered":"https:\/\/wordpress.org\/plugins\/agent-firewall-by-agentic-daisy\/"},"modified":"2026-08-28T12:18:14","modified_gmt":"2026-08-28T12:18:14","slug":"agentic-daisy-ai-agent-firewall","status":"publish","type":"plugin","link":"https:\/\/da.wordpress.org\/plugins\/agentic-daisy-ai-agent-firewall\/","author":23465430,"comment_status":"closed","ping_status":"closed","template":"","meta":{"version":"1.6.0","stable_tag":"1.6.0","tested":"7.1","requires":"6.9","requires_php":"8.1","requires_plugins":null,"header_name":"Agentic Daisy AI Agent Firewall","header_author":"Agentic Daisy","header_description":"Intercepts write actions performed by AI agents, applies deterministic policies, and provides an approval queue, audit ledger, and one-click rollback.","assets_banners_color":"505558","last_updated":"2026-08-28 12:18:14","external_support_url":"","external_repository_url":"","donate_link":"","header_plugin_uri":"https:\/\/agenticdaisy.com\/agent-firewall","header_author_uri":"https:\/\/agenticdaisy.com","rating":0,"author_block_rating":0,"active_installs":0,"downloads":351,"num_ratings":0,"support_threads":0,"support_threads_resolved":0,"author_block_count":0,"sections":["description","installation","faq","changelog"],"tags":{"0.1.1":{"tag":"0.1.1","author":"agenticdaisy","date":"2026-08-07 12:47:01","revision":3637871},"1.0.0":{"tag":"1.0.0","author":"agenticdaisy","date":"2026-08-07 15:36:17","revision":3638030},"1.0.1":{"tag":"1.0.1","author":"agenticdaisy","date":"2026-08-10 15:58:25","revision":3640823},"1.1.0":{"tag":"1.1.0","author":"agenticdaisy","date":"2026-08-11 15:23:03","revision":3642199},"1.2.0":{"tag":"1.2.0","author":"agenticdaisy","date":"2026-08-17 15:04:16","revision":3651324},"1.3.0":{"tag":"1.3.0","author":"agenticdaisy","date":"2026-08-19 14:14:42","revision":3654790},"1.4.0":{"tag":"1.4.0","author":"agenticdaisy","date":"2026-08-20 20:30:36","revision":3657761},"1.5.0":{"tag":"1.5.0","author":"agenticdaisy","date":"2026-08-26 14:12:25","revision":3667217},"1.6.0":{"tag":"1.6.0","author":"agenticdaisy","date":"2026-08-28 12:18:14","revision":3670321}},"upgrade_notice":{"1.6.0":"<p>AI clients that authenticate with an application password - most MCP clients - can now be bound to an agent and governed by policy instead of bypassing it. Refusing unclaimed application-password writes is now a checkbox on the Agents screen. Nothing changes until you bind a password.<\/p>","1.5.0":"<p>Security hardening: agent tokens can no longer reach the firewall&#039;s own screens and routes, and agents can no longer be linked to an administrator. Existing admin-linked agents are flagged for relinking. Agents are now linked, and shown, by user name rather than by numeric ID.<\/p>","1.4.0":"<p>Fixes an activation failure on fresh installs alongside plugins that add their own cron schedules. Also names what agents touched in the activity ledger instead of bare database ids, links post edits to the revision that records them, and shows what changed in AI tool-change alerts.<\/p>","1.3.0":"<p>Shows how many actions await approval in the admin menu, pages the activity ledger and approval queue instead of truncating them silently, and fixes database changes never reaching sites installed before an update.<\/p>","1.2.0":"<p>Supports WordPress 7.1, records agent attempts WordPress refuses before they run, governs abilities registered before the firewall loads, and closes a way a plugin could have hidden its own AI tool from the registry monitor.<\/p>","1.1.0":"<p>Governs AI agent tool calls that arrive over MCP, adds a mutation-level backstop for unrecognized transports, and names the site in alert emails.<\/p>","1.0.1":"<p>Stops repeat &quot;new ability&quot; alert emails caused by abilities briefly vanishing during plugin auto-updates.<\/p>","1.0.0":"<p>Adds an emergency kill switch, policy import\/export, a first-run setup guide, and single-digest ability-change alerts.<\/p>","0.1.1":"<p>Hardens option rollback: snapshot capture and restore now enforce a strict settings whitelist.<\/p>","0.1.0":"<p>Initial release.<\/p>"},"ratings":[],"assets_icons":{"icon-128x128.png":{"filename":"icon-128x128.png","revision":3637844,"resolution":"128x128","location":"assets","locale":"","width":128,"height":128},"icon-256x256.png":{"filename":"icon-256x256.png","revision":3637844,"resolution":"256x256","location":"assets","locale":"","width":256,"height":256}},"assets_banners":{"banner-1544x500.png":{"filename":"banner-1544x500.png","revision":3637844,"resolution":"1544x500","location":"assets","locale":"","width":1544,"height":500},"banner-772x250.png":{"filename":"banner-772x250.png","revision":3637844,"resolution":"772x250","location":"assets","locale":"","width":772,"height":250}},"assets_blueprints":{},"all_blocks":[],"tagged_versions":["0.1.1","1.0.0","1.0.1","1.1.0","1.2.0","1.3.0","1.4.0","1.5.0","1.6.0"],"block_files":[],"assets_screenshots":{"screenshot-1.png":{"filename":"screenshot-1.png","revision":3667216,"resolution":"1","location":"assets","locale":"","width":1400,"height":1015},"screenshot-2.png":{"filename":"screenshot-2.png","revision":3667216,"resolution":"2","location":"assets","locale":"","width":1400,"height":900},"screenshot-3.png":{"filename":"screenshot-3.png","revision":3667216,"resolution":"3","location":"assets","locale":"","width":1400,"height":900},"screenshot-4.png":{"filename":"screenshot-4.png","revision":3667216,"resolution":"4","location":"assets","locale":"","width":1400,"height":2516}},"screenshots":{"1":"Activity ledger showing which rule decided each action, with per-action undo, email-alert and retention settings.","2":"Pending approvals queue with payload review.","3":"Agent management with one-time token issuance.","4":"Policy editor."}},"plugin_section":[],"plugin_tags":[15643,2353,232494,1174,600],"plugin_category":[54],"plugin_contributors":[262708],"plugin_business_model":[],"class_list":["post-349096","plugin","type-plugin","status-publish","hentry","plugin_tags-agents","plugin_tags-ai","plugin_tags-ai-agent","plugin_tags-firewall","plugin_tags-security","plugin_category-security-and-spam-protection","plugin_contributors-agenticdaisy","plugin_committers-agenticdaisy"],"banners":{"banner":"https:\/\/ps.w.org\/agentic-daisy-ai-agent-firewall\/assets\/banner-772x250.png?rev=3637844","banner_2x":"https:\/\/ps.w.org\/agentic-daisy-ai-agent-firewall\/assets\/banner-1544x500.png?rev=3637844","banner_rtl":false,"banner_2x_rtl":false},"icons":{"svg":false,"icon":"https:\/\/ps.w.org\/agentic-daisy-ai-agent-firewall\/assets\/icon-128x128.png?rev=3637844","icon_2x":"https:\/\/ps.w.org\/agentic-daisy-ai-agent-firewall\/assets\/icon-256x256.png?rev=3637844","generated":false},"screenshots":[{"src":"https:\/\/ps.w.org\/agentic-daisy-ai-agent-firewall\/assets\/screenshot-1.png?rev=3667216","caption":"Activity ledger showing which rule decided each action, with per-action undo, email-alert and retention settings."},{"src":"https:\/\/ps.w.org\/agentic-daisy-ai-agent-firewall\/assets\/screenshot-2.png?rev=3667216","caption":"Pending approvals queue with payload review."},{"src":"https:\/\/ps.w.org\/agentic-daisy-ai-agent-firewall\/assets\/screenshot-3.png?rev=3667216","caption":"Agent management with one-time token issuance."},{"src":"https:\/\/ps.w.org\/agentic-daisy-ai-agent-firewall\/assets\/screenshot-4.png?rev=3667216","caption":"Policy editor."}],"raw_content":"<!--section=description-->\n<p><strong>This plugin restricts AI agents; it is not an AI tool.<\/strong> It calls no external service, sends nothing off your site, and neither generates nor executes code. Every decision is made by deterministic PHP running locally against rules you write.<\/p>\n\n<p>AI assistants can now manage WordPress sites through the Abilities API and the REST API. That's powerful, and also risky: a confused or hijacked agent can rewrite content, change settings, or delete things faster than anyone notices. Agent Firewall puts an AI agent firewall \u2014 a deterministic policy engine \u2014 between every agent and your site.<\/p>\n\n<p>Each agent gets its own identity, tied to a WordPress user of your choosing. Usually that is a bearer token (<code>ag_live_...<\/code>), stored as a SHA-256 hash and displayed a single time at issuance. For clients that can only send a username and password - most MCP clients - you can instead bind one of that user's application passwords to the agent, which identifies it just as the token does. An agent can never exceed the capabilities of its linked user, and scopes can pin it down further, to specific action categories or ability patterns.<\/p>\n\n<p>Write actions are intercepted at both doors. Ability execution callbacks are wrapped at registration, and direct REST writes are caught at <code>rest_pre_dispatch<\/code>, so switching transports doesn't dodge the rules. Policies match on ability name, action category, entity type or id, and source, in priority order. Each rule decides: allow, deny, log only, or require human approval. If nothing matches, destructive actions require approval and reads pass. A sliding-window rate limiter shuts down runaway loops, and the admin gets an email when it trips.<\/p>\n\n<p>Held actions land in an approval queue along with the target's modification timestamp. If a person edits that content before you approve, the stored action aborts with a conflict rather than overwriting the newer work.<\/p>\n\n<p>Every decision is written to an append-only audit ledger. Each entry carries a SHA-256 hash chained to the previous one, so any after-the-fact tampering breaks verification. Secrets in action payloads (passwords, API keys, tokens) are redacted before they reach the ledger.<\/p>\n\n<p>Executed actions can be undone from the dashboard. Post edits restore through the normal revision history, deletions come back from trash, and settings changes restore from stored snapshots. Where a clean undo isn't possible (say, user creation), the ledger says so instead of pretending.<\/p>\n\n<p>The plugin also watches for two quieter risks: it fingerprints every registered ability and emails you when one appears or changes definition (a known tool-poisoning pattern), and it records REST writes made with an application password that no agent has claimed - automation carrying no identity for a policy to apply to - in the audit ledger as \"untracked\". A setting on the Agents screen refuses those outright instead.<\/p>\n\n<p>All decisions are made by plain PHP on your server. The plugin makes no external service calls and collects no telemetry. The admin dashboard (Agent Firewall menu) runs on permission-checked REST endpoints. The Abilities API interception itself is what sets the WordPress 6.9 floor.<\/p>\n\n<h4>What this does and does not claim<\/h4>\n\n<p><strong>Every agent write that reaches WordPress is evaluated, and every one that succeeds is recorded.<\/strong> That is the promise, and it is deliberately narrower than \"blocks all AI attacks\".<\/p>\n\n<p>What it cannot see, stated plainly so you can judge the fit:<\/p>\n\n<ul>\n<li><strong>Writes that bypass WordPress.<\/strong> Anything with direct database access is invisible to any plugin, including this one.<\/li>\n<li><strong>What an agent decided.<\/strong> Prompt injection happens in the model, before a request exists. This governs what an agent <em>tries to do<\/em>, not what it was talked into wanting.<\/li>\n<li><strong>MCP servers that never touch your site.<\/strong> If a tool call is handled entirely elsewhere, there is nothing here to intercept.<\/li>\n<li><strong>Two rejections WordPress reports to nobody.<\/strong> An agent attempt refused before the action runs is now recorded with the \"refused\" status - a permission failure on every supported version, and malformed input from WordPress 7.1, the first version to expose that. Two cases stay invisible because core returns before any hook fires: an ability registered without a valid permission callback, and one whose input schema is missing entirely.<\/li>\n<\/ul>\n\n<p>Inside that boundary the guarantee is strict: no policy decision depends on a language model, nothing is sent anywhere, and the audit trail is tamper-evident rather than merely append-only. A security tool that overstates its reach is worse than one that draws the line clearly, so the line is drawn here.<\/p>\n\n<!--section=installation-->\n<ol>\n<li>Upload the plugin ZIP via Plugins &gt; Add New &gt; Upload, or copy the folder to <code>wp-content\/plugins\/<\/code>.<\/li>\n<li>Activate the plugin. Database tables are created on activation.<\/li>\n<li>Open the Agent Firewall admin menu. Until your first agent exists, a getting-started panel walks you through the setup: create an agent linked to a least-privilege user and issue its token.<\/li>\n<li>Adjust policies if needed. The policy list starts empty, but the built-in defaults already require approval for deletions and for any change to settings, accounts, roles, plugins, themes, templates, global styles and widgets. The Policies screen shows exactly what those defaults do.<\/li>\n<li>Give the token to your AI agent as an <code>Authorization: Bearer<\/code> header.<\/li>\n<\/ol>\n\n<!--section=faq-->\n<dl>\n<dt id=\"does%20this%20plugin%20call%20any%20external%20services%3F\"><h3>Does this plugin call any external services?<\/h3><\/dt>\n<dd><p>No. All policy decisions run as plain PHP on your server. Nothing leaves your site.<\/p><\/dd>\n<dt id=\"what%20happens%20to%20my%20data%20when%20i%20uninstall%3F\"><h3>What happens to my data when I uninstall?<\/h3><\/dt>\n<dd><p>Nothing, by default. The audit ledger is evidence, so it stays. If you want uninstall to remove all Agent Firewall tables and options, opt in first: <code>wp option update adaf_delete_data_on_uninstall 1<\/code>.<\/p><\/dd>\n<dt id=\"do%20agents%20need%20a%20special%20user%20account%3F\"><h3>Do agents need a special user account?<\/h3><\/dt>\n<dd><p>Yes. Each agent is linked to a WordPress user and can never exceed that user's capabilities; policies and scopes only restrict further. Give it a user with just the capabilities it needs. Administrators are refused outright: an agent inherits its user's capabilities, and an administrator-linked agent would hold the capability that governs the firewall itself. For the same reason no agent token can reach the firewall's own screens or REST routes, whatever its user may do - the only exception is polling the status of its own held action.<\/p><\/dd>\n<dt id=\"why%20does%20it%20require%20wordpress%206.9%3F\"><h3>Why does it require WordPress 6.9?<\/h3><\/dt>\n<dd><p>The Abilities API, the structured way AI agents act on WordPress, shipped in 6.9. Intercepting it is a core feature of this firewall.<\/p><\/dd>\n<dt id=\"how%20do%20i%20stop%20all%20agents%20immediately%3F\"><h3>How do I stop all agents immediately?<\/h3><\/dt>\n<dd><p>Use the kill switch: the \"Emergency stop: pause all agents\" button at the top of every Agent Firewall screen. While engaged, every agent action - reads included - is denied with a clear machine-readable reason, and every denial still lands in the audit ledger. Held approvals stay put; nothing is lost. Release it from the same place when the incident is over. It is a hard deny rather than hold-for-approval so a runaway agent cannot flood your approval queue while you investigate.<\/p><\/dd>\n<dt id=\"can%20an%20agent%20approve%20its%20own%20held%20actions%3F\"><h3>Can an agent approve its own held actions?<\/h3><\/dt>\n<dd><p>No. Approval endpoints require the <code>manage_options<\/code> capability. The firewall's own admin routes are exempt from agent gating precisely so a human can still step in when an agent is misbehaving.<\/p><\/dd>\n<dt id=\"what%20do%20agents%20see%20when%20an%20action%20is%20blocked%3F\"><h3>What do agents see when an action is blocked?<\/h3><\/dt>\n<dd><p>A structured error, not a bare refusal. Denials carry a <code>reason<\/code> (<code>policy<\/code>, <code>out_of_scope<\/code>, <code>rate_limited<\/code>, <code>untracked_automation<\/code>, <code>lockdown<\/code>), a <code>retryable<\/code> flag, and a plain-language explanation of what would change the answer. Rate limits say that waiting clears them; scope and policy denials say that retrying will not help and a site administrator has to act. Held actions report that they have not executed and should not be resubmitted. Rate-limit denials also carry a <code>retry_after<\/code> value and a standard <code>Retry-After<\/code> header saying when a slot frees, and held actions can be polled at <code>\/pending\/{id}\/status<\/code> with the agent's own token. Well-behaved agents act on this instead of hammering the endpoint.<\/p><\/dd>\n<dt id=\"are%20there%20any%20policies%20out%20of%20the%20box%3F\"><h3>Are there any policies out of the box?<\/h3><\/dt>\n<dd><p>The policy list starts empty, but the site is not unprotected. When no rule matches, the firewall falls back to built-in defaults: reads are allowed; creating or updating posts, pages and media is allowed; every deletion is held for approval; and any write to settings, user accounts, roles, plugins, themes, templates, global styles or widgets is held for approval even when it is a create or an update. Anything the firewall cannot categorize is held rather than allowed. The Policies screen lists these defaults so an empty policy list is not mistaken for no protection.<\/p><\/dd>\n<dt id=\"how%20do%20i%20write%20a%20policy%20rule%3F\"><h3>How do I write a policy rule?<\/h3><\/dt>\n<dd><p>Use the rule builder on the Policies screen, which offers only values that can actually occur, or switch it to JSON if you prefer. A rule matches on any combination of action category (create, update, delete, read), entity type, entity id, ability name, and source, and carries a decision: ALLOW, DENY, REQUIRE_APPROVAL, or LOG_ONLY. Rules are tried in order and the first match wins, so a rule with no match conditions must be last. A rule may also carry a sliding-window write limit instead of a decision.<\/p>\n\n<p>Each policy is either global or scoped to a single agent, chosen with the \"Applies to\" control and changeable later. At equal priority an agent's own policy is consulted before a global one.<\/p>\n\n<p>The full grammar is available over the API at <code>\/wp-json\/agenticdaisy-firewall\/v1\/policies\/schema<\/code>, which returns the JSON Schema, the accepted values for each dimension, and the defaults.<\/p><\/dd>\n<dt id=\"can%20i%20copy%20my%20policies%20to%20another%20site%3F\"><h3>Can I copy my policies to another site?<\/h3><\/dt>\n<dd><p>Yes. The Policies screen has \"Export policies (JSON)\" and \"Import policies\" buttons. Importing only ever adds: existing policies are never edited or deleted, and an entry identical to one you already have is skipped. Policies scoped to a single agent attach to an agent with the same name on the destination site; if none exists, the entry is skipped and the import report says so, because silently applying an agent-specific rule to every agent would change what it protects.<\/p><\/dd>\n<dt id=\"how%20do%20i%20check%20what%20a%20rule%20will%20actually%20do%3F\"><h3>How do I check what a rule will actually do?<\/h3><\/dt>\n<dd><p>Use \"Test an action\" on the Policies screen. Pick an action, an entity type and optionally an agent, and it reports what would happen and which rule decided it - or that no rule matched and a built-in default applied. It is a dry-run: nothing is written, no rate limit is consumed, and no agent is affected. This matters because rules are tried in order and the first match wins, so reading a list of policies does not always tell you which rule governs a given action.<\/p>\n\n<p>After the fact, the Activity Ledger's \"Decided by\" column reports the same thing for every action the firewall has already seen, so you can trace any verdict back to the rule that produced it. You can also filter the ledger by deciding policy, to see what one rule has actually been doing, or by \"Built-in defaults\" to see the actions no rule covered yet. The Policies screen lists any rule that has decided nothing at all, which catches a rule quietly shadowed by an earlier one - though a rule can also be unfired simply because nothing has attempted what it guards against, so it reports the fact rather than telling you to remove anything. If you have set a retention window, that list says so and names how far back the ledger still reaches, since a rule whose matches were pruned looks the same as one that never matched. That provenance is covered by the ledger's hash chain, so it cannot be rewritten without detection.<\/p><\/dd>\n<dt id=\"what%20happens%20if%20i%20write%20an%20invalid%20rule%3F\"><h3>What happens if I write an invalid rule?<\/h3><\/dt>\n<dd><p>It is rejected when you save it and nothing is stored. Structural problems, such as a decision that is not one of the recognized values, name the exact position of the error. Beyond that, the plugin rejects rules that would silently do nothing: a rule carrying neither a decision nor a limit, and any rule made unreachable by an earlier rule that already matches everything. If a stored rule is somehow unrecognizable at evaluation time, the action is held for approval; the engine never fails open.<\/p><\/dd>\n<dt id=\"can%20i%20stop%20the%20audit%20ledger%20growing%20forever%3F\"><h3>Can I stop the audit ledger growing forever?<\/h3><\/dt>\n<dd><p>Yes, but it is off by default because deleting audit records is not something a plugin should do to you unasked. Set a retention window under \"Ledger retention\" on the Activity Ledger screen and a daily job trims entries older than that. Because the ledger is hash-chained, pruning re-anchors the chain rather than breaking it, and every prune writes its own entry recording how many entries were removed and when - retention that left no trace of itself would be a way to erase an audit trail. Actions still waiting for approval always keep their records. Snapshots are removed with their entries, so actions older than the window can no longer be undone.<\/p>\n\n<p>Both jobs are also available to WP-CLI, along with a summary: <code>wp agent-firewall status<\/code> reports the ledger size, its oldest entry, whether the chain verifies and whether history has been trimmed; <code>wp agent-firewall verify<\/code> checks the chain and exits non-zero if it has been tampered with, so you can run it from cron and be told rather than having to look; <code>wp agent-firewall prune<\/code> applies the window, with <code>--dry-run<\/code> to see what would go first.<\/p><\/dd>\n<dt id=\"can%20i%20turn%20the%20alert%20emails%20off%3F\"><h3>Can I turn the alert emails off?<\/h3><\/dt>\n<dd><p>Yes. The Activity Ledger screen has a checkbox: \"Email me when an agent trips a rate limit or an ability changes definition\". It is on by default, sends only to the site administrator, and is throttled so a runaway loop produces one message rather than hundreds. Turning it off stops the mail; the anomalies are still recorded in the ledger either way. These are event alerts about your own site - the plugin sends no newsletters, digests or marketing of any kind.<\/p><\/dd>\n<dt id=\"my%20mcp%20client%20can%20only%20send%20a%20username%20and%20password.%20can%20it%20still%20be%20governed%3F\"><h3>My MCP client can only send a username and password. Can it still be governed?<\/h3><\/dt>\n<dd><p>Yes. Bind one of the linked user's application passwords to the agent, on the Agents screen. Many MCP clients authenticate with an application password over Basic auth and cannot send a custom <code>Authorization: Bearer<\/code> header; without a binding they arrive with no agent identity, so no policy can apply to them. A bound password identifies the agent exactly as its token would, and the same policies, scopes, rate limits and ledger entries apply.<\/p>\n\n<p>Create the password on that user's own profile in WordPress, then choose it from the picker. One password identifies one agent: a password another agent already holds is offered but not selectable. Deleting the password on the profile screen ends the binding, and the Agents screen says so.<\/p><\/dd>\n<dt id=\"what%20happens%20to%20automation%20that%20claims%20no%20agent%3F\"><h3>What happens to automation that claims no agent?<\/h3><\/dt>\n<dd><p>A write authenticated with an application password that no agent has claimed carries no identity, so there is nothing for a policy to apply to. By default it proceeds and is recorded in the Activity Ledger with the \"untracked\" status - it names the route, the action, and the acting WordPress user, and it joins the tamper-evident hash chain like every other entry.<\/p>\n\n<p>The Agents screen has a checkbox, \"Refuse writes from application passwords no agent claims\", which rejects them instead. Turning it on will also stop any existing integration that uses one, so bind the passwords you want to keep working first.<\/p><\/dd>\n<dt id=\"does%20this%20govern%20wp-cli%3F\"><h3>Does this govern WP-CLI?<\/h3><\/dt>\n<dd><p>No, and deliberately. A <code>wp<\/code> command runs with no HTTP request and no credential to identify, so no agent identity exists for it. It is also not a boundary worth defending: anyone who can run <code>wp<\/code> on the server can equally run <code>wp plugin deactivate agentic-daisy-ai-agent-firewall<\/code>. The firewall governs what arrives over HTTP - agent tokens, bound application passwords, the REST API, the Abilities API and MCP endpoints.<\/p><\/dd>\n\n<\/dl>\n\n<!--section=changelog-->\n<h4>1.6.0<\/h4>\n\n<ul>\n<li>New: an application password can be bound to an agent, so AI clients that can only send a username and password are governed like any other agent. Most MCP clients authenticate that way and cannot send a custom <code>Authorization: Bearer<\/code> header, so until now they arrived with no identity at all - every policy, scope and rate limit was skipped, and their writes were only recorded as \"untracked\". A bound password identifies the agent exactly as its token does, and the whole pipeline applies: per-call classification of MCP requests, scopes, policy, rate limiting, the audit ledger and undo. Bind one on the Agents screen, choosing from the linked user's own passwords by name. One password identifies one agent, so a password another agent already holds is shown but not selectable, and deleting it on the user's profile ends the binding, which the Agents screen reports rather than hiding.<\/li>\n<li>New: \"Refuse writes from application passwords no agent claims\" is now a checkbox on the Agents screen. The setting has existed since the first release, but the only way to reach it was to edit the database or read the source, so in practice nobody could refuse ungoverned automation who did not already know the option's name. Left off, such writes proceed and are ledgered as \"untracked\", as before. Turning it on will also stop any existing integration that uses an application password, so bind the ones you want to keep working first.<\/li>\n<li>Security: an application password bound to an agent that is disabled or revoked is now refused with a 401, rather than falling back to being recorded as untracked automation. Disabling an agent has to stop it; letting it keep writing with only a ledger note is not disabling it. A binding that points at an agent linked to a different WordPress user is refused for the same reason - it would run the request under capabilities the authenticating user never had.<\/li>\n<li>Improved: the documentation now says plainly that WP-CLI is not governed, and why. A <code>wp<\/code> command carries no request and no credential to identify, and it is not a boundary worth defending in any case: anyone who can run <code>wp<\/code> on the server can equally deactivate this plugin. This was previously an open question rather than a stated answer.<\/li>\n<\/ul>\n\n<h4>1.5.0<\/h4>\n\n<ul>\n<li>Security: agents can no longer reach the firewall's own controls. An agent is linked to a WordPress user and inherits that user's capabilities - so one linked to an administrator held the very permission that governs this plugin, and could have issued itself tokens, rewritten the policies judging it, approved its own held actions, or read the audit ledger recording them. The firewall governed every door except its own. Agent tokens are now refused on the plugin's own screens and routes outright, for reads as well as writes, whatever user they are linked to. The one exception is an agent asking whether its own held action has been decided, which it needs in order to wait rather than retry.<\/li>\n<li>Security: agents can no longer be linked to an administrator. Linking agents to a least-privilege user was always the documented advice; it is now enforced rather than suggested, and the check follows the capability rather than the role name, so a custom role carrying the same permission is refused too. If you have no suitable user yet, create one first - the error message says so. An agent linked to an administrator before this release keeps working and is flagged on the Agents screen so you can relink it; the change above already stops it reaching the controls in the meantime.<\/li>\n<li>Improved: choosing the WordPress user an agent is linked to is now a list of names rather than a user ID. The field asked for a number WordPress shows almost nowhere - awkward before, and unanswerable alongside the change above, since you would have had to guess an ID, be told it was refused, and guess again. The list offers only users an agent may actually be linked to, each shown with its role, so the least-privilege choice is visible rather than something to go and look up.<\/li>\n<li>Improved: the Agents screen names the WordPress user each agent is linked to, with that user's role, and links to their profile. It showed a bare user ID - \"#2\" - which identifies nobody, since WordPress surfaces user IDs almost nowhere; the name shown is now the same one you picked from the list when you created the agent. The role is shown alongside it because the linked user's capabilities are the ceiling on everything the agent can do. An agent whose linked user has since been deleted now says so instead of printing an ID that no longer resolves.<\/li>\n<li>Improved: the agent form explains what the identifier field is and calls it a slug rather than a key, which oversold it - nothing unlocks, and an agent never sends it. It is filled in from the name as you type, lowercased and hyphenated the way WordPress derives a post slug from a title, and suffixed if that slug is already taken instead of failing when you save. Editing it yourself stops the name driving it. The REST API takes <code>agent_slug<\/code> now and still accepts <code>agent_key<\/code>, returning both, so anything already scripted against it keeps working.<\/li>\n<li>Fixed: the screens no longer sit blank while they load. Every table now shows a spinner and the word \"Loading\" until its data arrives, and announces the result to screen readers the way WordPress announces its own. Two screens were worse than blank: Pending Approvals said \"No actions are waiting for review.\" and Policies said \"No policies yet\" from the moment they opened, before either had asked the server anything - so on a slower site the firewall spent the first few seconds telling you nothing needed your attention when it had no idea yet. The Activity Ledger and Agents screens, which had no such message, now say when they are genuinely empty.<\/li>\n<li>Fixed: \"Compare\" in the activity ledger took two clicks. The first only fetched the link and turned itself into a second link reading \"View what changed\", which had to be clicked in turn - nothing about the first click suggested it was only a preliminary. One click now opens the revision. The lookup is still made only when asked for, so a page of entries costs no extra queries for a link most people never follow.<\/li>\n<li>Fixed: the Undo button and the Compare link in the ledger sat flush against each other with no space and no shared baseline. They are now spaced and aligned, and wrap rather than overflow in a narrow column.<\/li>\n<li>Fixed: the three buttons in the first-run \"Get started\" panel did not line up, because each one sat directly below its own step and the steps run to different lengths. They now share a baseline.<\/li>\n<li>Fixed: the admin interface stayed in English on translated sites. Its on-screen text was never connected to WordPress's translation system, so the language packs the directory builds for this plugin were downloaded and then ignored. Everything the plugin renders now uses them.<\/li>\n<\/ul>\n\n<h4>1.4.0<\/h4>\n\n<ul>\n<li>New: the activity ledger says what an agent touched, not just its database id. A row that read \"page #218\" now names the page and links to it, and settings changes name the setting rather than reading only \"option\". The name is recorded at the moment the action is judged, so a page an agent deleted still shows what it was called - nothing can look up a deleted page afterwards. If the target has been renamed since, both names are shown: somebody renamed it, and that is worth seeing.<\/li>\n<li>New: themes, plugins, templates and widgets are named too. WordPress identifies these by name rather than by number, and that name was being discarded - so a row could say an agent changed a theme without saying which theme. Activating a plugin is among the most consequential things an agent can do to a site, and the record now says which one.<\/li>\n<li>New: allowed post edits offer a Compare link to the WordPress revision screen, showing exactly what the agent changed. It loads only when you ask for it, and is quietly absent when no revision was recorded - revisions can be switched off or trimmed away.<\/li>\n<li>Improved: the alert that warns you when an AI tool changes definition now shows the change. It named the ability and asked you to verify the change was expected, while storing only a fingerprint - so the previous wording was already gone and there was no way to check. Both versions of every changed field are now quoted, and a WordPress update that happened between checks is named, because it usually explains the change. That is offered as an explanation and not a clearance: any plugin can register a tool under a core-looking name.<\/li>\n<li>Improved: tool-registry changes are recorded in the activity ledger as well as emailed, so a change survives the email being deleted. They carry a \"drifted\" status of their own and can be filtered like any other entry.<\/li>\n<li>Fixed: activating the plugin on a fresh site could fail with \"headers already sent\", alongside warnings naming other plugins' translations as loading too early. The plugin scheduled its background sweeps while WordPress was still loading plugins, and asking WordPress for its list of schedules at that moment runs other plugins' code before they are ready. The sweeps are now scheduled a moment later, once WordPress has finished starting up. Only ever affected a first activation.<\/li>\n<\/ul>\n\n<h4>1.3.0<\/h4>\n\n<ul>\n<li>New: the number of actions waiting for your approval now shows in the WordPress admin menu, the way comments awaiting moderation do, and beside the Pending Approvals tab. A held action blocks the agent that asked for it and expires after 72 hours, so noticing one should not depend on opening the plugin first; an empty queue shows nothing at all.<\/li>\n<li>New: the activity ledger and the approval queue are paged, and both now say how many entries match what you are looking at. The ledger showed the twenty newest entries and the queue stopped at the first hundred held actions, in both cases without saying so - an audit log quietly showing you a fraction of itself. Every entry is now reachable, twenty, fifty or a hundred at a time.<\/li>\n<li>Improved: the activity ledger stays fast as it grows. Listing it newest-first had no index to work from, so drawing a page of a large ledger meant reading the whole table and sorting it; this release adds the index, applied to your site automatically.<\/li>\n<li>Fixed: database changes shipped in an update never reached sites that already had the plugin. WordPress reactivates a plugin silently after updating it, so the activation hook does not run - and that hook was the only thing that applied database changes. A site installed before an update kept the old structure indefinitely, and would have gone on doing so for every future release. The plugin now checks its own database version on load and migrates when it is behind, which is also how the index above reaches an existing site.<\/li>\n<\/ul>\n\n<h4>1.2.0<\/h4>\n\n<ul>\n<li>New: WordPress 7.1 support, verified against the release candidate. Two changes matter for what the firewall can promise there.<\/li>\n<li>New: a second interception door for abilities. WordPress 7.1 lets a plugin see every ability call before it runs, whoever registered the ability and whenever. Until now the firewall could only govern abilities registered after it loaded, because it works by wrapping their callbacks; one registered earlier - by a must-use plugin, say - was invisible to it. Such calls are now judged like any other, and a call already handled by the wrapper is still judged exactly once.<\/li>\n<li>New: refused attempts are now in the audit ledger. An agent trying an ability its user may not run, or sending input that does not fit, was refused by WordPress before the firewall ever saw the call - and left no trace, even though repeated refusals are exactly what enumeration looks like. Each one is now recorded with a \"refused\" status that reads distinctly from a policy denial, names which check refused it, and joins the hash chain like every other entry. Permission failures are recorded on every supported version; malformed input needs WordPress 7.1.<\/li>\n<li>Fixed: the Activity Ledger's status filter rejected \"untracked\" with an error instead of filtering by it. Every status the ledger can hold is now a valid filter.<\/li>\n<li>Fixed: the ability-registry monitor could be blinded on WordPress 7.1. The alert that warns you when an AI tool appears or changes definition read the registry through a helper that 7.1 lets any plugin filter, so a plugin could have hidden its own ability from the very check meant to notice it. The monitor now reads the registry directly.<\/li>\n<\/ul>\n\n<h4>1.1.0<\/h4>\n\n<ul>\n<li>New: MCP transport awareness. Agent tool calls that arrive as an MCP JSON-RPC envelope (its own POST, not a WordPress ability or a standard REST write) are now recognized on any route and evaluated per call, so <code>tools\/list<\/code> and a <code>tools\/call<\/code> that deletes an event are no longer indistinguishable. Protocol handshakes are treated as reads, tool names map to a policy action and entity, batches are judged call by call, and anything unparseable is held for approval rather than passing through. Blocked calls return a JSON-RPC error the agent can act on. Policy rules can target this door with <code>source: mcp<\/code>.<\/li>\n<li>New: effect backstop. An agent-authenticated write that reaches WordPress by a transport the firewall did not recognize is now caught at the mutation itself (posts, deletions, options, users, role and capability grants) and evaluated before it takes effect, so an unanticipated binding cannot slip a write past policy. It stays dormant unless an authenticated agent is acting, never re-evaluates a call already judged at another door, and records each catch distinctly as an unclassified-transport write. Rules can target it with <code>source: unclassified<\/code>.<\/li>\n<li>Improved: anomaly alert emails now name the site in the subject and body, so an administrator whose inbox serves several sites can tell at a glance which one alerted.<\/li>\n<\/ul>\n\n<h4>1.0.1<\/h4>\n\n<ul>\n<li>Fixed: repeated \"new ability\" alerts for abilities that were already known. An ability briefly absent from the registry (a plugin mid-auto-update, or one that registers conditionally) was dropped from the fingerprint baseline, so its return alerted as new every time. Absent abilities now keep their stored fingerprint: an unchanged return is silent, and a changed one still alerts as a definition change.<\/li>\n<\/ul>\n\n<h4>1.0.0<\/h4>\n\n<ul>\n<li>New: emergency kill switch. One control at the top of every screen denies all agent activity (reads included) until released; every denial is still ledgered, and agents receive a distinct non-retryable <code>lockdown<\/code> reason.<\/li>\n<li>New: policy export and import as portable JSON. Import is append-only, re-validates every rule, attaches agent-scoped policies by agent name, and reports anything it skipped.<\/li>\n<li>New: getting-started panel guides first-run setup until the first agent exists.<\/li>\n<li>Improved: ability-registry alerts now arrive as one digest email per scan instead of one email per ability, so a plugin update registering many abilities no longer floods the inbox.<\/li>\n<li>Improved: the Activity Ledger's \"Decided by\" column now names the kill switch and the token scope gate instead of attributing those denials to the built-in defaults.<\/li>\n<li>Fixed: opt-in uninstall now removes every plugin option (alerting, fingerprints, retention, lockdown, strict app-password mode) and clears the retention cron job.<\/li>\n<li>Fixed: application-password writes are now actually recorded. The readme promised they were \"flagged\", but in default mode they left no trace; each one now becomes an \"untracked\" entry in the audit ledger, naming the route, action and acting user, chained like every other entry.<\/li>\n<li>Fixed: a POST to the singleton \/wp\/v2\/settings route now classifies as an update, so policy rules matching update on options fire for the transport most clients use. Item routes addressed by slug (plugins, themes, sidebars, templates) also classify correctly.<\/li>\n<li>Improved: site-editor and appearance routes (global styles, templates, navigation, menus, widgets, blocks, plugins, themes) now map to named entities policy rules can target, instead of falling into \"unknown\". Templates, global styles and widgets join the held-for-approval defaults; menus and navigation are treated as routine content.<\/li>\n<\/ul>\n\n<h4>0.1.1<\/h4>\n\n<ul>\n<li>Security hardening: option snapshot capture and rollback restore are now strictly whitelist-bound. Input keys outside the settings map are never read into snapshots, and restore re-validates every option name before writing.<\/li>\n<\/ul>\n\n<h4>0.1.0<\/h4>\n\n<ul>\n<li>Initial release: agent identities with scoped tokens, Abilities API and REST interception, deterministic policy engine with rate limiting, approval queue with a state-drift guard, hash-chained audit ledger with secret redaction, revision\/trash\/snapshot rollback, tool-registry drift alerts, application-password write detection, React admin dashboard.<\/li>\n<\/ul>","raw_excerpt":"An AI agent firewall for WordPress: intercept every agent write, enforce policies, hold risky actions for approval, and undo anything in one click.","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/da.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin\/349096","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/da.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin"}],"about":[{"href":"https:\/\/da.wordpress.org\/plugins\/wp-json\/wp\/v2\/types\/plugin"}],"replies":[{"embeddable":true,"href":"https:\/\/da.wordpress.org\/plugins\/wp-json\/wp\/v2\/comments?post=349096"}],"author":[{"embeddable":true,"href":"https:\/\/da.wordpress.org\/plugins\/wp-json\/wporg\/v1\/users\/agenticdaisy"}],"wp:attachment":[{"href":"https:\/\/da.wordpress.org\/plugins\/wp-json\/wp\/v2\/media?parent=349096"}],"wp:term":[{"taxonomy":"plugin_section","embeddable":true,"href":"https:\/\/da.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_section?post=349096"},{"taxonomy":"plugin_tags","embeddable":true,"href":"https:\/\/da.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_tags?post=349096"},{"taxonomy":"plugin_category","embeddable":true,"href":"https:\/\/da.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_category?post=349096"},{"taxonomy":"plugin_contributors","embeddable":true,"href":"https:\/\/da.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_contributors?post=349096"},{"taxonomy":"plugin_business_model","embeddable":true,"href":"https:\/\/da.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_business_model?post=349096"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}