WordPress

Monitor a WordPress site: beam monitor on the web server's access log with the WordPress rules, and a small must-use plugin that logs sign-ins and role changes.

On this page

WordPress needs no code in the site for requests: nginx and Apache access logs are in the combined format Beam reads, with the status. A must-use plugin adds sign-ins and role changes.

What you'll have

  • Every request checked, with WordPress user enumeration and plugin exploits flagged, and probes for other stacks rolled up.
  • Sign-ins and role changes logged, so a password guessed after many failures is critical.
  • A routine for reading what comes in.

Before you start

Do steps 1 and 2 of the general cookbook first: App monitoring is on, the app is added, and its beam_app_ token is in your secret store as BEAM_APP_TOKEN. Everything below needs @agent-beam/beam 0.2.32 or newer.

Log sign-ins and role changes

Code
<?php
// wp-content/mu-plugins/beam-security.php
function beam_security_log(string $line): void {
    file_put_contents('/var/log/wordpress/beam-security.log', $line . "\n", FILE_APPEND | LOCK_EX);
}
function beam_client_ip(): string {
    return $_SERVER['REMOTE_ADDR'] ?? '';
}
add_action('wp_login', function (string $login, WP_User $user) {
    beam_security_log("login succeeded user={$user->ID} ip=" . beam_client_ip());
}, 10, 2);
add_action('wp_login_failed', function (string $username) {
    $user = get_user_by('login', $username) ?: get_user_by('email', $username);
    beam_security_log("login failed user=" . ($user ? $user->ID : '') . " ip=" . beam_client_ip());
});
add_action('set_user_role', function (int $user_id, string $role) {
    if ($role === 'administrator') beam_security_log("granted admin role to user={$user_id} by user=" . get_current_user_id());
}, 10, 2);
add_action('wp_create_application_password', function (int $user_id) {
    beam_security_log("API key created user={$user_id}");
});

Create /var/log/wordpress/ writable by the PHP user and not under the web root. Behind a proxy or CDN, take the client from the header it sets, and only when the request comes from it.

Run beam monitor next to the app

Code
npm install -g @agent-beam/beam
beam monitor --stack wordpress --file /var/log/nginx/access.log --file /var/log/wordpress/beam-security.log --app /srv/app

--stack wordpress turns on this framework's rules and rolls up attacks on frameworks you don't run; --app also checks the app's lockfiles daily (and would detect the stack on its own). Keep it running with systemd:

Code
# /etc/systemd/system/beam-monitor.service
[Unit]
Description=AgentBeam App monitoring
After=network-online.target

[Service]
EnvironmentFile=/etc/beam/app.env
ExecStart=/usr/bin/env beam monitor --stack wordpress --file /var/log/nginx/access.log --file /var/log/wordpress/beam-security.log --app /srv/app
Restart=always
User=beam

[Install]
WantedBy=multi-user.target
Code
sudo install -m 600 /dev/null /etc/beam/app.env && echo "BEAM_APP_TOKEN=beam_app_..." | sudo tee /etc/beam/app.env >/dev/null
sudo systemctl daemon-reload && sudo systemctl enable --now beam-monitor

In Docker or Kubernetes, run the same command in a sidecar that mounts the log directory read-only, with BEAM_APP_TOKEN from a secret.

Prove it works

Code
beam monitor status   # app, level, stack, usage
beam monitor test     # a test finding through your alert workflows
curl -s "https://your-site.example.com/?id=1%20UNION%20SELECT%201" -o /dev/null

Within a minute, the SQL injection test shows on App monitoring: under Needs action if the app answered 2xx, or counted under "needed nothing from you" if it refused it. Then sign in to a test account with a wrong password ten times and the right one once: Sign-in succeeded after 10 failed attempts appears as critical.

Dependencies

beam cve check reads composer.lock if you manage plugins with Composer (Bedrock, for example). Plugins installed from the dashboard have no lockfile to check: keep them updated, and remove ones you don't use.

What Beam watches for on this stack

Besides every built-in rule and the attack-in-progress signals:

RuleMeansSeverity
WordPress user enumeration/wp-json/wp/v2/users or ?author=1Medium
WordPress plugin file readKnown admin-ajax.php file-read actions (Revolution Slider, Duplicator and others)High
PHP-CGI argument injection and PHPUnitAs in the Laravel recipeCritical
Failed sign-in burstsMany login failed lines, including through xmlrpc.phpHigh

Attacks on other frameworks (WordPress probes, Laravel or Spring exploits, and so on) are rolled up as Not your stack when there's no status to say otherwise, and never alert.

Close what Beam would catch

Beam raises these when they're found; better that there's nothing to find:

  • Turn off XML-RPC if nothing uses it, and limit sign-in attempts.
  • Stop user enumeration (block ?author= and restrict the users REST endpoint).
  • Update core, themes and plugins; remove ones you don't use.
  • Block wp-config.php, backups and .git at the web server.

Coverage checklist

FeatureDone when
Access log watchedFindings show Answered or Refused
Stack declared--stack wordpress
Sign-in and role eventsThe must-use plugin writes to beam-security.log
AlertsCritical pages someone, high reaches a channel (step 8)
RoutineSomeone reads Needs action daily and Attack analytics weekly

Set this up with your coding agent

Install the agentbeam-setup skill for Claude Code, Codex, Cursor, Gemini CLI, OpenCode and Copilot, then ask your agent to set up AgentBeam on this machine. Beam scans the skill before it installs.

npx -y @agent-beam/beam add skill agentbeam-setup -g --all-agents
Explore the docs