=== Papy3D WAF ===
Contributors: papy3d
Tags: security, firewall, waf, hardening, brute-force
Requires at least: 6.8
Tested up to: 7.1
Requires PHP: 8.3
Stable tag: 2.0.31
License: GPLv2 or later
License URI: https://www.gnu.org/licenses/gpl-2.0.html
Plugin URI: https://laforge.papy-3d-factory.xyz

A local pre-WordPress application firewall with bounded inspection, declarative rules, observation mode, and safe server diagnostics.

== Description ==

Papy3D WAF is a local application firewall that inspects PHP requests before WordPress loads. It uses `auto_prepend_file`, a standalone bounded runtime, declarative rules, signed A/B publication and deferred import of authenticated security events.

The plugin provides Observation, Balanced and Hardened modes. Its protections cover common web attacks and WordPress-specific probes, including SQL injection, XSS, command injection, traversal, file inclusion, sensitive-file access, malicious uploads, webshell patterns, SSRF, XXE and repeated login abuse. Administrators can review detections, create narrow exceptions, apply temporary source blocks and monitor runtime performance.

Installation of the early loader is never automatic. An administrator must review the diagnostics and explicitly start installation. The installer does not replace an unknown `auto_prepend_file`, validates the published runtime before activation and restores the previous compatible Papy3D loader if validation fails. Multisite runtime installation is intentionally unavailable. Mutable runtime data created by WordPress is confined to the dedicated directory below the base returned by `wp_upload_dir()`, under `papy3d-waf/`, with direct HTTP access protection; these files are non-executable JSON, DAT, LOG or text data. Because `auto_prepend_file` must keep working while WordPress replaces the plugin directory during an automatic update, the installer also maintains one dedicated `papy3d-waf-runtime/` directory directly below the dynamically resolved WordPress content directory. That exceptional directory contains only byte-for-byte copies of the PHP runtime files shipped with this plugin, plus non-executable bootstrap metadata; it contains no remotely supplied code, generated rules, logs or visitor data. Direct HTTP access to both directories is denied and verified before activation. On supported PHP-FPM/CGI/LiteSpeed or Apache setups, the explicit installation also writes only the marked Papy3D WAF `auto_prepend_file` block to the site-level `.user.ini` or `.htaccess`; it never edits WordPress core files, theme files or plugin source files, and removal targets only its own files and marker.

Request values are inspected only in memory. WordPress authentication and password cookies are excluded, authorization headers are not collected, and the journal stores payload-free detection markers instead of URI queries, cookie values, request bodies, header values or uploaded-file content. IP storage can use complete addresses or irreversible IPv4 /24 and IPv6 /64 network obfuscation.

Papy3D WAF can integrate with Papy3D Security Guard through signed capability delegation. Trusted-proxy remote updates are disabled by default, require explicitly selected providers and a separate administrator opt-in for scheduled refresh; manual refresh remains an explicit administrator action. The optional Cloudflare list integration runs only when enabled or explicitly requested by an administrator. Automatic Cloudflare append is disabled by default and requires exact-IP storage, an active blocking mode, a configured account and an existing selected IP list. No telemetry, remote code or remotely executable rule is used.

See the FAQ, Privacy, External services and Changelog sections for detailed behavior, data handling and release history.

== Installation ==

1. Upload and activate the plugin.
2. Open the top-level **Papy3D WAF** administration page.
3. Record at least one administrator IPv4, IPv6 or CIDR range in “Always-allowed addresses”; verify the detected source before saving.
4. Review the detected SAPI, effective directive, owner, storage, and multisite status.
5. Start the explicit installation. The initial runtime policy is always Observation and installation is unavailable while the administrator allowlist is empty.
6. Keep an administration page open while the signed probe validates automatically; WP-Cron continues in the background if the page is closed.
7. Use the manual probe button only if the automatic validation cannot complete, then review events before selecting Balanced or Hardened mode.

For emergency recovery, contact hosting support and provide the `P3DWAF_EMERGENCY_BYPASS` recovery identifier. Any temporary recovery setting must be removed immediately after access is restored.

== Frequently Asked Questions ==

= What happens when an active WAF is updated? =

