{"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-10-22T13:20:55","modified_gmt":"2026-10-22T13:20:55","slug":"agentic-daisy-ai-agent-firewall","status":"publish","type":"plugin","link":"https:\/\/br.wordpress.org\/plugins\/agentic-daisy-ai-agent-firewall\/","author":23465430,"comment_status":"closed","ping_status":"closed","template":"","meta":{"version":"2.1.0","stable_tag":"2.1.0","tested":"7.1.3","requires":"6.9","requires_php":"8.1","requires_plugins":null,"header_name":"Agentic Daisy AI Agent Firewall - Approvals, Audit & Undo for MCP","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-10-22 13:20:55","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":917,"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},"1.7.0":{"tag":"1.7.0","author":"agenticdaisy","date":"2026-09-01 16:50:05","revision":3676523},"1.8.0":{"tag":"1.8.0","author":"agenticdaisy","date":"2026-09-11 05:31:29","revision":3690834},"1.8.1":{"tag":"1.8.1","author":"agenticdaisy","date":"2026-09-29 18:02:04","revision":3719575},"2.0.0":{"tag":"2.0.0","author":"agenticdaisy","date":"2026-10-19 12:15:35","revision":3754694},"2.0.1":{"tag":"2.0.1","author":"agenticdaisy","date":"2026-10-19 15:18:28","revision":3755067},"2.0.2":{"tag":"2.0.2","author":"agenticdaisy","date":"2026-10-19 21:13:02","revision":3755568},"2.0.3":{"tag":"2.0.3","author":"agenticdaisy","date":"2026-10-20 15:46:47","revision":3757028},"2.0.4":{"tag":"2.0.4","author":"agenticdaisy","date":"2026-10-20 19:01:57","revision":3757325},"2.1.0":{"tag":"2.1.0","author":"agenticdaisy","date":"2026-10-22 13:20:55","revision":3759975}},"upgrade_notice":{"2.1.0":"<p>Fixes a bypass in 2.0.x: a REST route typed in another casing could escape the rules that hold writes. Adds the add-on API, rules on facts and budgets, and non-administrator approvals. Adds database columns; going back to 2.0.x makes some ledger entries fail the daily check.<\/p>","2.0.4":"<p>Security fixes from a review of 2.0.x: an expired held action can no longer be approved, agents see only their own held actions, and the 2.0.3 database change is confirmed rather than assumed.<\/p>","2.0.3":"<p>Fixes false tamper alerts from the activity ledger&#039;s daily check, makes rate limits exact when an agent writes several things at once, and stops an interrupted approval from running twice. Adds a database column and index; existing ledgers keep verifying.<\/p>","2.0.2":"<p>Times in the admin now show in your site&#039;s timezone, and recent ones read &quot;30 minutes ago&quot;. An agent trying to use Agent Firewall&#039;s own controls is now recorded in the activity ledger.<\/p>","2.0.1":"<p>Each part of Agent Firewall is now its own page in the admin menu, with a new Settings page for alerts, webhooks and retention. Pages load faster, and links in earlier alerts still work.<\/p>","2.0.0":"<p>Security fixes: a stopped agent can no longer read the site, and an agent running as an administrator can no longer reach the plugin&#039;s own settings, ledger or policies. The audit ledger now checks itself daily. Paid Pro and Agency editions launch alongside; this free plugin loses nothing.<\/p>","1.8.1":"<p>Approving a held action now carries it out, and nothing WordPress would refuse is held any more. MCP clients using an unclaimed application password no longer flood the audit ledger. Longer display name; adds a database column.<\/p>","1.8.0":"<p>WooCommerce actions are classified correctly now: store lookups stop waiting in the approval queue, while price changes and deletions are held. Agent clients reading held-action responses from the Abilities API must branch on the <code>adaf_pending_approval<\/code> error code; the REST API is unchanged.<\/p>","1.7.0":"<p>Adds an Exposure Scan - what can write to this site, and a printable report for a client. Held actions now announce themselves by email or webhook rather than waiting for someone to open the dashboard. Both are off until you switch them on.<\/p>","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":{"blueprint.json":{"filename":"blueprint.json","revision":3759976,"resolution":false,"location":"assets","locale":"","contents":"{\"$schema\":\"https:\\\/\\\/playground.wordpress.net\\\/blueprint-schema.json\",\"meta\":{\"title\":\"Agent Firewall demo\",\"description\":\"A shop site where an AI agent's edit was allowed, two deletions wait for approval, a read-only agent was refused, and an approval was stopped because a person had changed the page first.\",\"author\":\"agenticdaisy\",\"categories\":[\"Security\"]},\"landingPage\":\"\\\/wp-admin\\\/admin.php?page=agentic-daisy-ai-agent-firewall&tab=pending\",\"preferredVersions\":{\"php\":\"8.3\",\"wp\":\"latest\"},\"features\":{\"networking\":false},\"steps\":[{\"step\":\"installPlugin\",\"pluginData\":{\"resource\":\"wordpress.org\\\/plugins\",\"slug\":\"agentic-daisy-ai-agent-firewall\"},\"options\":{\"activate\":true},\"ifAlreadyInstalled\":\"skip\"},{\"step\":\"login\",\"username\":\"admin\",\"password\":\"password\"},{\"step\":\"runPHP\",\"code\":\"<?php\\n\\\/\\\/ Agent Firewall demo, step 1 of 4: a small site and two agents, created\\n\\\/\\\/ through the plugin's own REST API as the site administrator.\\nrequire '\\\/wordpress\\\/wp-load.php';\\nwp_set_current_user( 1 );\\nupdate_option( 'blogname', 'Corner Shop (Agent Firewall demo)' );\\n\\n$editor = wp_insert_user( array( 'user_login' => 'claude', 'user_pass' => wp_generate_password(), 'role' => 'editor', 'display_name' => 'Claude (Editor)' ) );\\n$posts  = array(\\n\\t'sale'     => wp_insert_post( array( 'post_title' => 'Spring sale: 20% off', 'post_content' => 'Everything in store is 20% off this week.', 'post_status' => 'publish' ) ),\\n\\t'about'    => wp_insert_post( array( 'post_title' => 'About us', 'post_content' => 'A small shop on the corner since 1998.', 'post_status' => 'publish', 'post_type' => 'page' ) ),\\n\\t'shipping' => wp_insert_post( array( 'post_title' => 'Shipping policy', 'post_content' => 'Orders ship within 2 days.', 'post_status' => 'publish' ) ),\\n);\\n\\n$api = static function ( string $method, string $route, array $body = array() ) {\\n\\t$request = new WP_REST_Request( $method, $route );\\n\\t$request->set_body_params( $body );\\n\\treturn rest_do_request( $request )->get_data();\\n};\\n\\n$claude   = $api( 'POST', '\\\/agenticdaisy-firewall\\\/v1\\\/agents', array( 'name' => 'Claude Desktop', 'agent_key' => 'claude-desktop', 'wp_user_id' => $editor ) );\\n$reporter = $api( 'POST', '\\\/agenticdaisy-firewall\\\/v1\\\/agents', array( 'name' => 'Weekly report bot', 'agent_key' => 'weekly-report-bot', 'wp_user_id' => $editor, 'scopes' => array( 'read' ) ) );\\n\\n\\\/\\\/ Kept for the next steps, and deleted by the last one.\\nupdate_option(\\n\\t'adaf_demo_seed',\\n\\tarray(\\n\\t\\t'posts'  => $posts,\\n\\t\\t'tokens' => array(\\n\\t\\t\\t'claude'   => $api( 'POST', \\\"\\\/agenticdaisy-firewall\\\/v1\\\/agents\\\/{$claude['id']}\\\/token\\\" )['token'],\\n\\t\\t\\t'reporter' => $api( 'POST', \\\"\\\/agenticdaisy-firewall\\\/v1\\\/agents\\\/{$reporter['id']}\\\/token\\\" )['token'],\\n\\t\\t),\\n\\t),\\n\\tfalse\\n);\\n\"},{\"step\":\"runPHP\",\"code\":\"<?php\\n\\\/\\\/ Step 2: Claude, with its own token, through the real firewall. The edit\\n\\\/\\\/ is allowed and can be undone from the ledger; both deletions are held.\\nrequire '\\\/wordpress\\\/wp-load.php';\\n$seed = get_option( 'adaf_demo_seed' );\\n\\n\\\/\\\/ Present the agent's token and have WordPress work out who is calling\\n\\\/\\\/ again, exactly as it does for a request that arrives with one.\\n$_SERVER['HTTP_AUTHORIZATION'] = 'Bearer ' . $seed['tokens']['claude'];\\n$GLOBALS['current_user']       = null;\\nwp_get_current_user();\\n\\n$send = static function ( string $method, string $route, array $body = array() ): void {\\n\\t$request = new WP_REST_Request( $method, $route );\\n\\t$request->set_body_params( $body );\\n\\trest_do_request( $request );\\n};\\n$posts = $seed['posts'];\\n$send( 'POST', \\\"\\\/wp\\\/v2\\\/posts\\\/{$posts['sale']}\\\", array( 'title' => 'Spring sale: 30% off everything' ) );\\n$send( 'DELETE', \\\"\\\/wp\\\/v2\\\/pages\\\/{$posts['about']}\\\" );\\n$send( 'DELETE', \\\"\\\/wp\\\/v2\\\/posts\\\/{$posts['shipping']}\\\" );\\n\"},{\"step\":\"runPHP\",\"code\":\"<?php\\n\\\/\\\/ Step 3: a read-only agent tries to publish. Its token's scopes refuse it\\n\\\/\\\/ before any policy runs.\\nrequire '\\\/wordpress\\\/wp-load.php';\\n$seed = get_option( 'adaf_demo_seed' );\\n\\n\\\/\\\/ Present the agent's token and have WordPress work out who is calling\\n\\\/\\\/ again, exactly as it does for a request that arrives with one.\\n$_SERVER['HTTP_AUTHORIZATION'] = 'Bearer ' . $seed['tokens']['reporter'];\\n$GLOBALS['current_user']       = null;\\nwp_get_current_user();\\n\\n$request = new WP_REST_Request( 'POST', '\\\/wp\\\/v2\\\/posts' );\\n$request->set_body_params( array( 'title' => 'Weekly report', 'status' => 'publish' ) );\\nrest_do_request( $request );\\n\"},{\"step\":\"runPHP\",\"code\":\"<?php\\n\\\/\\\/ Step 4: a person updates the shipping policy while Claude's deletion of it\\n\\\/\\\/ waits, then someone approves the deletion. The firewall sees the page\\n\\\/\\\/ changed since it was held and refuses to act on stale intent.\\nrequire '\\\/wordpress\\\/wp-load.php';\\n$seed = get_option( 'adaf_demo_seed' );\\nwp_set_current_user( 1 );\\n\\nglobal $wpdb;\\n$shipping = (int) $seed['posts']['shipping'];\\n$wpdb->update(\\n\\t$wpdb->posts,\\n\\tarray(\\n\\t\\t'post_content'      => 'Orders now ship within 1 day.',\\n\\t\\t'post_modified'     => gmdate( 'Y-m-d H:i:s', time() + 60 ),\\n\\t\\t'post_modified_gmt' => gmdate( 'Y-m-d H:i:s', time() + 60 ),\\n\\t),\\n\\tarray( 'ID' => $shipping )\\n);\\nclean_post_cache( $shipping );\\n\\nforeach ( rest_do_request( new WP_REST_Request( 'GET', '\\\/agenticdaisy-firewall\\\/v1\\\/pending' ) )->get_data() as $held ) {\\n\\tif ( 'rest:delete:post' === $held['ability_name'] ) {\\n\\t\\trest_do_request( new WP_REST_Request( 'POST', \\\"\\\/agenticdaisy-firewall\\\/v1\\\/pending\\\/{$held['id']}\\\/approve\\\" ) );\\n\\t}\\n}\\n\\ndelete_option( 'adaf_demo_seed' );\\n\"}]}"}},"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","1.7.0","1.8.0","1.8.1","2.0.0","2.0.1","2.0.2","2.0.3","2.0.4","2.1.0"],"block_files":[],"assets_screenshots":{"screenshot-1.png":{"filename":"screenshot-1.png","revision":3755065,"resolution":"1","location":"assets","locale":"","width":1400,"height":900},"screenshot-2.png":{"filename":"screenshot-2.png","revision":3755065,"resolution":"2","location":"assets","locale":"","width":1400,"height":900},"screenshot-3.png":{"filename":"screenshot-3.png","revision":3755065,"resolution":"3","location":"assets","locale":"","width":1400,"height":1033},"screenshot-4.png":{"filename":"screenshot-4.png","revision":3755065,"resolution":"4","location":"assets","locale":"","width":1400,"height":2575},"screenshot-5.png":{"filename":"screenshot-5.png","revision":3755065,"resolution":"5","location":"assets","locale":"","width":1400,"height":1509},"screenshot-6.png":{"filename":"screenshot-6.png","revision":3755065,"resolution":"6","location":"assets","locale":"","width":1400,"height":900}},"screenshots":{"1":"The activity ledger: every agent action, the rule that decided it, and a one-click undo.","2":"The approval queue: each held action names what it would change and shows its full payload, and waits until a person approves or rejects it.","3":"Agents: each one tied to a least-privilege user, with a token shown once or a bound application password.","4":"Policies: allow, deny, log or hold by action, content type or ability, with the built-in defaults spelled out and a dry-run to test any action against them.","5":"Exposure scan: everything that can write to this site and who is accountable for it, with a printable report for a client.","6":"Settings: alert emails, a signed webhook for Slack, Discord or Google Chat, and how long the ledger keeps its history."}},"plugin_section":[],"plugin_tags":[256265,232494,8534,242115,600],"plugin_category":[54],"plugin_contributors":[262708],"plugin_business_model":[],"class_list":["post-349096","plugin","type-plugin","status-publish","hentry","plugin_tags-abilities-api","plugin_tags-ai-agent","plugin_tags-audit-log","plugin_tags-mcp","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=3755065","caption":"The activity ledger: every agent action, the rule that decided it, and a one-click undo."},{"src":"https:\/\/ps.w.org\/agentic-daisy-ai-agent-firewall\/assets\/screenshot-2.png?rev=3755065","caption":"The approval queue: each held action names what it would change and shows its full payload, and waits until a person approves or rejects it."},{"src":"https:\/\/ps.w.org\/agentic-daisy-ai-agent-firewall\/assets\/screenshot-3.png?rev=3755065","caption":"Agents: each one tied to a least-privilege user, with a token shown once or a bound application password."},{"src":"https:\/\/ps.w.org\/agentic-daisy-ai-agent-firewall\/assets\/screenshot-4.png?rev=3755065","caption":"Policies: allow, deny, log or hold by action, content type or ability, with the built-in defaults spelled out and a dry-run to test any action against them."},{"src":"https:\/\/ps.w.org\/agentic-daisy-ai-agent-firewall\/assets\/screenshot-5.png?rev=3755065","caption":"Exposure scan: everything that can write to this site and who is accountable for it, with a printable report for a client."},{"src":"https:\/\/ps.w.org\/agentic-daisy-ai-agent-firewall\/assets\/screenshot-6.png?rev=3755065","caption":"Settings: alert emails, a signed webhook for Slack, Discord or Google Chat, and how long the ledger keeps its history."}],"raw_content":"<!--section=description-->\n<p><strong>Install this before you give an AI agent an application password.<\/strong><\/p>\n\n<p>Connecting Claude, ChatGPT, Cursor or any other MCP client to WordPress hands it a real login. From then on it can edit, publish, change settings and delete as fast as it can act - including when it has misunderstood you, or been talked into something by text it read along the way. Agent Firewall sits between those agents and your site:<\/p>\n\n<ul>\n<li><strong>Every agent has its own identity<\/strong>, tied to a least-privilege WordPress user, so you always know which one did what.<\/li>\n<li><strong>Risky writes wait for you.<\/strong> Deletions and changes to settings, users, plugins and themes are held for your approval out of the box, while everyday content edits keep flowing.<\/li>\n<li><strong>No stale approvals.<\/strong> If a person edits the content while an agent's change is waiting, approving it aborts instead of overwriting the newer work.<\/li>\n<li><strong>Every action is on the record<\/strong>, in a tamper-evident audit ledger that names the rule behind each decision.<\/li>\n<li><strong>Mistakes undo in one click<\/strong>, from revisions, the trash, or stored snapshots.<\/li>\n<li><strong>You hear about trouble<\/strong>, by email or in Slack, Discord or Google Chat, and one button pauses every agent at once.<\/li>\n<\/ul>\n\n<p>It does not replace your MCP server. Keep the official MCP Adapter or whichever MCP plugin you already use; Agent Firewall governs what arrives through it, whether that is a WordPress ability, a REST API call or an MCP tool call.<\/p>\n\n<h4>Pro and Agency<\/h4>\n\n<p>Everything else on this page is free, with no limits, and stays free. The paid editions add tools for people answering to clients, and for agencies running many sites:<\/p>\n\n<ul>\n<li>An AI evaluator that reviews the actions your rules send to it, approves routine content edits a person could undo, and holds everything else for you <em>(Pro+)<\/em><\/li>\n<li>Ledger CSV export, with the chain hashes that prove nothing was altered, for the evidence clients and auditors ask for <em>(Pro+)<\/em><\/li>\n<li>A digest email when something needs a person, and a weekly all-clear when nothing does, to the recipients you choose <em>(Pro+)<\/em><\/li>\n<li>Exposure-scan history: each scan shows what is new, resolved and changed since the last one <em>(Pro+)<\/em><\/li>\n<li>Network rules every site must follow, one switch that stops agents across the network, network-wide activity, and agents created on many sites at once, each with its own token <em>(Agency+)<\/em><\/li>\n<li>Webhook routing: send chosen events from chosen agents to several destinations, including Microsoft Teams, with retries and a delivery log <em>(Agency+)<\/em><\/li>\n<li>Starter policy packs for publisher, store and membership sites, previewed before they apply and removable as a group <em>(Agency+)<\/em><\/li>\n<\/ul>\n\n<p>Pro and Agency are sold at <a href=\"https:\/\/agenticdaisy.com\/agent-firewall\/#pricing\">agenticdaisy.com<\/a>, with a 14-day free trial and no card needed.<\/p>\n\n<h4>How it works<\/h4>\n\n<p><strong>This plugin restricts AI agents; it is not an AI tool.<\/strong> It neither generates nor executes code, and every decision is made by deterministic PHP running locally against rules you write. Nothing leaves your site unless you ask for it: the only request it can make outward is to an alert webhook address you enter yourself.<\/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 just before their handler runs - after WordPress has checked the linked user's permissions and validated the input - so switching transports doesn't dodge the rules, and nothing WordPress would refuse anyway ever waits for your approval. A REST write WordPress refuses is still recorded, with the \"refused\" status - up to 20 per agent for each kind of target every ten minutes, so an agent hammering a door it cannot open cannot flood the ledger, while one that turns to a different kind of target is still seen doing so. Policies match on ability name, action category, entity type or id, source, and whether the ability declares its own action destructive, 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 - or a message in Slack, Google Chat or Discord, if you give it a webhook address.<\/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. (That check covers posts, pages and media, which record when they last changed; users, terms, plugins and settings do not, so for them the queue cannot tell whether someone got there first.) If you have set a webhook address, each held action announces itself there with a link straight to the queue, so nothing waits on someone happening to open the dashboard.<\/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 alerts 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 collects no telemetry and calls no third-party service; the one outbound request it can make is to the alert webhook you configure, if you configure one. 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>A plugin that answers a request first.<\/strong> Another plugin can hook the same point in WordPress's request handling at the same priority and answer a write itself before the firewall sees it. Nothing in WordPress lets one plugin guarantee it goes first, so such a write is recorded afterward with the \"bypassed\" status and counted against the agent's rate limit, rather than passing unseen.<\/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 but an alert endpoint you chose yourself, and the audit trail is tamper-evident rather than merely append-only.<\/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=\"can%20i%20try%20it%20without%20installing%20it%3F\"><h3>Can I try it without installing it?<\/h3><\/dt>\n<dd><p>Yes. <a href=\"https:\/\/playground.wordpress.net\/?blueprint-url=https%3A%2F%2Fwordpress.org%2Fplugins%2Fwp-json%2Fplugins%2Fv1%2Fplugin%2Fagentic-daisy-ai-agent-firewall%2Fblueprint.json\">Open the live demo<\/a> - or use the Live Preview button on this page, where it is shown - for a throwaway WordPress site in your browser, running in WordPress Playground, with the plugin installed and a small shop already set up. An AI agent has been at work on it: one of its edits was allowed and can be undone from the Activity Ledger, a page deletion is waiting for your approval, a second deletion was stopped because a person had changed the post first, and a read-only agent was refused for reaching beyond its scope. You land on the approval queue - approve or reject the waiting deletion and see what happens. Nothing is installed on your own site, and reloading the preview starts over.<\/p><\/dd>\n<dt id=\"does%20this%20plugin%20call%20any%20external%20services%3F\"><h3>Does this plugin call any external services?<\/h3><\/dt>\n<dd><p>Not unless you ask it to. All policy decisions run as plain PHP on your server, and by default nothing leaves your site. The one exception is the optional webhook: if you save an endpoint under \"Webhook URL\" on the Settings page (Agent Firewall &gt; Settings), four events are posted there as JSON - an action held for your approval, an agent tripping its rate limit, an agent denied for reaching outside its scopes or while the kill switch is engaged, and an AI tool changing definition. Each delivery carries what the decision was about and nothing more: the agent's name and identifier, the action category, the kind of entity and its id, the ability names involved, your site's address, the time, the plugin version, and a link back to your own dashboard. The content of a post, an option or an agent's payload is never included, and neither is any credential. You choose that address, clearing the field stops it, and there is no phone-home, no analytics and no third-party service behind any of it.<\/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>. On a multisite network each site decides for itself: deleting the plugin from the network removes the data of every site that opted in (<code>wp option update adaf_delete_data_on_uninstall 1 --url=&lt;site&gt;<\/code>) and keeps the rest.<\/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 not offered: 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 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>\n\n<p>The one exception is an MCP plugin whose keys can only run as an administrator. An administrator can confirm that an agent runs as one, and then only the add-on that knows that plugin's keys can sign the agent in; it never gets a token or an application password. Even then, the agent cannot deactivate Agent Firewall or change its settings, and its changes to plugins, themes, users, roles, settings and site templates, anything carrying scripts, and anything the firewall cannot identify wait for a person. No plugin can guard against a tool that runs SQL, PHP or shell commands for an agent, so keep such tools switched off.<\/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=\"what%20happens%20when%20i%20approve%20a%20held%20action%3F\"><h3>What happens when I approve a held action?<\/h3><\/dt>\n<dd><p>The request the agent sent is sent again, exactly as it was - the same route, the same fields - and it runs as the agent's linked user, not as you. WordPress checks it afresh: if that user is not allowed to make the change, it is refused, the action is marked \"refused\", and it leaves the queue, because approving it again would be refused again. If WordPress rejects it for another reason, such as an invalid value, it stays in the queue for you to reject. An approved change can be undone from the Activity Ledger wherever an allowed one can - edits and deletions of posts, pages and media.<\/p>\n\n<p>A few held actions cannot be sent again, and the queue says so instead of offering Approve: a file upload, whose file is not kept while it waits; a write caught inside another plugin's code rather than arriving as a request; and an MCP tool that is not backed by a WordPress ability. Reject those, and make the change yourself if you want it.<\/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> (such as <code>policy<\/code>, <code>out_of_scope<\/code>, <code>rate_limited<\/code>, <code>budget_exceeded<\/code>, <code>untracked_automation<\/code> or <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; over the Abilities API that arrives as the error code <code>adaf_pending_approval<\/code>, carrying the id to poll, because WordPress checks an ability's return against the output the ability says it produces and \"held for review\" matches no ability's declared output. 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: anything the ability itself declares destructive is held for approval; 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, source, and whether the ability declares itself destructive, 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 Settings page (Agent Firewall &gt; Settings) 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 Settings page (Agent Firewall &gt; Settings) 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. The same checkbox covers the daily check of the audit ledger: if its tamper-evident chain stops verifying - an entry changed, inserted or removed outside the plugin - you are emailed once, with the first entry that fails, and reminded daily until it verifies again. 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 or marketing of any kind.<\/p><\/dd>\n<dt id=\"can%20i%20be%20emailed%20when%20something%20is%20waiting%20for%20my%20approval%3F\"><h3>Can I be emailed when something is waiting for my approval?<\/h3><\/dt>\n<dd><p>Yes, with a second checkbox on the same page: \"Email me when an action is held for my approval\". It is <strong>off by default<\/strong>, and stays off when you update - the plugin will not start emailing you because of an upgrade, only because you ticked the box. Switched on, you get one message per held action, addressed to the site administrator, with a link that opens the approval queue directly - and, if anything has waited more than a day, one reminder a day until the queue is dealt with, well before held actions expire. It is a separate switch from the alert one above, so turning either off leaves the other alone. Held actions are listed on the Pending Approvals screen and counted in the admin menu whether or not you enable the mail.<\/p>\n\n<p>Denials are not emailed at all. An agent refused for reaching outside its scopes, or while the kill switch is engaged, is reported to your webhook if you have one - that is information you may want to watch, rather than something waiting on you, and a denied agent in a loop would fill an inbox you cannot mute.<\/p><\/dd>\n<dt id=\"can%20the%20alerts%20go%20to%20slack%20instead%20of%20email%3F\"><h3>Can the alerts go to Slack instead of email?<\/h3><\/dt>\n<dd><p>Yes. Save your incoming webhook URL under \"Webhook URL\" on the Settings page (Agent Firewall &gt; Settings), then press \"Send a test message\" to confirm it arrives rather than finding out during an incident. Six things are delivered: an action held for your approval, held actions still waiting after a day, an agent tripping its rate limit, an agent denied for reaching outside its scopes or while the kill switch is engaged, an AI tool changing definition, and the audit ledger failing its daily verification. Denials by policies you wrote are deliberately not sent - those are the firewall doing its job, and an agent stuck in a loop would bury everything else. The message line is sent as both \"text\" and \"content\", which is what Slack, Google Chat and Discord incoming webhooks read, so those work with nothing else configured; n8n, a log collector or an endpoint of your own get the structured fields - event, site, timestamp and the details - in the same JSON. Microsoft Teams is the exception: the connector webhooks that accepted a plain message were switched off in May 2026, and the Workflows endpoints that replaced them want a card-shaped body, so a Teams channel needs something in between for now. Every delivery is signed as an X-ADAF-Signature header holding a sha256 HMAC of the body, using the secret shown once you save an endpoint, so the receiving end can tell a real delivery from anyone who learned the URL. Email and webhook are independent: turning one off leaves the other alone, and both are throttled the same way, so a runaway loop is one message rather than hundreds.<\/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%20woocommerce%3F\"><h3>Does this govern WooCommerce?<\/h3><\/dt>\n<dd><p>Yes, and there is nothing WooCommerce-specific to configure. WooCommerce registers seven abilities in core - product and order queries, product create, update and delete, order status changes and order notes - and declares for each whether it is read-only and whether it is destructive. The firewall reads those declarations, so store lookups are allowed while changing a price, changing an order's status and deleting a product are held for a person. One rule covers all of it: <code>{\"match\": {\"destructive\": true}, \"decision\": \"REQUIRE_APPROVAL\"}<\/code>. WooCommerce does not expose refunds as an ability; a refund made through its REST API is evaluated there like any other write.<\/p><\/dd>\n<dt id=\"are%20writes%20made%20through%20wp-cli%20policed%3F\"><h3>Are writes made through WP-CLI policed?<\/h3><\/dt>\n<dd><p>No, and deliberately. (The plugin's own <code>wp agent-firewall<\/code> commands, above, are a separate thing - those are for you, not for agents.) A <code>wp<\/code> command runs with no HTTP request and no credential to identify, so no agent identity exists for it and no policy can attach. 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>2.1.0<\/h4>\n\n<ul>\n<li>Security: a REST request whose route was typed in another casing, such as <code>\/WP\/v2\/users<\/code>, reached the right WordPress screen but was not recognized, so the built-in defaults could allow writes they should hold, such as creating a user. Routes are now recognized in any casing.<\/li>\n<li>New: add-ons can extend the firewall. An add-on such as Agent Firewall for WooCommerce names the actions it understands (a refund, a price change), supplies facts about them (the amount, the change in percent), holds writes made deep inside its own code, and can let a capability such as shop manager approve its holds.<\/li>\n<li>New: policy rules can test those facts (\"refund amount is more than 100\") and set budgets (\"no more than 500 in refunds a day per agent\"), in the rule builder.<\/li>\n<li>New: approvals can belong to people who are not administrators, for the actions an add-on assigns to them. An optional setting stops anyone approving actions taken by an agent linked to their own account.<\/li>\n<li>New: an agent can run as an administrator, for MCP plugins whose keys can only run as one, when an administrator confirms it. Only the add-on that knows the key can sign it in, and Agent Firewall still refuses to be switched off by it and holds its changes to plugins, users and settings for a person.<\/li>\n<li>New: the WordPress MCP adapter's execute-ability tool is judged as the ability it runs, so your rules apply to it and approving its hold carries it out.<\/li>\n<li>New: the exposure scan can include an add-on's own checks, and an add-on can link to the activity ledger for one order or other item.<\/li>\n<li>Fixed: an agent editing a post whose long title is written in a script such as Japanese or Chinese was refused every time, as if the ledger were busy. Long titles are now shortened between characters, not inside one.<\/li>\n<li>Fixed: removing the plugin with its data, after opting in, left seven settings behind, the alert webhook's secret among them.<\/li>\n<li>Note: 2.1 adds database columns. If you go back to an earlier version, ledger entries that record add-on facts will fail the ledger's daily check.<\/li>\n<\/ul>\n\n<h4>2.0.4<\/h4>\n\n<ul>\n<li>Fixed: a held action past its expiry time could still be approved and run until the hourly cleanup marked it expired, which on a quiet site can be hours late. It is now refused and expired the moment someone tries to approve it.<\/li>\n<li>Fixed: an agent whose WordPress user is an administrator could check the status of other agents' held actions. Agents now see only their own.<\/li>\n<li>Fixed: if the database change made by 2.0.3 did not complete, for example because the database user may not add indexes, it was still recorded as done and never tried again. It is now checked, and tried again until it completes.<\/li>\n<\/ul>\n\n<h4>2.0.3<\/h4>\n\n<ul>\n<li>Fixed: two actions recorded at the same moment could make the activity ledger fail its daily check, reporting tampering that never happened, and go on reporting it every day. Ledger entries are now written strictly one after another. A ledger that already failed this way keeps failing at that entry; if the alert began without anyone touching the database, this is the likely cause.<\/li>\n<li>Improved: when the ledger fails its daily check, the alert email, the webhook, the Pro digest and <code>wp agent-firewall verify<\/code> now say what was found: entries removed, an entry inserted, or an entry changed.<\/li>\n<li>Fixed: an agent sending several writes at once could go a few writes past its rate limit. The limit now holds exactly.<\/li>\n<li>Changed: if the activity ledger cannot record an action, for example while the database is very busy, the action is refused and the agent is asked to retry in a second (reason <code>ledger_busy<\/code>), rather than run with no record.<\/li>\n<li>Fixed: an approved action cut off part-way, for example by a server timeout, could go back to the approval queue and run a second time if approved again. It is now marked interrupted, never run again, and the ledger explains that it may or may not have taken effect.<\/li>\n<li>Improved: recording an agent's refused attempts no longer slows down as its history grows.<\/li>\n<li>Pro and Agency: the digest reports interrupted approvals and attempts on the firewall's own controls, the AI evaluator counts interrupted actions in an agent's history, and the Network Activity filter offers every status.<\/li>\n<\/ul>\n\n<p>Earlier releases are listed in <a href=\"https:\/\/plugins.svn.wordpress.org\/agentic-daisy-ai-agent-firewall\/trunk\/changelog.txt\">changelog.txt<\/a>, which ships with the plugin.<\/p>","raw_excerpt":"AI agent firewall for MCP and the Abilities API: hold risky agent writes for your approval, audit every action, and undo mistakes in one click.","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/br.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin\/349096","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/br.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin"}],"about":[{"href":"https:\/\/br.wordpress.org\/plugins\/wp-json\/wp\/v2\/types\/plugin"}],"replies":[{"embeddable":true,"href":"https:\/\/br.wordpress.org\/plugins\/wp-json\/wp\/v2\/comments?post=349096"}],"author":[{"embeddable":true,"href":"https:\/\/br.wordpress.org\/plugins\/wp-json\/wporg\/v1\/users\/agenticdaisy"}],"wp:attachment":[{"href":"https:\/\/br.wordpress.org\/plugins\/wp-json\/wp\/v2\/media?parent=349096"}],"wp:term":[{"taxonomy":"plugin_section","embeddable":true,"href":"https:\/\/br.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_section?post=349096"},{"taxonomy":"plugin_tags","embeddable":true,"href":"https:\/\/br.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_tags?post=349096"},{"taxonomy":"plugin_category","embeddable":true,"href":"https:\/\/br.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_category?post=349096"},{"taxonomy":"plugin_contributors","embeddable":true,"href":"https:\/\/br.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_contributors?post=349096"},{"taxonomy":"plugin_business_model","embeddable":true,"href":"https:\/\/br.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_business_model?post=349096"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}