=== CSP Violation Reporter ===
Contributors: guidumasperes
Tags: csp, security, reporting, content-security-policy, reports
Requires at least: 6.5
Tested up to: 7.1
Requires PHP: 7.4
Stable tag: 0.2.0
License: GPLv2 or later
License URI: https://www.gnu.org/licenses/gpl-2.0.html

Collect and inspect Content Security Policy (CSP) reports with filtering, grouping, rate limits and automatic cleanup.

== Description ==

CSP Violation Reporter helps site administrators investigate Content Security Policy violations. It receives browser reports through a public WordPress REST endpoint, stores them in the site's own database, and provides a searchable administrative dashboard.

Reports can be reviewed from Tools > CSP Violations. The plugin supports the modern Reporting API payload format as well as the older `csp-report` JSON shape.

* Review violations by directive, blocked resource, document URL, disposition and date.
* Group noisy resources and count repeated occurrences without storing an extra row for every identical report.
* Inspect safely escaped raw JSON, source locations, referrers and first/last-seen timestamps.
* Configure rate limits, retention and the maximum number of stored records.
* Receive same-origin iframe reports and automatically upgrade the database schema without reactivation.

Filter by directive, disposition, URL and date range, or group violations by resource. Repeated violations with the same document, blocked resource, directive, policy and source location share one record with an occurrence count. Each record includes first/last-seen timestamps and the latest raw JSON report.

Protection is enabled by default:

* Accepts document URLs only from the origins configured in this site's WordPress Address and Site Address. Redacted iframe reports require a local referrer.
* Limits intake to 100 reports per minute per connecting address and 1,000 reports per minute for the site.
* Limits request bodies to 64 KiB, individual normalized reports to 16 KiB and batches to 50 entries.
* Removes inactive reports after 30 days and retains at most 10,000 records, keeping the most recently seen.

Retention and quotas can be adjusted in Tools > CSP Violations > Settings. Existing reports are preserved during schema upgrades; retention rules also apply to pre-upgrade reports.

Endpoint:

`/wp-json/csp-violation-reporter/v1/report`

The plugin does not create or modify Content Security Policy headers. Site owners should configure CSP headers in their web server, hosting dashboard, theme, or security tooling.

Example report endpoint configuration:

`Content-Security-Policy: default-src 'self'; report-uri https://example.com/wp-json/csp-violation-reporter/v1/report`

For the modern Reporting API, use an HTTPS endpoint:

`Reporting-Endpoints: csp-endpoint="https://example.com/wp-json/csp-violation-reporter/v1/report"`

`Content-Security-Policy: default-src 'self'; report-to csp-endpoint`

== Installation ==

1. Upload the plugin folder to `/wp-content/plugins/`.
2. Activate the plugin through the Plugins screen in WordPress.
3. Open Tools > CSP Violations to copy the reporting endpoint.
4. Configure your CSP Reporting API group and reference it from your `report-to` directive.

== Frequently Asked Questions ==

= Does this plugin set my CSP header? =

No. This plugin receives and displays CSP violation reports. CSP header generation is intentionally left to your theme, server, security plugin, or hosting environment.

= Is the report endpoint public? =

Yes. Browser violation reports are sent without WordPress authentication. Admin views remain protected by the `manage_options` capability.

= Does origin validation authenticate the sender? =

No. The document URL is supplied by the sender, so it can be forged. Origin checks reject unrelated sites but are not authentication. Database-backed per-address and site-wide quotas, payload limits and retention reduce abuse. For high-volume attacks, also configure rate limiting at your web server or firewall.

= How are proxies and CDNs handled? =

Limits use the connecting address in `REMOTE_ADDR`. The plugin does not trust client-controlled `X-Forwarded-For` headers. Configure trusted-proxy address restoration in your web server if a CDN or reverse proxy fronts the site.

= Are iframe violations supported? =

Yes. Some browsers report iframe document URLs as `about`, `about:blank`, `about:srcdoc` or `blob` instead of an HTTP URL. These reports are accepted only with a referrer on this site's origin. Full blob URLs are checked against their embedded HTTP(S) origin. Redacted reports without a local referrer are rejected because their origin cannot be established. Parent referrers also distinguish otherwise identical redacted violations during deduplication.

= When does automatic cleanup run? =

Cleanup is scheduled hourly with WP-Cron and also runs when reports are accepted or retention settings change. WP-Cron depends on site traffic; on low-traffic sites, configure a server scheduler to trigger WordPress cron. Expired records are removed in batches. The record ceiling is enforced on every accepted request.

= Are repeated reports discarded? =

Their occurrences are counted, the last-seen time is refreshed, and the latest raw report is retained. The raw JSON is the normalized CSP report body, not the outer Reporting API envelope. Historical 0.1.1 records keep their original data and can be inspected through grouped views.

= What happens when the endpoint rejects a report? =

The endpoint returns HTTP 400 for malformed reports, 403 for foreign document origins, 413 for oversized payloads, and 429 with a Retry-After header for rate limits. Storage failures return HTTP 500 or 503. Accepted requests retain the existing `{"stored": N}` response format; N counts accepted occurrences, including duplicates.

= Does the plugin store visitor IP addresses? =

No. The plugin stores a salted hash of the remote address to help with deduplication and abuse analysis without retaining the raw IP address.

= Does the plugin send data to third parties? =

No. Reports are stored in the site's own WordPress database.

== Privacy ==

This plugin stores CSP violation reports submitted by browsers. Stored fields can include the document URL, referrer URL, blocked URI, violated directive, source file, line and column numbers, a user agent string, a salted hash of the remote address, and the raw report payload.

Short-lived rate-limit records contain a salted address hash and counters. Raw JSON can contain URL query parameters and other information supplied by the browser. Administrators should set retention to suit their site's privacy policy. Reports expire by last-seen time and can also be cleared manually. Uninstalling removes the plugin's report tables, protection settings and scheduled cleanup events.

The plugin does not store raw IP addresses and does not transmit report data to external services.

== Changelog ==

= 0.2.0 =

* Tested compatibility with WordPress 7.1, 7.1.2 and the minimum supported WordPress 6.5.
* Added atomic per-address and site-wide rate limits and reduced payload and batch ceilings.
* Validated reported document origins against the current site's URLs.
* Supported same-origin iframe reports, including browser-redacted about/blob URLs.
* Added configurable retention, a storage ceiling and scheduled cleanup.
* Deduplicated new violations with occurrence counts and first/last-seen times.
* Added filters, grouped views and safely escaped raw JSON details.
* Added automatic versioned database upgrades and complete lifecycle cleanup.
* Corrected modern originalPolicy and legacy script-sample field handling.
* Preserved the existing endpoint and accepted-response format for integrations.
* Improved grouped display of historical reports without a directive.

= 0.1.1 =

* Prepared SQL statements that include the plugin's custom table name.

= 0.1.0 =

* Initial development release.

== Upgrade Notice ==

= 0.2.0 =

Back up your database. Enables origin checks, rate limits, 30-day retention and a 10,000-record cap by default, including old reports. Review Tools > CSP Violations > Settings. Endpoint URL unchanged.
