Skip to content

Protect Dashboard: Add the Firewall section - #53193

Open
enejb wants to merge 16 commits into
trunkfrom
add/protect-dashboard-firewall
Open

enejb wants to merge 16 commits into
trunkfrom
add/protect-dashboard-firewall

Conversation

@enejb

@enejb enejb commented Oct 6, 2026 •

Copy link
Copy Markdown
Member

Fixes JETPACK-2957

Part of JETPACK-2882

Proposed changes

  • Adds the Firewall section to the flag-gated Protect dashboard (packages/protect/src/sections/class-firewall.php + packages/protect/routes/dashboard/sections/firewall/).
  • PHP state: the waf module's availability and on/off state, the all-time blocked request count from Waf_Blocklog_Manager::get_all_time_block_count() (sent even while the firewall is off, since the counter survives), and whether the site has a Scan plan.
  • Overview card: locale-formatted "All-time blocked requests" count while the firewall is on (a prompt to turn it on while it's off, live from the Settings tab once loaded), an "Unavailable" badge when the firewall isn't available, and a "Configure Firewall" / "Turn on in Settings" link that switches to the Settings tab.
  • Settings card: one row per setting, each with a toggle, label and description, under "Block malicious requests before they reach your site." The rows are "Enable firewall", "Automatic firewall rules" (usable with a Scan plan or once automatic rules are available, with the same free-version note as Jetpack Settings), "Block these IP addresses", "Always allowed IP addresses", and share basic/detailed data. A list's text box opens inside its row when the toggle is on. Shows a notice instead when the hosting doesn't support the firewall.
  • Share-data toggles save through jetpack/v4/waf, like Jetpack Settings, so turning off basic sharing also turns off detailed sharing (and vice versa) and the standalone bootstrap is regenerated.
  • Settings are saved through existing endpoints: the share-data toggles through jetpack/v4/waf, everything else through jetpack/v4/settings.
  • Firewall test: a "Test firewall" button on the Overview card. POST jetpack/v4/protect-dashboard/firewall/test creates a one-time token that expires after 60 seconds (only its hash is stored, in jetpack-waf/self-check-token) and returns /?jetpack_waf_test=<token>. The browser requests that URL without cookies. The WAF runner checks the token before any rules run (so the IP allow list can't let the request through) and blocks it as rule -2, "firewall test". A snackbar shows "Testing the firewall…", then the result: blocked, logged but not blocked (silent mode), not blocked, or firewall off. The result stays until dismissed, with a checkmark when the firewall blocked the request and an X when it didn't or the test couldn't run. Its "Learn more" opens a dialog with the steps: the token, the request, the status and X-JetpackWAF-Blocked header, and whether the block was logged. The test also runs while the firewall is off, to show the request getting through.
  • Recently blocked requests: the card shows the latest block. "View blocked requests" opens a dialog with the last 10 rows of the blocklog (Waf_Blocklog_Manager::get_recent_blocks()), refreshed after a test via GET jetpack/v4/protect-dashboard/firewall/blocks.
    • The button only shows while "Share basic data" is on, since that's when the firewall keeps its request log (jetpack-waf/waf-blocklog).
    • When that log has an entry for a block, matched by second and rule, the dialog adds Request and Client columns. Blocks without one show "—". Nothing new is stored and the blocklog table is unchanged; the log isn't read while sharing is off.
  • Dialogs on the Protect page are lifted above the admin menu (--wp-ui-dialog-z-index, as in the VideoPress dashboard), so they aren't covered on narrower screens.
  • automattic/jetpack-waf is a dev dependency of packages/protect so static analysis can see it, as in Sync; at runtime it's still optional and guarded.
  • IP lists: "Always allowed IP addresses" has an "Add my IP address" button that uses the IP the firewall sees (Waf_Request::get_real_user_ip_address()).
  • Saved settings: every successful save on the Settings tab shows a "Settings updated." snackbar (one at a time, so a burst of saves doesn't stack). A failed save keeps the existing error and rollback, with no snackbar.
  • Stale block counts (WAF fix): the WAF writes the counter options with raw mysqli, so on sites with a persistent object cache get_option() returned stale counts. They're now read straight from the options table.

Related product discussion/links

Does this pull request change what data or activity we track or use?

No.

Testing instructions

  • On a connected site running this branch:
    • wp companion feature-flag enable jetpack-protect-dashboard (or filter jetpack_feature_flag_enabled_jetpack-protect-dashboard to true)
    • wp eval 'Jetpack_Options::delete_option("available_modules");'
    • wp jetpack module activate protect-dashboard and wp jetpack module activate waf
  • Go to Jetpack > Protect. The Firewall card shows "All-time blocked requests" with a number, and a "Configure Firewall" link.
  • Click "Configure Firewall": you land on the Settings tab. The Firewall card shows the toggles; "Automatic firewall rules" is disabled on a free site with the free-version help text.
  • Turn on "Block these IP addresses", enter 203.0.113.7, click Save. Both requests to jetpack/v4/settings return 200, and after a reload the toggle is on and the address is still there. wp-content/jetpack-waf/rules/block-ip.php now contains the address.
  • Clear the list and turn the toggle off; the address is gone from block-ip.php.
  • Turn on "Share detailed data with Jetpack": basic sharing turns on too (one POST to jetpack/v4/waf). Turn off "Share basic data with Jetpack": detailed sharing turns off too, and wp-content/jetpack-waf/bootstrap.php now defines both JETPACK_WAF_SHARE_DATA and JETPACK_WAF_SHARE_DEBUG_DATA as ''.
  • Turn off "Enable firewall": the other toggles disable, and back on the Overview tab the card asks you to turn the firewall on, with a "Turn on in Settings" link. The blocked count stays visible after turning it back on without reloading.
  • With the firewall on, click "Test firewall". A "Testing the firewall…" snackbar appears, then one with a checkmark saying the firewall blocked the request, which stays until you dismiss it. "Learn more" opens the steps: a 403 with the firewall header, and the block logged. "Latest: Firewall test" shows on the card, and the all-time count goes up by one.
  • With "Share basic data" on, click "View blocked requests": the new block has its request (/?jetpack_waf_test=…) and client (e.g. "Chrome · macOS"). Narrow the window to a phone width: the dialog stays above the admin menu and rows stack.
  • Turn "Share basic data" off: "View blocked requests" disappears; the "Latest" line stays.
  • Turn the firewall off and click "Test firewall": the snackbar says the request got through because the firewall is off, and Learn more shows 200 OK with no firewall header.
  • On the Settings tab, turn on "Always allowed IP addresses", click "Add my IP address", and save. A "Settings updated." snackbar appears, and wp-content/jetpack-waf/rules/allow-ip.php contains the address.
  • Add the IP of a machine you can make requests from to the block list, then request the site from it. The response is a 403, and the block shows as "Blocked IP address". Don't block the server's own IP: Jetpack Sync's loopback requests come from it.

Review follow-ups

  • Share-data toggles now save through jetpack/v4/waf, so the basic/detailed coupling and standalone bootstrap regeneration run; the optimistic update mirrors the coupling.
  • Automatic rules: enabled with a Scan plan or when rules are available, unchecked while unusable, and the help text matches Jetpack Settings for each case.
  • The all-time blocked count is always sent (so it's correct after turning the firewall on mid-session), fetched via Waf_Blocklog_Manager::get_all_time_block_count() only, and locale-formatted.
  • Block-list help text now matches the parser (commas, spaces or new lines) and says it applies while the firewall is on.
  • Deferred: none.

Code coverage

The coverage check flags class-firewall.php, settings-card.tsx and Waf_Blocklog_Manager::get_recent_blocks(). What's left uncovered is REST callbacks, page state and toggle wiring, plus raw $wpdb/mysqli reads of the blocklog and options tables. These are exercised end to end on a Jurassic Ninja site with the steps above, so this PR carries the Covered by non-unit tests label. The logic with branches has unit tests: test outcomes, block labels, user-agent summaries, matching request-log entries to blocks, the dialog's gating and columns, the test snackbar and Learn more, and saved-settings snackbars and rollback.

Screenshots

Overview → Firewall card:
Firewall overview card

Test firewall → result snackbar with Learn more:
Firewall test snackbar

Learn more → how the test works:
How the firewall test works

View blocked requests (with "Share basic data" on; rows logged while it was off show "—"):
Recently blocked requests

Settings → Firewall card:
Firewall settings card

Settings → saved snackbar:
Settings updated snackbar

@enejb enejb self-assigned this Oct 6, 2026
@github-actions

github-actions Bot commented Oct 6, 2026 •

Copy link
Copy Markdown
Contributor

Are you an Automattician? Please test your changes on all WordPress.com environments to help mitigate accidental explosions.

  • To test on WoA, go to the Plugins menu on a WoA dev site. Click on the "Upload" button and follow the upgrade flow to be able to upload, install, and activate the Jetpack Beta plugin. Once the plugin is active, go to Jetpack > Jetpack Beta, select your plugin (Jetpack), and enable the add/protect-dashboard-firewall branch.
  • To test on Simple, run the following command on your sandbox:
bin/jetpack-downloader test jetpack add/protect-dashboard-firewall

Interested in more tips and information?

  • In your local development environment, use the jetpack rsync command to sync your changes to a WoA dev blog.
  • Read more about our development workflow here: PCYsg-eg0-p2
  • Figure out when your changes will be shipped to customers here: PCYsg-eg5-p2

@github-actions github-actions Bot added [Feature] Protect Dashboard [Plugin] Jetpack Issues about the Jetpack plugin. https://wordpress.org/plugins/jetpack/ labels Oct 6, 2026
@github-actions

github-actions Bot commented Oct 6, 2026 •

Copy link
Copy Markdown
Contributor

Thank you for your PR!

When contributing to Jetpack, we have a few suggestions that can help us test and review your patch:

  • ✅ Include a description of your PR changes.
  • ✅ Add a "[Status]" label (In Progress, Needs Review, ...).
  • ✅ Add testing instructions.
  • ✅ Specify whether this PR includes any changes to data or privacy.
  • ✅ Add changelog entries to affected projects

This comment will be updated as you work on your PR and make changes. If you think that some of those checks are not needed for your PR, please explain why you think so. Thanks for cooperation 🤖


Follow this PR Review Process:

  1. Ensure all required checks appearing at the bottom of this PR are passing.
  2. Make sure to test your changes on all platforms that it applies to. You're responsible for the quality of the code you ship.
  3. You can use GitHub's Reviewers functionality to request a review.
  4. When it's reviewed and merged, you will be pinged in Slack to deploy the changes to WordPress.com simple once the build is done.

If you have questions about anything, reach out in #jetpack-developers for guidance!


Jetpack plugin:

No scheduled milestone found for this plugin.

If you have any questions about the release process, please ask in the #jetpack-releases channel on Slack.


Protect plugin:

No scheduled milestone found for this plugin.

If you have any questions about the release process, please ask in the #jetpack-releases channel on Slack.

@jp-launch-control

jp-launch-control Bot commented Oct 6, 2026 •

Copy link
Copy Markdown

Code Coverage Summary

Coverage changed in 6 files. Only the first 5 are listed here.

File Coverage Δ% Δ Uncovered
projects/packages/waf/src/class-waf-blocklog-manager.php 82/172 (47.67%) -2.33% 18 💔
projects/packages/waf/src/class-waf-runner.php 82/121 (67.77%) -0.56% 1 ❤️‍🩹
projects/packages/protect/routes/dashboard/data/use-protect-settings.ts 56/58 (96.55%) 0.12% 0 💚
projects/packages/protect/routes/dashboard/data/is-module-active.ts 1/1 (100.00%) 100.00% -1 💚
projects/packages/protect/routes/dashboard/components/tab-link.tsx 3/7 (42.86%) 28.57% -2 💚

9 files are newly checked for coverage. Only the first 5 are listed here.

File Coverage
projects/packages/protect/src/sections/class-firewall.php 14/94 (14.89%) 💔
projects/packages/protect/routes/dashboard/sections/firewall/settings-card.tsx 4/20 (20.00%) 💔
projects/packages/protect/routes/dashboard/sections/firewall/firewall-test.ts 10/14 (71.43%) 💚
projects/packages/protect/routes/dashboard/sections/firewall/overview-card.tsx 34/39 (87.18%) 💚
projects/packages/waf/src/class-waf-self-check.php 35/36 (97.22%) 💚

Full summary · PHP report · JS report

Coverage check overridden by Covered by non-unit tests Use to ignore the Code coverage requirement check when E2Es or other non-unit tests cover the code .

@enejb
enejb force-pushed the add/protect-dashboard-firewall branch 2 times, most recently from cc151f9 to 032237f Compare October 6, 2026 01:11
@enejb
enejb force-pushed the add/protect-dashboard-foundation branch 3 times, most recently from b531356 to 6567e4b Compare October 8, 2026 14:23
Base automatically changed from add/protect-dashboard-foundation to trunk October 8, 2026 15:34
@enejb
enejb force-pushed the add/protect-dashboard-firewall branch from 4d26414 to f82b81e Compare October 9, 2026 15:07
@github-actions github-actions Bot added [Package] WAF [Plugin] Protect A plugin with features to protect a site: brute force protection, security scanning, and a WAF. labels Oct 9, 2026
…n, and show the requests it kept

The View blocked requests dialog now needs the firewall's request log (Share basic data). When that log has a block, the dialog adds its request and client, read from the log file rather than a new blocklog column.
@enejb
enejb requested a balanced review from Copilot October 9, 2026 23:55
@enejb
enejb marked this pull request as ready for review October 9, 2026 23:55
enejb added 2 commits October 9, 2026 16:57
Protect only uses the WAF when a plugin bundles it, so it is a dev dependency, as in Sync. Also pass the self-check's rule ID as the string Waf_Runtime::block() documents.
…erview

The IP lists stay on the Settings tab, so the page no longer sends them with its state.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Firewall tests can fail across supported origin configurations, and refresh failures can display stale successful results.

3 open findings
What changed in this PR

Adds the Firewall section to the feature-gated Protect dashboard and extends the WAF package with testing, recent-block, and cache-safe count APIs.

Changes:

  • Adds Firewall overview/settings cards, manual IP rules, recent blocks, and self-test UI.
  • Adds one-time WAF self-check tokens and direct database reads for current counts.
  • Adds PHP/JavaScript tests and changelog entries.
File Description
projects/​plugins/​protect/​changelog/​fix-waf-stale-block-counts Records the Protect count fix.
projects/​plugins/​jetpack/​changelog/​add-protect-dashboard-firewall Records the Jetpack dashboard feature.
projects/​packages/​waf/​tests/​php/​unit/​WafSelfCheckTest.php Tests self-check tokens and blocking.
projects/​packages/​waf/​tests/​php/​unit/​WafBlocklogManagerTest.php Tests cache-bypassing count reads.
projects/​packages/​waf/​tests/​php/​unit/​functions-wordpress.php Adds an unserialization test shim.
projects/​packages/​waf/​src/​class-waf-self-check.php Implements one-time firewall tests.
projects/​packages/​waf/​src/​class-waf-runner.php Runs self-checks before normal rules.
projects/​packages/​waf/​src/​class-waf-blocklog-manager.php Adds recent blocks and fresh count reads.
projects/​packages/​waf/​changelog/​fix-waf-stale-block-counts Records the WAF count fix.
projects/​packages/​waf/​changelog/​add-protect-dashboard-firewall Records new WAF dashboard support.
projects/​packages/​protect/​tests/​php/​Firewall_Section_Test.php Tests compatibility fallbacks.
projects/​packages/​protect/​src/​sections/​class-firewall.php Provides Firewall state and REST routes.
projects/​packages/​protect/​routes/​dashboard/​sections/​firewall/​types.ts Defines Firewall state types.
projects/​packages/​protect/​routes/​dashboard/​sections/​firewall/​test/​manual-rules.test.ts Tests manual-rule summaries.
projects/​packages/​protect/​routes/​dashboard/​sections/​firewall/​test/​firewall-test.test.ts Tests firewall outcomes and labels.
projects/​packages/​protect/​routes/​dashboard/​sections/​firewall/​test-details.tsx Renders self-test diagnostics.
projects/​packages/​protect/​routes/​dashboard/​sections/​firewall/​style.scss Styles Firewall lists and diagnostics.
projects/​packages/​protect/​routes/​dashboard/​sections/​firewall/​settings-card.tsx Adds Firewall settings controls.
projects/​packages/​protect/​routes/​dashboard/​sections/​firewall/​recent-blocks.tsx Renders recent blocked requests.
projects/​packages/​protect/​routes/​dashboard/​sections/​firewall/​overview-card.tsx Adds Firewall status and testing UI.
projects/​packages/​protect/​routes/​dashboard/​sections/​firewall/​manual-rules.tsx Summarizes configured IP rules.
projects/​packages/​protect/​routes/​dashboard/​sections/​firewall/​index.tsx Registers Firewall dashboard cards.
projects/​packages/​protect/​routes/​dashboard/​sections/​firewall/​firewall-test.ts Executes and classifies self-tests.
projects/​packages/​protect/​changelog/​add-protect-dashboard-firewall Records the shared dashboard feature.

🧠 Review effort: Balanced


💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

path: `${ FIREWALL_PATH }/test`,
method: 'POST',
} );
const response = await fetch( url, { credentials: 'omit', cache: 'no-store' } );
* @return The addresses to show and the count of the rest.
*/
export function summarizeIpList( list?: unknown ): { shown: string[]; more: number } {
const ips = typeof list === 'string' ? list.split( /[\s,]+/ ).filter( Boolean ) : [];
Comment on lines +96 to +105
const lastId = blocks.recentBlocks[ 0 ]?.id ?? 0;
setResult( await runFirewallTest( active ) );
const fresh = await apiFetch< {
blockedCount: number | null;
recentBlocks: BlockedRequest[];
} >( { path: `${ FIREWALL_PATH }/blocks` } );
setBlocks( fresh );
setRecorded(
fresh.recentBlocks.some( block => block.id > lastId && block.ruleId === SELF_CHECK_RULE_ID )
);
@enejb enejb added the Covered by non-unit tests Use to ignore the Code coverage requirement check when E2Es or other non-unit tests cover the code label Oct 10, 2026

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Covered by non-unit tests Use to ignore the Code coverage requirement check when E2Es or other non-unit tests cover the code [Feature] Protect Dashboard [Package] My Jetpack [Package] Protect [Package] WAF [Plugin] Jetpack Issues about the Jetpack plugin. https://wordpress.org/plugins/jetpack/ [Plugin] Protect A plugin with features to protect a site: brute force protection, security scanning, and a WAF. [Status] In Progress [Tests] Includes Tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants