Page Protect
Page Protect is a Cloudflare Page Shield-style client-side security feature. It monitors, inventories, and enforces client-side resources (scripts, connections) loaded by website visitors via Content-Security-Policy (CSP) headers, with log-based violation report collection, a CSP policy builder, script inventory with code-change detection, an inventory beacon, beacon trust, and a policy recommender — all scoped per-backend and integrated into the existing HAProxy config generation pipeline.
How It Works
Browser loads page
│
│ 1. HAProxy serves HTML response
│ - Injects <script src="/_cx-assets.js"> (if beacon enabled)
│ - Sets Server-Timing: cxid header (if beacon trust enabled)
│ - Sets Content-Security-Policy header (per backend policy)
│
▼
Browser loads resources (scripts, styles, images, XHR)
│
│ 2. CSP violation? → POST to /_csp-report
│ 3. Beacon JS collects Resource Timing entries
│ + reads cxid from Server-Timing
│ → POST to /_cx-assets
│
▼
HAProxy captures both in JSON log-format (txn.csp_report, txn.asset_beacon)
│
▼
Page Protect Sampler (background thread, every 10s)
│ - Reads HAProxy container logs via Docker SDK
│ - Parses CSP reports → stores in csp_reports table
│ - Parses beacon POSTs → upserts script inventory (source="beacon")
│ - Prunes old reports (report_retention_days)
│
▼
Page Protect Hasher (background thread, every 60s)
│ - Fetches due script URLs via httpx (HTTP/2)
│ - Computes SHA-256, compares to last-known hash
│ - Flags hash_changed on mismatch
│ - Bypasses WAF/rate-limit/security rules via X-CoreX-Internal token
│
▼
Dashboard / Inventory / Reports tabs in the UI
Four Pillars
- CSP Report Collection — HAProxy captures CSP violation report POST bodies in its JSON log-format (
txn.csp_reportvar); a background sampler reads HAProxy container logs via Docker SDK, extracts reports, and stores them in the database. - CSP Policy Builder — A dedicated
PageProtectPolicymodel (scoped per-backend viabackend_ids) generates CSP headers (Content-Security-Policyfor enforce,Content-Security-Policy-Report-Onlyfor monitor) emitted in HAProxy backend sections. Separate from the generic Response Headers feature. - Script/Connection Inventory — The sampler parses CSP reports and beacon POSTs to build/maintain an inventory of detected scripts, connections, and other resources with first-seen/last-seen tracking, domain attribution, and source tracking (
csp,manual, orbeacon). - Code Change Detection — A background hasher periodically fetches detected script URLs, computes SHA-256 hashes, and flags changes (supply-chain attack detection).
UI Tabs
The Page Protect page (Protection > Page Protect) has 6 tabs:
| Tab | Description |
|---|---|
| Dashboard | Live stats: total scripts, violations (24h), changed scripts, active policies, top violated directives, top blocked URIs |
| Policies | CSP policy builder with directive editor, live header preview, auto-suggest from inventory, and policy recommender |
| Inventory | Script/connection inventory with sorting, filtering, manual add, hash check, reset, ignore, view content, delete |
| Reports | CSP violation reports with filtering, expandable rows, CSV export, clear all |
| Baseline | Baseline window for policy recommendations — start/stop a collection window, then recommend a policy |
| Settings | Monitoring, change detection, report retention, beacon injection, beacon trust, manual report sampling |
Dashboard Tab
The dashboard shows 4 stat cards and 2 top-lists:
| Card | Description |
|---|---|
| Total Scripts | Count of entries in the script inventory |
| Violations (24h) | CSP reports captured in the last 24 hours |
| Changed Scripts | Scripts where the hash changed since last check (red if > 0) |
| Active Policies | Enabled CSP policies |
- Top Violated Directives — most frequently violated CSP directives
- Top Blocked URIs — most frequently blocked resource URIs
Auto-refreshes every 15 seconds.
Policies Tab
CSP policies are scoped per-backend (via backend_ids; empty = all backends). Each policy generates a CSP header emitted in the HAProxy backend section.
Policy Fields
| Field | Description |
|---|---|
| Name | Unique policy name |
| Mode | monitor (Report-Only — collect violations without blocking) or enforce (block violations) |
| Sample Rate (%) | Percentage of responses that get the CSP header (1–100; uses rand(100) lt <rate> for sampling) |
| Report Path | URL path browsers POST violation reports to (default /_csp-report; must match report-uri in the policy) |
| Backends | Which backends this policy applies to (none = all) |
| Enabled | Toggle to enable/disable the policy |
| Directives | CSP directive → source list mapping (see below) |
CSP Directives
| Directive | Controls |
|---|---|
default-src | Fallback for all resource types |
script-src | JavaScript sources |
style-src | Stylesheet sources |
img-src | Image sources |
font-src | Font sources |
frame-src | Iframe sources |
connect-src | XHR, WebSocket, fetch destinations |
media-src | Audio/video sources |
object-src | Plugin/embed sources |
worker-src | Worker sources |
frame-ancestors | Who can embed this page (clickjacking protection) |
base-uri | Allowed <base> tag URIs |
form-action | Form submission destinations |
report-uri | Violation report endpoint |
upgrade-insecure-requests | Force HTTPS for all resources |
Each directive accepts:
'self'— same origin'none'— no sources'unsafe-inline','unsafe-eval','strict-dynamic'— special keywords- Specific URLs (e.g.
https://cdn.example.com) - Nonces (per-request generated)
- Hashes
Directive Editor
The policy form includes a visual directive editor:
- Add/remove directives from a dropdown of all CSP directives
- Add sources to each directive via text input (press Enter or click Add)
- Remove individual sources with the × button
- Live CSP header preview at the bottom of the form
Auto-Suggest from Inventory
When the script inventory has detected scripts or connections, the policy form shows clickable suggestions:
- Detected scripts (blue chips) — click to add to
script-src - Detected connections (purple chips) — click to add to
connect-src
Policy Recommender
The Recommend Policy button analyzes the script inventory and CSP violation reports to generate a recommended CSP policy:
- Click Recommend Policy (optionally filter by backend first)
- A modal shows:
- Summary: scripts analyzed, reports analyzed, baseline window, backend filter
- Warnings (e.g. origins seen from < 2 distinct IPs — may be attacker probes)
- Recommended directives (preview)
- Per-origin details: origin, occurrence count, distinct IPs (amber if < 2)
- Click Apply to Form to populate the policy form with the recommendations
- Edit further before saving
The recommender uses the baseline window if one is set (see Baseline Tab).
If you have manually added a Content-Security-Policy header via the Response Headers feature, the Page Protect CSP header (emitted in the backend section) takes precedence because it runs after the frontend’s response headers (HAProxy set-header = last one wins).
Inventory Tab
The inventory tracks all client-side resources detected via CSP reports, beacon POSTs, or manual entry.
Inventory Fields
| Field | Description |
|---|---|
| URL | Resource URL |
| Type | script, connect, img, style, font, frame, object, other |
| Domain | Extracted hostname from the URL |
| Source | How the entry was detected: CSP (from violation report), Beacon (from beacon POST), or Manual (manually added) |
| Method | Fetch method for hashing: Auto (probe — HEAD then GET), GET, or POST |
| Occurrences | How many times this resource was seen in reports/beacons |
| Last Seen | Last time this resource appeared in a report or beacon |
| Last Checked | Last time the hasher fetched and hashed this resource |
| Hash Status | OK (hash matches), Changed (hash differs — possible supply-chain attack), Error (fetch failed), Unchecked (not yet hashed), Ignored |
Sorting and Filtering
- Sort — click any column header to sort ascending/descending (URL, type, domain, source, method, occurrences, last seen, last checked, hash status)
- Filter by type — dropdown to show only a specific resource type
- Filter by status — All, Changed, Unchanged, Ignored
Inventory Actions
| Action | Description |
|---|---|
| Add Asset | Manually add a URL to the inventory (source = Manual). Useful when CSP is in enforce mode (no violation reports) or to proactively monitor a critical asset. Requires http:// or https:// URL. |
| Check All | Force a hash check on all non-ignored assets immediately (overrides the interval) |
| Check (per-row) | Force a hash check on a single asset |
| Reset Hash | Reset the hash baseline for an asset — the next check establishes a fresh baseline (useful after a legitimate update) |
| View Content | Open the fetched script content in a new window (if available) |
| Ignore / Stop Ignoring | Toggle ignored state — ignored assets are not hashed but remain in the inventory |
| Delete | Remove the asset from the inventory |
Inventory entries are checked indefinitely until manually deleted. The auto_prune_stale_days setting (in Settings) removes assets not seen in traffic or successfully hashed within the configured window, but changed assets are always preserved.
Reports Tab
The Reports tab shows captured CSP violation reports.
Report Fields
| Field | Description |
|---|---|
| Time | When the report was captured |
| Client IP | IP of the browser that sent the report |
| Document URI | The page where the violation occurred |
| Directive | The CSP directive that was violated |
| Blocked URI | The resource that was blocked |
| Hostname | Extracted hostname from the document URI |
Click any row to expand and see the full report JSON (referrer, effective directive, original policy, source file, line/column number, status code, script sample, backend name, listener name, report type).
Filtering and Export
- Filter by violated directive — text input
- Filter by hostname — text input
- Export CSV — download all reports (up to 10,000) as CSV
- Clear All — delete all reports (requires write permission)
Baseline Tab
The baseline window defines a collection period for policy recommendations. During a baseline window, the recommender only considers scripts and reports captured within the window.
Baseline States
| Status | Description |
|---|---|
| Idle | No baseline window set |
| Baselining | Collection in progress (shows elapsed time, live counts) |
| Complete | Window finished (shows duration, final counts) |
Baseline Fields
| Field | Description |
|---|---|
| Note | Optional label for the baseline window |
| Window | Start → end timestamps (end is “in progress” while baselining) |
| Scripts | Count of scripts detected during the window |
| Reports | Count of CSP reports captured during the window |
| Distinct IPs | Count of distinct client IPs that sent reports |
| Pages Observed | Count of distinct pages (document URIs) in reports |
Baseline Workflow
- Click Start Baseline (optionally add a note)
- Crawl your site (or have users browse all pages) to ensure complete resource coverage
- Counts update every 5 seconds
- Click Stop Baseline when done
- Go to Policies > Recommend Policy — the recommender uses the baseline window
- Click Clear to discard the window and start fresh
Settings Tab
Monitoring
| Setting | Default | Description |
|---|---|---|
| Monitoring Enabled | false | Enables CSP report capture in coreX and the background sampler. After enabling, apply the coreX config to start capturing. |
| Report Path | /_csp-report | The URL path browsers POST CSP violation reports to. Must match the report-uri in your CSP policies. |
| Report Retention (days) | 7 | How long to keep CSP reports before pruning. |
Code Change Detection
| Setting | Default | Description |
|---|---|---|
| Code Change Detection | false | Periodically fetches detected scripts and hashes their content to detect supply-chain changes. |
| Change Detection Interval (hours) | 24 | How often to re-hash scripts. |
| Auto-Prune Stale (days) | 0 | Assets not seen in traffic or successfully hashed within this many days are automatically removed. 0 = disabled. Changed assets are preserved. |
Inventory Beacon
The inventory beacon injects a <script src="/_cx-assets.js"> tag into HTML responses via the resp_transform Rust filter. The beacon JS uses the Resource Timing API to collect all loaded resources and POSTs them to /_cx-assets. The sampler parses these POSTs and upserts resources with source="beacon".
The inventory beacon and beacon trust both require Response Transformations to be enabled in System > Settings > Advanced. When beacon is enabled, resp_transform_enabled is forced True so the Rust module loads. The UI disables these controls and shows a warning when Response Transformations is off.
| Setting | Default | Description |
|---|---|---|
| Inventory Beacon | false | Inject the beacon JS into HTML responses to collect resource timing data |
| Beacon Endpoint Path | /_cx-assets | URL path the beacon JS POSTs resource data to |
| Beacon Script Path | /_cx-assets.js | URL path where the beacon JS file is served |
| Beacon Content Types | text/html | Comma-separated content types to inject the beacon into |
| Beacon Path Patterns | (empty) | Comma-separated path prefixes to match (empty = all paths) |
| Beacon Backends | (empty) | Which backends to inject the beacon into (empty = all) |
When beacon is enabled, CSP headers automatically allowlist the beacon script path in script-src (or 'self' is added to default-src).
Beacon Trust
Beacon Trust is an anti-spoofing mechanism that proves a client IP received a real HTML response from coreX. It works alongside the inventory beacon — the beacon JS reads a per-request cxid from the Server-Timing response header and submits it with the beacon POST, and HAProxy validates it before marking the source IP as trusted. Trusted IPs stay trusted for BEACON_TRUST_TTL_SECONDS (default 900s / 15 minutes) with a sliding-window refresh on every subsequent request.
Trusted IPs can be referenced in security rules via the ip.beacon_trusted field (see Security Rules).
| Setting | Default | Description |
|---|---|---|
| Beacon Trust | false | Enable the beacon trust anti-spoofing mechanism |
Beacon Trust uses two stick-tables: cxid_table (string-keyed, 1m slots, 120s expiry — tracks recent cxids) and beacon_trust_table (ip-keyed, 100k slots, 900s sliding-window expiry — tracks trusted IPs). These are always emitted so security rules referencing ip.beacon_trusted don’t break; they’re simply empty when beacon trust is disabled. HAProxy’s tune.stick-counters is set to at least 6 to accommodate the sc4/sc5 tracking.
Manual Report Collection
The Sample Reports Now button triggers an immediate CSP report sample from coreX logs (instead of waiting for the next sampler interval). Reports the number of new reports stored.
Environment Variables
| Variable | Default | Description |
|---|---|---|
PAGE_PROTECT_SAMPLER_INTERVAL_SECONDS | 10 | How often the sampler polls HAProxy logs |
PAGE_PROTECT_REPORT_BODY_MAX_BYTES | — | Max CSP report body size to capture |
PAGE_PROTECT_HASH_TIMEOUT_SECONDS | — | Timeout for script hash fetches |
PAGE_PROTECT_HASH_USER_AGENT | coreX-Manager-PageProtect/2.0 | UA used by the hasher (also part of the bypass ACL) |
PAGE_PROTECT_HASHER_BYPASS_TOKEN | (random) | Secret token for hasher bypass (pin via env for stability) |
PAGE_PROTECT_HASHER_BYPASS_HEADER | X-CoreX-Internal | Header name for the bypass token |
PAGE_PROTECT_DEFAULT_REPORT_PATH | /_csp-report | Default CSP report endpoint path |
PAGE_PROTECT_BEACON_JS_PATH | /etc/haproxy/page-protect-beacon.js | Path to the beacon JS file in the HAProxy container |
BEACON_TRUST_TTL_SECONDS | 900 | How long a trusted IP stays trusted (sliding window) |
BEACON_CXID_TTL_SECONDS | 120 | How long a cxid stays in the table |
Step-by-Step: Configure CSP for a Listener
- Navigate to Protection > Page Protect > Settings
- Enable Monitoring Enabled
- Set Report Path to
/_csp-report(or your preferred path) - Save and Apply Changes — HAProxy now captures CSP violation reports
- Navigate to Policies
- Click Add Policy
- Name:
my-csp-policy, Mode:monitor(start with Report-Only) - Select the backend(s) it applies to (or leave empty for all)
- Set
default-srcto'self' - Set
script-srcto'self'plus any allowed CDN URLs (use auto-suggest from inventory if available) - Set
style-srcto'self'plus any allowed CDN URLs - Set
img-srcto'self'data:(if using data URIs) - Set
frame-ancestorsto'none'(clickjacking protection) - Review the CSP header preview
- Save and Apply Changes
Step-by-Step: Use the Policy Recommender
- Navigate to Protection > Page Protect > Baseline
- Click Start Baseline (optionally add a note)
- Crawl your site or have users browse all pages
- Watch the counts update (scripts, reports, distinct IPs, pages)
- Click Stop Baseline when done
- Navigate to Policies
- Click Add Policy
- Click Recommend Policy
- Review the recommended directives, warnings, and per-origin details
- Click Apply to Form
- Edit the policy as needed (add/remove directives or sources)
- Save and Apply Changes
Step-by-Step: Enable the Inventory Beacon
- Navigate to System > Settings > Advanced
- Enable Response Transformations and Apply Changes
- Navigate to Protection > Page Protect > Settings
- Enable Inventory Beacon
- Configure beacon settings (endpoint path, script path, content types, path patterns, backends)
- Save and Apply Changes
- The beacon JS is now injected into HTML responses
- Browse your site — resources appear in the Inventory tab with source = Beacon
Step-by-Step: Enable Beacon Trust
- Ensure Response Transformations is enabled (see above)
- Ensure Inventory Beacon is enabled (beacon trust requires the beacon JS to read the cxid)
- Navigate to Protection > Page Protect > Settings
- Enable Beacon Trust
- Save and Apply Changes
- HAProxy now inserts
cxidinto theServer-Timingheader on HTML responses - The beacon JS reads it and submits it with the beacon POST
- Validated IPs are tracked in
beacon_trust_tablefor 15 minutes - Use the
ip.beacon_trustedfield in security rules to gate access on trusted IPs
Step-by-Step: Detect Script Changes (Supply-Chain Monitoring)
- Navigate to Protection > Page Protect > Settings
- Enable Code Change Detection
- Set Change Detection Interval (hours) (e.g. 24 for daily checks)
- Save and Apply Changes
- Navigate to Inventory
- Review detected scripts — the Hash Status column shows OK/Changed/Error/Unchecked
- Click Check All to force an immediate hash check
- If a script shows Changed, investigate — it may be a legitimate update or a supply-chain attack
- If legitimate, click Reset Hash to establish a new baseline
- If suspicious, click Ignore to stop monitoring it, or Delete to remove it
Verification
-
Check CSP header:
curl -k -sI https://localhost/ | grep -i content-security-policy -
Check Server-Timing header (beacon trust):
curl -k -sI https://localhost/ | grep -i server-timingExpect:
cxid;desc="<uuid>"on HTML responses (when beacon trust is enabled). -
Check beacon script injection:
curl -k -s https://localhost/ | grep -o '_cx-assets.js'Expect: the beacon script tag in HTML responses (when beacon injection is enabled).
-
Check inventory:
- Navigate to Protection > Page Protect > Inventory
- Verify scripts detected via CSP reports (source = CSP) or beacon (source = Beacon) appear
- Verify hash status shows OK after the first check
-
Test violation reporting:
- Open your site in a browser
- Open developer tools > Console
- Look for CSP violation reports
- Verify violations appear in the Reports tab
-
Test enforcement:
- Switch a policy from
monitortoenforce - Apply Changes
- Attempt to load a non-allowlisted script
- Verify the browser blocks it
- Switch a policy from
-
Test beacon trust:
- Enable beacon trust (see step-by-step above)
- Browse your site
- Navigate to System > Tables
- Expand
beacon_trust_table— verify your IP appears - Expand
cxid_table— verify recent cxids appear
Next Steps
- Certificates & TLS — Configure HSTS alongside CSP
- Security Rules — Use
ip.beacon_trustedto gate access on trusted IPs - Settings — Enable Response Transformations (required for beacon)
- Metrics & Logging — Monitor CSP violations