The first runtime installation remains an explicit administrator action. After the WAF has been activated successfully, plugin updates do not require a visit to the status page. The external loader keeps the last validated runtime active while the updated plugin schedules a deferred reconciliation. A new runtime is compiled in the inactive slot, tested through an exact signed loopback probe, and activated atomically only after validation. On failure, the previous loader and runtime stay active and a bounded retry is scheduled.


= Does it replace a network firewall or CDN? =

No. It cannot stop traffic before PHP, static-file requests, or volumetric denial-of-service attacks.

= Does it overwrite another auto_prepend_file? =

No. An unknown owner is never overwritten. Security Guard integration is used only when its public protocol-1.0 connector satisfies the minimum supported plugin and connector versions.

= Does Observation mode block requests? =

No. It records matching rules without blocking or banning.

= How do I import the malicious-IP file into Cloudflare? =

Create or open an IP custom list in Cloudflare, choose Upload CSV, and select the file generated by the “Malicious IPs” tab. The file intentionally has no header and contains one exact IPv4 or IPv6 address per line followed by an optional description. Review the candidates before using the list in a blocking rule.

= Why is Cloudflare export disabled in obfuscated-IP mode? =

A stored IPv4 /24 or IPv6 /64 network cannot be converted back to the original visitor address. Exporting it could block an entire network, so the plugin requires exact-IP storage for new candidates. Historical obfuscation remains irreversible.

= How does direct Cloudflare list addition work? =

Create a Cloudflare API token restricted to the target account with the Account Filter Lists: Edit permission, then save the account ID and token in the “Malicious IPs” tab. Select an existing list of type IP. The existing append button remains manual and unchanged. A separate disabled-by-default option can append an exact IP automatically after either a cumulative threshold or a rolling burst threshold is reached. Both paths reload the remote list, skip exact duplicates and IPs already covered by a remote CIDR, verify the local 10,000-entry safety ceiling, and submit only new entries. A separate explicitly confirmed action can clear all items while preserving the remote list object and any WAF rule that references it. The selected list must already be referenced by a Cloudflare blocking rule; the plugin does not create or modify that rule.

== Privacy ==

Events are stored locally. WordPress authentication and password cookies are excluded from inspection, and authorization headers are not collected. Request values are inspected only in memory: the queue and database journal store payload-free detection markers rather than URI queries, cookie values, request bodies, header values, or uploaded-file content. The queue has bounded files and the WordPress event retention period is configurable.

Administrators can retain complete IP addresses or store IPv4 as /24 networks and IPv6 as /64 networks. Switching to obfuscated logging irreversibly transforms imported historical IPs; pending authenticated events are masked in the interface and exports and are stored obfuscated at import. A local HMAC remains available for grouping and temporary blocking. JSON export can include the whole retained journal, or one exact IP only while complete-IP storage is enabled. Exports contain sensitive security data and must be protected and deleted after use.

Papy3D WAF also stores the administrator-configured “always allowed” IPv4, IPv6 or CIDR ranges locally as mandatory security configuration. This allowlist is independent of the journal IP-storage mode and is not obfuscated, because changing the configured range would make the access-control rule ineffective. The detected current source is only saved after an authorized administrator explicitly submits the first-run form.

Automatic trusted-proxy updates are disabled by default. When an administrator selects providers and separately enables automatic refresh, the server contacts only those selected providers' published IP-list endpoints over HTTPS. The providers necessarily receive the server source IP and standard HTTPS metadata. The request user agent identifies Papy3D WAF and its version but does not include the site URL, users, content, settings, or visitor addresses. Retrieved data is validated locally and cached as last-known-good network ranges.

When an administrator configures the optional Cloudflare API integration, the account ID, selected list ID, and API token are sent only to Cloudflare over HTTPS. Manual actions remain explicit. When automatic append is separately enabled, one exact public IP and a bounded WAF comment can also be sent after the configured cumulative or burst threshold. Cumulative counters use local HMAC identities. A local synchronization registry links retained WAF identities to pending or confirmed membership of the selected Cloudflare list so the Log can display AUTO, MANUAL, EXTERNAL or pending state without querying Cloudflare for each row. Exact registry addresses are erased when obfuscated-IP mode is enabled and are not repopulated by later reconciliation. The token is stored locally with authenticated encryption, is never displayed again, and is removed with the connection data on request. Automatic submission and real reports are suspended when IP obfuscation is active.

== External services ==

The optional direct Cloudflare API integration uses `https://api.cloudflare.com/client/v4/` and sends the account ID, selected list ID, Bearer API token and standard HTTPS metadata. Credential management, list selection, refresh, download, manual append and clearing remain administrator-triggered. If the administrator separately enables automatic append, WordPress can send one exact public IP and a bounded WAF comment after a cumulative or burst threshold; the pre-WordPress runtime never contacts Cloudflare directly. An optional daily report sends exact blocked IPs, trigger counts, reasons and outcomes through the site’s configured `wp_mail()` transport to administrator-selected recipients. Cloudflare privacy and terms: https://www.cloudflare.com/privacypolicy/ and https://www.cloudflare.com/policies/terms/.

Trusted-proxy remote updates are optional. The explicit refresh button contacts only providers selected by an administrator. Scheduled WP-Cron refresh is disabled by default and runs only after the administrator separately enables automatic refresh while at least one provider remains selected. The request sends no site URL, user, visitor IP, content, or plugin configuration. The selected provider receives only the server source IP, standard HTTPS metadata, and a generic plugin/version user agent. Retrieved values are locally validated as public IP addresses or CIDR ranges, bounded, cached as last-known-good data, and merged with manual entries that are never overwritten.

The following public sources can be contacted:

* Cloudflare public IP API: https://api.cloudflare.com/client/v4/ips (machine-readable IPv4/IPv6 CIDR data)
  * Privacy: https://www.cloudflare.com/privacypolicy/
  * Terms: https://www.cloudflare.com/policies/terms/
* QUIC.cloud combined server list: https://www.quic.cloud/ips-all
  * Privacy: https://www.quic.cloud/privacy-policy/
  * Terms: https://www.quic.cloud/terms-of-use/
* bunny.net IPv4/IPv6 edge lists: https://bunnycdn.com/api/system/edgeserverlist and https://bunnycdn.com/api/system/edgeserverlist/IPv6
  * Privacy: https://bunny.net/privacy/
  * Terms: https://bunny.net/tos/
* Fastly public IP list: https://api.fastly.com/public-ip-list
  * Privacy: https://www.fastly.com/privacy
  * Terms: https://www.fastly.com/terms
* Imperva public integration API: https://my.imperva.com/api/integration/v1/ips
  * Privacy: https://www.imperva.com/trust-center/privacy-statement/
  * Terms: https://www.imperva.com/legal/website-terms-of-use/

Sucuri ranges are bundled from its published firewall documentation, so selecting Sucuri does not contact GoDaddy or Sucuri. Documentation source: https://docs.sucuri.net/website-firewall/troubleshooting/same-ip-for-all-users/


== Changelog ==

= 2.0.31 =
* Corrected the WPCS suppression scope for the intentional pre-WordPress queue writability check and fixed assignment alignment; no runtime behavior or security policy change.

= 2.0.30 =
* Hardened all persistent runtime state writes against pre-existing symlinks and path-swap races by verifying the opened regular file identity before writing; probe receipts now use exclusive random temporary files.

= 2.0.29 =
* Removed the unsupported `Tested up to` field from the main PHP plugin header; WordPress compatibility remains declared only in `readme.txt`.

= 2.0.28 =
* Harden pre-WordPress request ingestion across server headers, query, form, cookies, uploads and JSON/XML bodies; restore complete header inspection and client-IP resolution, make lifecycle shutdown fail-safe, and neutralize spreadsheet formulas in CSV exports.
* Initial public release.
* Hardened all pre-WordPress runtime class entry points so direct HTTP requests return 404 even after the early loader has initialized.
* WordPress Coding Standards cleanup: complete parameter PHPDoc and alignment-only formatting fixes; no runtime behavior change.
* Security Guard handoff is started only when Security Guard actually owns the effective early loader; an available connector with owner `none` no longer causes a false transfer failure.
* Hardened `auto_prepend_file` ownership detection to avoid false owner conflicts while preserving fail-closed handling for unknown loaders.
* Provides the standalone pre-WordPress WAF runtime, signed A/B publication, Observation/Balanced/Hardened modes, authenticated event journal, privacy controls, trusted-proxy support, Cloudflare integration, Security Guard ownership handoff, and bounded performance diagnostics.
* WordPress 7.1 compatible, with English source strings and bundled French translation.
* Hosting-configuration diagnostics direct administrators to hosting support and do not request direct edits to server or plugin files.
