Scan time: 2026-09-04 03:47:50
Overall Score
Compared with scan from 02.09.2026 12:11 (Score 52 → 54) · View full history
⚠ This website has serious GDPR deficiencies. Immediate action is required.
GDPR Issues Detected (5):
❌ 8 third-party server(s) outside the EU/EEA — data transfers without legal basis may violate Art. 44–49 GDPR.
Affected servers outside the EU:
❌ 1 tracking service(s) detected — without prior consent (opt-in) this violates Art. 6(1) GDPR.
Detected trackers:
⚠ Third-party cookies are being set — without consent this violates the ePrivacy Directive.
⚠ No Content Security Policy — increased risk of cross-site scripting (XSS) and data theft.
⚠ Missing or unsafe Referrer-Policy — URLs containing personal data may be leaked to third parties.
Note: This automated analysis does not replace legal advice. For a complete GDPR assessment, consult a data protection officer.
↓ See detailed results for each category below.
The website uses an encrypted connection (HTTPS).
Latest encryption active (TLS 1.3 — TLSv1.3).
The security certificate is valid (expires 2027-01-24).
Adequate encryption method (TLS_AES_128_GCM_SHA256, 128 bit).
HSTS is enabled — the browser is instructed to always use the encrypted connection.
HSTS duration: 31536000 seconds (at least 1 year) — very good.
HSTS preload is enabled — browsers know about the encryption before the first visit.
No Content Security Policy (CSP) found. The website has no protection against injected malicious code.
A Content Security Policy (CSP) is a doorkeeper rule for the browser: "Scripts and styles may only be loaded from these allowed sources." Without CSP, injected malicious code (XSS) can freely fetch anything. Start with a simple, secure baseline.
File: .htaccess in the web root
<IfModule mod_headers.c>
Header always set Content-Security-Policy "default-src 'self'; img-src 'self' data: https:; style-src 'self' 'unsafe-inline'; script-src 'self' 'unsafe-inline'; font-src 'self' https: data:; object-src 'none'; frame-ancestors 'self'; base-uri 'self'"
</IfModule>⚠ This policy is intentionally pragmatic (allows inline styles since many themes/plugins rely on them). If something breaks after enabling: F12 → Console shows "Refused to load…" — add the affected domain after script-src / img-src.
File: .htaccess in the WordPress root
# BEGIN WebForensik CSP
<IfModule mod_headers.c>
Header always set Content-Security-Policy "default-src 'self'; img-src 'self' data: https:; style-src 'self' 'unsafe-inline'; script-src 'self' 'unsafe-inline'; font-src 'self' https: data:; object-src 'none'; frame-ancestors 'self'; base-uri 'self'"
</IfModule>
# END WebForensik CSP⚠ WordPress often loads external scripts (Google Fonts, jQuery CDN, analytics pixel) — if CSP blocks them: open the console, see which domain is blocked, append that domain to "script-src 'self'" separated by a space.
File: functions.php of your CHILD theme
add_action('send_headers', function () {
header("Content-Security-Policy: default-src 'self'; img-src 'self' data: https:; style-src 'self' 'unsafe-inline'; script-src 'self' 'unsafe-inline'; font-src 'self' https: data:; object-src 'none'; frame-ancestors 'self'; base-uri 'self'");
});⚠ If unsure: start with "Content-Security-Policy-Report-Only" (only monitor, don’t block), watch violations in the console, then switch to enforced mode.
✓ How to verify it works: Open page, F12 → Console — no red "Refused to load…" messages. Network tab → first request → Response Header "content-security-policy" visible.
Referrer-Policy: no-referrer-when-downgrade (via HTTP-Header).
The setting "no-referrer-when-downgrade" shares too much URL information with other websites.
Your current Referrer-Policy reveals too much (e.g. "unsafe-url" or "no-referrer-when-downgrade"). Switch to a more privacy-friendly setting.
File: .htaccess in the web root
<IfModule mod_headers.c>
Header always set Referrer-Policy "strict-origin-when-cross-origin"
</IfModule>⚠ Replace the existing Referrer-Policy line.
File: .htaccess in the WordPress root
<IfModule mod_headers.c>
Header always set Referrer-Policy "strict-origin-when-cross-origin"
</IfModule>⚠ Replace the existing Referrer-Policy entry.
✓ How to verify it works: F12 → Network → Response Header — new value visible.
MIME type protection active (nosniff) — browsers will not misinterpret files.
Clickjacking protection active: X-Frame-Options = SAMEORIGIN.
No Permissions-Policy set. Third-party scripts could access camera, microphone, or location.
Permissions-Policy controls whether scripts (including third-party) may access camera, microphone, location, motion sensors etc. GDPR-relevant because sensitive device APIs can otherwise be reached unnoticed.
File: .htaccess in the web root
<IfModule mod_headers.c>
Header always set Permissions-Policy "camera=(), microphone=(), geolocation=(), payment=(), usb=(), accelerometer=(), gyroscope=(), magnetometer=(), interest-cohort=()"
</IfModule>⚠ "()" at the end means: no caller (not even your own page) may use this API. If you need geolocation (e.g. a map feature): use geolocation=(self) instead of geolocation=(). "interest-cohort=()" disables Google’s FLoC tracking.
File: .htaccess in the WordPress root
<IfModule mod_headers.c>
Header always set Permissions-Policy "camera=(), microphone=(), geolocation=(), payment=(), usb=(), interest-cohort=()"
</IfModule>⚠ Standard WordPress needs none of these APIs. If you use a plugin that needs the camera (QR scanner, video upload), set that API to "(self)".
File: functions.php of your CHILD theme
add_action('send_headers', function () {
header('Permissions-Policy: camera=(), microphone=(), geolocation=(), payment=(), usb=(), interest-cohort=()');
});⚠ Back up functions.php before edits.
✓ How to verify it works: F12 → Network → Response Header: "permissions-policy" visible.
16 first-party and 2 third-party cookie(s).
2 third-party cookie(s) detected. These can be used to track you across different websites.
Third-party cookies (e.g. from Google, Facebook) track visitors across websites. Under GDPR Art. 6 and ePrivacy / national implementations, explicit consent is required BEFORE setting them. Three-step fix: (1) identify which external services set the cookies, (2) remove services you don’t strictly need, (3) for essential services, add a consent banner that loads them only after "Accept".
WordPress plugin: Cookie-consent plugins for WordPress: "Complianz" (free, GDPR-focused, thorough wizard), "Real Cookie Banner" (free, knows many services), "Borlabs Cookie" (paid, most feature-complete). Critical configuration: set all tracking services to "do NOT load before consent" — most plugins detect standard services (GA, Maps, YouTube) automatically.
✓ How to verify it works: Open the site in incognito mode → F12 → Application → Cookies → your-domain.com. BEFORE clicking "Accept" there must be NO cookies from google.com, facebook.com etc. AFTER consent, yes.
8 of 18 cookie(s) without Secure flag — sent over unencrypted connections too.
Cookies without the "Secure" flag are also sent over unencrypted HTTP — and can be intercepted by anyone on the same WLAN. There’s no reason to omit Secure on HTTPS-only sites.
File: .htaccess in the web root
<IfModule mod_headers.c>
Header always edit Set-Cookie "^(.*)$" "$1; Secure" "expr=!(resp('Set-Cookie') -strmatch '*Secure*')"
</IfModule>⚠ This header appends "; Secure" to cookies that don’t have it yet. Requires Apache 2.4+. The cleaner fix is to correct the code that sets the cookie (PHP: session.cookie_secure=1 in php.ini, or setcookie() with "secure" => true).
File: functions.php of your CHILD theme
add_filter('secure_logged_in_cookie', '__return_true');
add_action('init', function () {
if (!headers_sent()) {
@ini_set('session.cookie_secure', '1');
@ini_set('session.cookie_httponly', '1');
@ini_set('session.cookie_samesite', 'Lax');
}
});⚠ Sets the Secure flag for WordPress login cookies and PHP session cookies. Plugins that set their own cookies must be configured separately (check plugin settings).
WordPress plugin: Plugins that set cookies (cache, anti-spam, A/B testing) often have toggles like "Secure cookies" or "HTTPS only" in their settings.
✓ How to verify it works: F12 → Application → Cookies → your-domain.com. The "Secure" column should show a checkmark for every cookie.
17 of 18 cookie(s) without HttpOnly flag — could be read by malicious code.
Cookies without the "HttpOnly" flag can be read by JavaScript — an XSS attacker can steal session cookies and impersonate the logged-in user. Set HttpOnly for all cookies JavaScript doesn’t actively need.
File: .htaccess in the web root
<IfModule mod_headers.c>
Header always edit Set-Cookie "^(.*)$" "$1; HttpOnly" "expr=!(resp('Set-Cookie') -strmatch '*HttpOnly*')"
</IfModule>⚠ Cleaner: set cookies with HttpOnly directly (PHP: setcookie(..., [..., 'httponly'=>true])). Exception: cookies that JS actively reads (e.g. some consent cookies).
File: functions.php of your CHILD theme (or better wp-config.php)
@ini_set('session.cookie_httponly', '1');
@ini_set('session.cookie_secure', '1');⚠ WordPress login cookies have been HttpOnly since 2.x. If you use a plugin that sets session cookies (e.g. WooCommerce cart pre-login), check its settings.
✓ How to verify it works: F12 → Application → Cookies → "HttpOnly" column shows checkmarks everywhere (except for deliberately JS-readable cookies like the consent cookie).
14 of 18 cookie(s) without SameSite protection — sent with requests from other websites.
Without "SameSite" cookies are sent on requests from foreign sites — the basis of CSRF attacks (a foreign page silently triggers actions in your name because the login cookie travels along). Set SameSite=Lax as a minimum.
File: .htaccess in the web root
<IfModule mod_headers.c>
Header always edit Set-Cookie "^(.*)$" "$1; SameSite=Lax" "expr=!(resp('Set-Cookie') -strmatch '*SameSite*')"
</IfModule>⚠ SameSite=Lax is a good default. Strict is safer but breaks external links (user clicks from Google to your site — cookies are NOT sent, login is lost). None allows cross-site but requires "; Secure".
File: wp-config.php (above "/* That’s all, stop editing! */")
@ini_set('session.cookie_samesite', 'Lax');
@ini_set('session.cookie_secure', '1');
@ini_set('session.cookie_httponly', '1');⚠ Sets SameSite/Secure/HttpOnly for PHP session cookies. WordPress login cookies have been SameSite=Lax since WP 6.2. Update older versions!
✓ How to verify it works: F12 → Application → Cookies → "SameSite" column should show "Lax" or "Strict" everywhere, not empty.
| Name | Domain | Encrypted | Server only | SameSite |
|---|---|---|---|---|
| optimizelyEndUserId | .bbc.com | No | No | None |
| optimizelySession | .bbc.com | No | No | None |
| ckns_explicit | .bbc.com | No | No | None |
| ckns_policy | .bbc.com | No | No | None |
| ckns_policy_exp | .bbc.com | No | No | None |
| ckns_privacy | .bbc.com | No | No | None |
| ckns_echo_device_id | .bbc.com | No | No | None |
| ckns_mvt | .bbc.com | No | No | None |
| _cb | .bbc.com | Yes | No | None |
| _chartbeat2 | .bbc.com | Yes | No | None |
| _cb_svref | .bbc.com | Yes | No | None |
| _sp_su | .bbc.com | Yes | No | None |
| _pprv | .bbc.com | Yes | No | Lax |
| pa_vid | .bbc.com | Yes | No | Lax |
| _pcid | .bbc.com | Yes | No | Lax |
| _pctx | .bbc.com | Yes | No | Lax |
| Name | Domain | Encrypted | Server only | SameSite |
|---|---|---|---|---|
| __cf_bm | .tinypass.com | Yes | Yes | None |
| https://www.bbc.com_oeu1788486466051r0.5 | a4621041136.cdn.optimizely.com | Yes | No | None |
18 localStorage and 0 sessionStorage item(s) found.
Extensive localStorage usage — may indicate tracking or fingerprinting.
Heavy use of localStorage may indicate tracking or fingerprinting (localStorage isn’t covered by cookie consent but can identify visitors). Check whether the data is actually needed — and if yes, mention it in your privacy policy.
WordPress plugin: localStorage usage usually originates from themes or plugins (cookie banner state, cart, A/B testing, slider position). Approach: F12 → Application → Local Storage → your-domain.com — review entries, identify the likely culprit (plugin name in the key), check the plugin settings or remove the plugin if not needed.
✓ How to verify it works: Incognito browser → load page once → F12 → Application → Local Storage — as few entries as possible, none without a clear functional purpose.
| Name | Value |
|---|---|
| optimizely_data$$oeu1788486466051r0.56990511409795 | {"profile":{"visitorId":"oeu1788486466051r0.5699051140979545","customBehavior":{ |
| optimizely_data$$oeu1788486466051r0.56990511409795 | {} |
| _cb | DuS_LjCnuk_oCsWnSG |
| optimizely_data$$oeu1788486466051r0.56990511409795 | {"lastSessionTimestamp":1788486466053,"sessionId":"6169d463-2c48-41ec-a4f9-d62b9 |
| _chartbeat2 | .1788486466692.1788486466692.1.mTAZKDcW_efDBOpK6C9CJBDBbdkeY.1 |
| optimizely_data$$oeu1788486466051r0.56990511409795 | {} |
| optimizely_data$$oeu1788486466051r0.56990511409795 | {} |
| _cb_expires | 1822614466694 |
| _sp_non_keyed_local_state | {"gdpr":{"_sp_v1_data":"1293142","_sp_v1_p":"882"},"usnat":{"_sp_v1_data":"13111 |
| _cb_svref | external |
| optimizely_data$$pending_events | {} |
| _chartbeat2_expires | 1822614466694 |
| _sp_local_state | {"gdpr":{"mmsCookies":["_sp_v1_ss=1:H4sIAAAAAAAAAItWqo5RKimOUbKKxs_IAzEMamN1YpRS |
| _sp_user_consent_26225 | {"gdpr":{"authId":null,"uuid":null,"getMessageAlways":false,"applies":true,"acti |
| optimizely_data$$oeu1788486466051r0.56990511409795 | [{"eb":{"n":"27302320011_homepage","y":"pageview","c":"other"},"h":"cd08b61","tb |
| optimizely_data$$oeu1788486466051r0.56990511409795 | [] |
| optimizely_data$$oeu1788486466051r0.56990511409795 | {} |
| _cb_svref_expires | 1788488266697 |
118 request(s) to 16 different third-party servers.
8 third-party server(s) outside the EU/EEA — potentially problematic for GDPR compliance.
GDPR-relevant: visitor data (at least IP + User-Agent) is transmitted to servers outside the EU/EEA. Since the Schrems-II ruling (2020) this requires Standard Contractual Clauses + supplementary technical measures AND prior consent. Best fix: replace with EU alternatives where possible.
WordPress plugin: Common culprits and EU alternatives: Google Fonts → Bunny Fonts or self-host (plugin "OMGF — Host Google Fonts Locally"). Google Analytics → Matomo (self-hosted) or Plausible (EU servers). Google reCAPTCHA → hCaptcha (EU) or Friendly Captcha. Google Maps → OpenStreetMap. YouTube embeds → plugin "WP YouTube Lyte" loads only after click. CDN: Cloudflare → BunnyCDN (EU) or KeyCDN.
✓ How to verify it works: Load in incognito mode, F12 → Network → list all requests → check "Domain" column for non-EU servers. After migration no unrequested US domains should load.
8 third-party server(s) within the EU/EEA.
Requested URLs:
https://static.files.bbci.co.uk/bbcdotcom/web/20260901-153401-e72efa0ccc-web-3.20.0-5/grey-placeholder.png
https://static.files.bbci.co.uk/fonts/reith/2.512/BBCReithSans_W_Md.woff2
https://static.files.bbci.co.uk/fonts/reith/2.512/BBCReithSans_W_Bd.woff2
https://static.files.bbci.co.uk/fonts/reith/2.512/BBCReithSans_W_Rg.woff2
https://static.files.bbci.co.uk/fonts/reith/2.512/BBCReithSerif_W_Md.woff2
https://static.files.bbci.co.uk/fonts/reith/2.512/BBCReithSerif_W_Rg.woff2
https://static.files.bbci.co.uk/fonts/reith/2.512/BBCReithSans_W_ExBd.woff2
https://static.files.bbci.co.uk/bbcdotcom/web/20260901-153401-e72efa0ccc-web-3.20.0-5/_next/static/chunks/178~uy1pz6ka4.js
https://static.files.bbci.co.uk/bbcdotcom/web/20260901-153401-e72efa0ccc-web-3.20.0-5/_next/static/chunks/0b-tgv1_m1uo9.js
https://static.files.bbci.co.uk/bbcdotcom/web/20260901-153401-e72efa0ccc-web-3.20.0-5/_next/static/chunks/0d9awe5d081.2.js
https://static.files.bbci.co.uk/bbcdotcom/web/20260901-153401-e72efa0ccc-web-3.20.0-5/_next/static/chunks/0s_kjwmep8yf2.js
https://static.files.bbci.co.uk/bbcdotcom/web/20260901-153401-e72efa0ccc-web-3.20.0-5/_next/static/chunks/0-a6nanpwqpag.js
https://static.files.bbci.co.uk/bbcdotcom/web/20260901-153401-e72efa0ccc-web-3.20.0-5/_next/static/chunks/02w2j6a0szhem.js
https://static.files.bbci.co.uk/bbcdotcom/web/20260901-153401-e72efa0ccc-web-3.20.0-5/_next/static/chunks/01ahds4cdfgmx.js
https://static.files.bbci.co.uk/bbcdotcom/web/20260901-153401-e72efa0ccc-web-3.20.0-5/_next/static/chunks/0r3he0rx_9z1a.js
https://static.files.bbci.co.uk/bbcdotcom/web/20260901-153401-e72efa0ccc-web-3.20.0-5/_next/static/chunks/0k530rgixl5kt.js
https://static.files.bbci.co.uk/bbcdotcom/web/20260901-153401-e72efa0ccc-web-3.20.0-5/_next/static/chunks/004vauwu48azs.js
https://static.files.bbci.co.uk/bbcdotcom/web/20260901-153401-e72efa0ccc-web-3.20.0-5/_next/static/chunks/0jl4bpoiy-t_p.js
https://static.files.bbci.co.uk/bbcdotcom/web/20260901-153401-e72efa0ccc-web-3.20.0-5/_next/static/chunks/0zpz-874su9ks.js
https://static.files.bbci.co.uk/bbcdotcom/web/20260901-153401-e72efa0ccc-web-3.20.0-5/_next/static/chunks/0jcf7ay9pl-s0.js
... and 57 more request(s)
Requested URLs:
https://cdn.privacy-mgmt.com/unified/wrapperMessagingWithoutDetection.js
https://cdn.privacy-mgmt.com/mms/v2/get_site_data?hasCsp=true&href=https%3A%2F%2Fwww.bbc.com%2F&account_id=1786
https://cdn.privacy-mgmt.com/unified/4.40.2/gdpr-tcf.27718c8cb9d29947d2c1.bundle.js
https://cdn.privacy-mgmt.com/unified/4.40.2/usnat.f12613136193900e32e2.bundle.js
https://cdn.privacy-mgmt.com/wrapper/v2/meta-data?hasCsp=true&accountId=1786&env=prod&metadata=%7B%22gdpr%22%3A%7B%7D%2C%22usnat%22%3A%7B%7D%7D&propertyId=26225&scriptVersion=4.40.2&scriptType=unified
https://cdn.privacy-mgmt.com/wrapper/v2/messages?hasCsp=true&env=prod&body=%7B%22accountId%22%3A1786%2C%22campaignEnv%22%3A%22prod%22%2C%22campaigns%22%3A%7B%22gdpr%22%3A%7B%22consentStatus%22%3A%7B%7
https://cdn.privacy-mgmt.com/index.html?hasCsp=true&message_id=1489022&consentUUID=null&consent_origin=https%3A%2F%2Fcdn.privacy-mgmt.com%2Fconsent%2Ftcfv2&preload_message=true&version=v1
https://cdn.privacy-mgmt.com/polyfills.c181d.js
https://cdn.privacy-mgmt.com/Notice.f8557.css
https://cdn.privacy-mgmt.com/Notice.22547.js
https://cdn.privacy-mgmt.com/consent/tcfv2/vendor-list/categories?siteId=26225
https://cdn.privacy-mgmt.com/consent/tcfv2/vendor-list/categories?siteId=26225&consentLanguage=en
Requested URLs:
https://ichef.bbci.co.uk/news/480/cpsprodpb/bbd3/live/c8f1c0a0-a7b2-11f1-b5cf-8556ae508a51.jpg.webp
https://ichef.bbci.co.uk/news/640/cpsprodpb/f49e/live/1194d700-a7f1-11f1-8061-c3b9abc6cd07.jpg.webp
https://ichef.bbci.co.uk/images/ic/1024x576/p0p7skvf.jpg.webp
https://ichef.bbci.co.uk/images/ic/480x270/p0p7p4wq.jpg.webp
https://ichef.bbci.co.uk/images/ic/480x270/p0p7tq6c.jpg.webp
https://ichef.bbci.co.uk/news/480/cpsprodpb/e958/live/adbdf2a0-a7fc-11f1-a291-b542ee92de7c.jpg.webp
https://ichef.bbci.co.uk/images/ic/1024x576/p0p7l8dj.jpg.webp
https://ichef.bbci.co.uk/images/ic/480x270/p0dlxpgc.jpg.webp
https://ichef.bbci.co.uk/images/ic/480x270/p0p310rj.jpg.webp
https://ichef.bbci.co.uk/images/ic/480x270/p0p7x1hn.jpg.webp
https://ichef.bbci.co.uk/images/ic/480x270/p0p7tds3.jpg.webp
Requested URLs:
https://uk-script.dotmetrics.net/door.js?d=www.bbc.com&t=homestudio
https://uk-script.dotmetrics.net/hit.gif?id=13934&url=https%3A%2F%2Fwww.bbc.com%2F&dom=www.bbc.com&r=1788486466730&pvs=1&ecid=bb239392-52df-4ccc-a38e-bd6281069e84&c=false&tzOffset=-120&doorUrl=http%3a
https://uk-script.dotmetrics.net/Scripts/ncs-script.js?v=366
Requested URLs:
https://mybbc-analytics.files.bbci.co.uk/echo-client-js/echo-2.6.0-avi.min.js
https://mybbc-analytics.files.bbci.co.uk/analytics-remote-config/producers.json
https://mybbc-analytics.files.bbci.co.uk/analytics-remote-config/masterbrands.json
Requested URLs:
https://cdn.optimizely.com/public/4621041136/s/bbcx_prod.js
https://cdn.optimizely.com/datafiles/Y67aV8AjKqtntTQvKqk1A.json
Requested URLs:
https://static.bbci.co.uk/frameworks/requirejs/0.13.0/sharedmodules/require.js
Requested URLs:
https://emp.bbci.co.uk/emp/bump-4/bump-4.js
Requested URLs:
https://cdn.tinypass.com/api/tinypass.min.js
Requested URLs:
https://a4621041136.cdn.optimizely.com/client_storage/a4621041136.html
Requested URLs:
https://www.bbc.co.uk/userinfo
Requested URLs:
https://static.chartbeat.com/js/chartbeat.js
Requested URLs:
https://rm-script.dotmetrics.net/hit.gif?id=13934&url=https%3A%2F%2Fwww.bbc.com%2F&dom=www.bbc.com&r=1788486466730&pvs=1&pvid=bb239392-52df-4ccc-a38e-bd6281069e84&c=false&tzOffset=-120
Requested URLs:
https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js
Requested URLs:
https://ping.chartbeat.net/ping?h=bbc.com&p=%2F&u=DuS_LjCnuk_oCsWnSG&d=bbc.com&g=50924&g0=No%20Section&g1=NewsMediaOrganization&g4=indexPage&n=1&f=00001&c=0&x=0&m=0&y=12582&o=1920&w=1080&j=45&R=1&W=0&
Requested URLs:
https://logx.optimizely.com/v1/events
1 known tracker(s) detected! These track visitors across different websites.
Trackers (Google Analytics, Facebook Pixel, …) capture visitors and follow them across multiple sites. Under GDPR Art. 6 and ePrivacy / national implementations, explicit consent is required BEFORE loading the tracker. "Continued scrolling = consent" is NOT acceptable.
WordPress plugin: Consent plugins that properly block trackers until consent: "Complianz" (free, very good), "Real Cookie Banner", "Borlabs Cookie" (paid, most thorough). Principle after setup: do NOT embed the tracker snippet (e.g. GA script) directly in your theme — register it with the consent plugin, which only releases it after "Accept". Privacy-friendly tracker alternatives: Matomo (cookieless mode → may need no consent), Plausible (EU, anonymous, vendor claims no consent needed — legal advice recommended).
✓ How to verify it works: Incognito, load page — BEFORE "Accept": F12 → Network → no requests to google-analytics.com, facebook.com/tr etc. AFTER "Accept", yes.
Chartbeat (Content): static.chartbeat.com
0 of 80 external resource(s) use integrity verification (SRI).
Only some of your external resources (0 of 80) are protected by SRI. Add integrity attributes to the remaining ones too.
WordPress plugin: Approach: view page source → all <script src="https://…"> and <link href="https://…"> without integrity attribute → generate hash at https://www.srihash.org/ → add integrity="sha384-…" crossorigin="anonymous". Plugin "WP-SRI" automates many cases.
✓ How to verify it works: F12 → Console on page load: no "Failed to find a valid digest" messages. Source: all external <script>/<link> have an integrity attribute.
No external resources use integrity verification. Tampered files would not be detected.
SRI (Subresource Integrity) is a checksum in HTML that defines what an externally loaded file MUST look like. If someone tampers with the external file (e.g. a CDN gets compromised), the browser refuses to load it. You add the "integrity" attribute on the script/link tag.
WordPress plugin: In WordPress you can rarely add SRI hashes manually (scripts are queued via wp_enqueue_script()). Plugin "WP-SRI" (in the plugin directory) adds integrity hashes automatically for external scripts/styles. For statically embedded resources in your theme: generate the hash at https://www.srihash.org/, add integrity="sha384-…" and crossorigin="anonymous" on the <script>/<link> tag.
✓ How to verify it works: F12 → Network → requests with status 200 from CDN domains (cdn.jsdelivr.net, cdnjs.cloudflare.com etc.) → in HTML source the tag must contain "integrity=\"sha384-…\" crossorigin=\"anonymous\"".
No CAA records. Any certificate authority could issue a certificate for this domain.
CAA records (Certification Authority Authorization) define in DNS which Certificate Authorities are allowed to issue certificates for your domain. Without a CAA record an attacker could request a fraudulent certificate for your domain at any CA. CAA is pure DNS configuration — set in your registrar/DNS-panel, NOT in WordPress.
Find your host in the table, copy the values to your DNS panel. For multi-CA hosts: one separate CAA record per CA (all with tag issue, flag 0, name @). Additionally recommended: an iodef record with a contact email for abuse reports.
| # | Host | CA(s) used | CAA value(s) — tag issue |
|---|---|---|---|
| 1 | Hetzner Webhosting (basic certificate, free in package) | DigiCert (programme „Encryption Everywhere") | digicert.com |
| 1 | Hetzner Webhosting (Let’s Encrypt, free) | Let’s Encrypt (ISRG) | letsencrypt.org |
| 2 | All-Inkl | Let’s Encrypt + Sectigo (Pro) | letsencrypt.orgsectigo.com |
| 3 | IONOS (1&1) | DigiCert (GeoTrust) + Let’s Encrypt | digicert.comletsencrypt.org |
| 4 | STRATO | Sectigo + Let’s Encrypt | sectigo.comletsencrypt.org |
| 5 | Cloudflare (Universal SSL) | Google Trust Services + DigiCert + Let’s Encrypt | pki.googdigicert.comletsencrypt.org |
| 6 | AWS (ACM / CloudFront) | Amazon Trust Services | amazon.comamazontrust.comawstrust.comamazonaws.com |
| 7 | Mittwald | Let’s Encrypt + Sectigo | letsencrypt.orgsectigo.com |
| 8 | Webgo | Let’s Encrypt + Sectigo | letsencrypt.orgsectigo.com |
| 9 | raidboxes (Managed WordPress) | Let’s Encrypt | letsencrypt.org |
| 10 | Host Europe / DomainFactory | Sectigo + Let’s Encrypt | sectigo.comletsencrypt.org |
Name Type Flag Tag Value
@ CAA 0 issue "digicert.com"
@ CAA 0 issue "letsencrypt.org"
@ CAA 0 iodef "mailto:security@your-domain.com"
The iodef line (last line) is optional but recommended: CAs report abuse attempts to that address. For subdomains (e.g. shop.your-domain.com) create separate records with the subdomain name instead of @ — modern CAs check parent CAA automatically though.
If your host is not on the list: open your current certificate in the browser (padlock → certificate → issuer). The CA name is shown there (e.g. "Sectigo RSA Domain Validation Secure Server CA" → value sectigo.com). Add that as a CAA record, done.
WordPress plugin: CAA records are NOT created in WordPress but in your domain registrar / DNS provider panel (e.g. Hetzner-Robot, IONOS Domains, Cloudflare Dashboard, INWX, etc.). Common label: "CAA record" or under "TXT records" with type selector "CAA". One separate record per CA.
✓ How to verify it works: On https://www.ssllabs.com/ssltest/analyze.html?d=your-domain.com → "DNS CAA" section → all your CAs should be listed. Or via dig: dig CAA your-domain.com.
No IPv6 support (no AAAA record).
Your domain has no IPv6 address (AAAA record). Over 40% of users (especially mobile) reach the internet via IPv6 — they must take the slower IPv4 gateway detour.
WordPress plugin: Pure DNS + server matter. Step 1: check if your host has an IPv6 address for you (hosting panel or support ticket). Step 2: in the DNS panel create an AAAA record pointing to that IPv6. Step 3: test.
✓ How to verify it works: dig AAAA your-domain.com — or online https://ipv6-test.com/validate.php?url=your-domain.com.
No SPF record. Emails can be forged in the name of this domain.
SPF (Sender Policy Framework) defines in DNS which servers may send emails on behalf of your domain. Without SPF any phisher can spoof emails from you — and recipients are more likely to fall for them.
WordPress plugin: DNS matter, not WordPress. Create a TXT record in your DNS panel. Examples: If you send NO emails: v=spf1 -all (reject all senders). If only your host sends: v=spf1 a mx ~all. If Google Workspace: v=spf1 include:_spf.google.com ~all. If Microsoft 365: v=spf1 include:spf.protection.outlook.com -all.
✓ How to verify it works: dig TXT your-domain.com | grep spf — or online https://www.kitterman.com/spf/validate.html.
No DMARC record. The domain is vulnerable to email phishing.
DMARC combines SPF and DKIM into an explicit instruction for receiving mail servers: "What to do if emails claim to come from us but SPF/DKIM fail?" Without DMARC each server decides — usually generously. With DMARC=reject you effectively prevent phishing in your name.
WordPress plugin: DNS matter. TXT record at subdomain _dmarc.your-domain.com. Recommended stages: Observe first: v=DMARC1; p=none; rua=mailto:dmarc-reports@your-domain.com — review reports for weeks. Then tighten: v=DMARC1; p=quarantine; rua=… — suspicious mails go to spam. Final: v=DMARC1; p=reject; rua=… — they’re refused outright.
✓ How to verify it works: dig TXT _dmarc.your-domain.com — or online https://dmarcian.com/dmarc-inspector/.
security.txt found: https://www.bbc.com/.well-known/security.txt
Contact field present (required) — security researchers can report vulnerabilities.
Expires field present (required).
Preferred languages specified.
Network Error Logging (NEL) active — network errors are reported to an external service.
Network Error Logging (NEL) reports network errors to an external server. As with external reporting: mention in privacy policy and verify GDPR compliance of the recipient.
File: .htaccess in the web root
<IfModule mod_headers.c>
Header always unset NEL
Header always unset Report-To
</IfModule>⚠ If you don’t actively need NEL: these two lines remove both reporting headers. If you do: document it in the privacy policy.
File: .htaccess in the WordPress root
<IfModule mod_headers.c>
Header always unset NEL
Header always unset Report-To
</IfModule>⚠ If headers come from a plugin, configure the plugin instead.
✓ How to verify it works: F12 → Network → first request → Response Header: NO "nel" or "report-to" anymore (or deliberately documented).
Data is reported to external service: Report-To: default.bbc-reporting-api.app
Your site sends error or CSP reports to an external service (Report-To: default.bbc-reporting-api.app). GDPR-relevant: at least IP address and URL are transmitted. Verify (a) the recipient is GDPR-compliant, (b) the transfer is mentioned in your privacy policy, (c) a data processing agreement (DPA) exists.
WordPress plugin: If you didn’t set up the reporting endpoint yourself, it usually comes from a plugin (e.g. Sentry, Rollbar, Datadog). Check the plugin configuration — either disable, replace with an EU vendor, or gate behind consent.
✓ How to verify it works: Privacy policy contains an entry about error reporting + DPA is in place. In incognito: F12 → Network → no unintended reporting requests.
Cookie consent system detected: TCF API (__tcfapi).
TCF-compliant consent system (Transparency & Consent Framework) — IAB standard.
Consent system detected, but banner does not appear to be visible.
Your consent system is wired up but the banner doesn’t appear visibly — perhaps hidden by another plugin or custom CSS. Risk: without a visible banner, no consent is given.
WordPress plugin: Approach: 1) clear browser cache + cookies, use incognito. 2) In the consent plugin: check display conditions (e.g. "only EU visitors" — and you’re testing from a non-EU server). 3) F12 → Console for red errors from consent scripts. 4) Inspector → search DOM for "cookie", "consent" — element present but display:none? z-index too low? 5) Uninstall conflicting cookie-notice plugins.
✓ How to verify it works: Incognito tab, load page, wait 5 seconds — banner visible centered/bottom, doesn’t fully block main content, is clickable.
Trackers are loaded on page load — possibly BEFORE consent is given.
Your trackers are loaded BEFORE the user can consent ("pre-consent loading"). Common misconfiguration in cookie plugins — banner appears, but too late: the GA script is already running. Violates ePrivacy.
WordPress plugin: Almost always caused by the theme or a tracking plugin that embeds the tracker code directly (e.g. "Google Analytics for WordPress" with auto-insert). Fix: 1) remove tracking code from the theme/plugin. 2) In the consent plugin (Complianz/Real Cookie Banner): register the tracker as a "service", paste the snippet there — the plugin will load it only after consent. 3) ALTERNATIVELY: plugin "PYS PixelYourSite" combined with consent gating. Do NOT rely on "GA anonymized before consent" — legally unsettled and risky.
✓ How to verify it works: Incognito → F12 → Network (clear all, start recording) → load page, do NOT click banner, wait 10 seconds → there must be NO requests to google-analytics.com, googletagmanager.com, facebook.com/tr, doubleclick.net etc.
Privacy policy linked: "How to watch the cameras that watch youIn the trade-off between privacy and security, it helps to kn" (/future/article/20260902-how-to-watch-the-cameras-that-watch-you).
No legal notice (Impressum) found — required under German law (§ 5 DDG).
No imprint (legal notice) found — mandatory in Germany under § 5 DDG for all business-grade websites (and effectively for many other commercial sites in the EU). Even private blogs with ad or affiliate revenue typically require one. Violations are commonly targeted by warning letters.
WordPress plugin: Step 1: create an imprint. Free generator (German law): https://www.e-recht24.de/impressum-generator.html. Mandatory information includes: full legal name, postal address (no P.O. box), phone OR another second contact, email, for companies: trade register + VAT ID, supervisory authority if applicable, professional liability insurance if applicable. Step 2: in WordPress → Pages → Add New → title "Imprint" → publish. Step 3: footer menu → add "Imprint". IMPORTANT: the imprint must be "easily recognizable, directly accessible, permanently available" — a footer link satisfies this, an "About us" → "then imprint" does NOT.
✓ How to verify it works: Footer on every page → link "Imprint" or "Legal notice" visible → opens the imprint page with all mandatory information.
Privacy policy page is accessible (HTTP 200).
All missing security headers combined into one block. Append this block to the end of your .htaccess — done. 6 headers will be set.
The Content-Security-Policy above deliberately includes 'unsafe-inline' for both style-src and script-src. This does NOT provide full XSS protection — it's a pragmatic trade-off, not a bug.
Why? A typical WordPress setup (theme + 5-15 plugins) emits 10-50 different inline <script> blocks into the HTML: jQuery init, slider init, cookie banner, tracking, GTM, web vitals, lazy-load, speculation rules and so on. A strict script-src 'self' blocks them all — the site becomes visually and functionally broken (blank slider, broken cookie banner, dead plugins).
Consequence for scoring: Sites running WordPress with plugins can score at most ~75-85 points in the CSP category in this app — the full 100% rating is only achievable when inline code is signed via nonce or hash (technically demanding, breaks on every theme/plugin update).
Paths to full XSS protection (in increasing complexity):
Anyone who doesn't take one of these paths lives with 'unsafe-inline' — like about 95% of all production WordPress sites on the web. The other CSP directives still protect: default-src 'self' blocks external resources, object-src 'none' bans Flash/Java, frame-ancestors 'self' prevents clickjacking, base-uri 'self' prevents base-tag hijacking. Not maximum protection, but realistic protection for WP reality.
On Hetzner-Konsoleh webhosting (and comparable shared hosts like All-Inkl, IONOS, Strato, 1blu, …), Apache throws a 500 Internal Server Error as soon as Header always edit Set-Cookie … expr=… appears in .htaccess. The Apache error log says:
Can't parse envclause/expression: syntax error, unexpected T_OP_STR_EQ, expecting $end
This is not a WebForensik bug and not a typo — the shared host has blocked the mod_headers expr= subset via AllowOverride limits (for security, because Header edit could also manipulate cookies of other tenants).
☛ For Hetzner-Konsoleh users: use the variant below marked with the red "Hetzner / Shared" badge. It consists of two files (.htaccess + wp-config.php) instead of one, but avoids the 500 error reliably. Cookie flags go into wp-config.php instead of .htaccess.
Append this block to the end of your .htaccess in the web root — done.
<IfModule mod_headers.c>
Header always set Content-Security-Policy "default-src 'self'; img-src 'self' data: https:; style-src 'self' 'unsafe-inline'; script-src 'self' 'unsafe-inline'; font-src 'self' https: data:; object-src 'none'; frame-ancestors 'self'; base-uri 'self'; upgrade-insecure-requests"
Header always set Referrer-Policy "strict-origin-when-cross-origin"
Header always set Permissions-Policy "camera=(), microphone=(), geolocation=(), payment=(), usb=(), accelerometer=(), gyroscope=(), magnetometer=(), interest-cohort=(), browsing-topics=()"
# Fehlende Cookie-Flags konditional ergänzen (nur wenn nicht schon gesetzt)
Header always edit Set-Cookie "^(.*)$" "$1; Secure" "expr=!(resp('Set-Cookie') -strmatch '*Secure*')"
Header always edit Set-Cookie "^(.*)$" "$1; HttpOnly" "expr=!(resp('Set-Cookie') -strmatch '*HttpOnly*')"
Header always edit Set-Cookie "^(.*)$" "$1; SameSite=Lax" "expr=!(resp('Set-Cookie') -strmatch '*SameSite*')"
</IfModule>
This variant avoids the 500 Internal Server Error on Hetzner-Konsoleh and similar shared hosts (All-Inkl, IONOS, Strato, 1blu …): the .htaccess only contains the header directives (no "Header edit"), cookie flags move into wp-config.php. Two files to edit instead of one, but guaranteed to run.
# BEGIN WebForensik Security
<IfModule mod_headers.c>
Header always set Content-Security-Policy "default-src 'self'; img-src 'self' data: https:; style-src 'self' 'unsafe-inline'; script-src 'self' 'unsafe-inline'; font-src 'self' https: data:; object-src 'none'; frame-ancestors 'self'; base-uri 'self'; upgrade-insecure-requests"
Header always set Referrer-Policy "strict-origin-when-cross-origin"
Header always set Permissions-Policy "camera=(), microphone=(), geolocation=(), payment=(), usb=(), accelerometer=(), gyroscope=(), magnetometer=(), interest-cohort=(), browsing-topics=()"
</IfModule>
# END WebForensik Security
Insert ABOVE the line "/* That's all, stop editing! */". Back up wp-config.php first!
// === WebForensik: Cookie-Hardening (Hetzner-Konsoleh-tauglich) ===
// Bitte OBERHALB der Zeile "/* That's all, stop editing! */" einfügen.
// Wirkt auf PHP-Session- und WordPress-Login-Cookies.
// Plugin-eigene Cookies (z.B. WooCommerce, Cookie-Banner) müssen in den
// Plugin-Einstellungen separat auf "Secure" gestellt werden.
@ini_set('session.cookie_secure', '1');
@ini_set('session.cookie_httponly', '1');
@ini_set('session.cookie_samesite', 'Lax');
if (!defined('FORCE_SSL_ADMIN')) define('FORCE_SSL_ADMIN', true);
Insert this block ABOVE the "# BEGIN WordPress" line, otherwise WP overwrites it on permalink changes.
# BEGIN WebForensik Security
<IfModule mod_headers.c>
Header always set Content-Security-Policy "default-src 'self'; img-src 'self' data: https:; style-src 'self' 'unsafe-inline'; script-src 'self' 'unsafe-inline'; font-src 'self' https: data:; object-src 'none'; frame-ancestors 'self'; base-uri 'self'; upgrade-insecure-requests"
Header always set Referrer-Policy "strict-origin-when-cross-origin"
Header always set Permissions-Policy "camera=(), microphone=(), geolocation=(), payment=(), usb=(), accelerometer=(), gyroscope=(), magnetometer=(), interest-cohort=(), browsing-topics=()"
# Fehlende Cookie-Flags konditional ergänzen
Header always edit Set-Cookie "^(.*)$" "$1; Secure" "expr=!(resp('Set-Cookie') -strmatch '*Secure*')"
Header always edit Set-Cookie "^(.*)$" "$1; HttpOnly" "expr=!(resp('Set-Cookie') -strmatch '*HttpOnly*')"
Header always edit Set-Cookie "^(.*)$" "$1; SameSite=Lax" "expr=!(resp('Set-Cookie') -strmatch '*SameSite*')"
</IfModule>
# END WebForensik Security
If your host disallows .htaccess changes: append this PHP snippet to the end of your CHILD theme's functions.php. Back up first — NEVER edit the parent theme, it gets overwritten on updates.
add_action('send_headers', function () {
header("Content-Security-Policy: default-src 'self'; img-src 'self' data: https:; style-src 'self' 'unsafe-inline'; script-src 'self' 'unsafe-inline'; font-src 'self' https: data:; object-src 'none'; frame-ancestors 'self'; base-uri 'self'; upgrade-insecure-requests");
header("Referrer-Policy: strict-origin-when-cross-origin");
header("Permissions-Policy: camera=(), microphone=(), geolocation=(), payment=(), usb=(), accelerometer=(), gyroscope=(), magnetometer=(), interest-cohort=(), browsing-topics=()");
});
// Cookie-Flags für PHP-Session-Cookies — wirkt nur auf $_SESSION,
// NICHT auf von Plugins/Themes per setcookie() gesetzte Cookies.
// Für umfassende Cookie-Absicherung die .htaccess-Variante oben verwenden.
add_action('init', function () {
if (headers_sent()) return;
@ini_set('session.cookie_secure', '1');
@ini_set('session.cookie_httponly', '1');
@ini_set('session.cookie_samesite', 'Lax');
}, 1);
| Header | Value |
|---|---|
| accept-ranges | bytes |
| alt-svc | h3=":443";ma=86400,h3-29=":443";ma=86400,h3-27=":443";ma=86400 |
| belfrage-cache-status | MISS |
| bid | sally |
| brequestid | 65fe1dd0d1dd49f6b35074dce57ca653 |
| bsig | 2cf064e14eb09c62a19b776a6952424f |
| cache-control | public, stale-if-error=90, stale-while-revalidate=30, max-age=30 |
| content-encoding | gzip |
| content-length | 89154 |
| content-type | text/html; charset=utf-8 |
| date | Fri, 04 Sep 2026 01:47:45 GMT |
| etag | "2y1j9rmp1dczq6" |
| fastly-restarts | 1 |
| nel | {"report_to":"default","max_age":2592000,"include_subdomains":true,"failure_fraction":0.25} |
| origin-agent-cluster | ?0 |
| referrer-policy | no-referrer-when-downgrade |
| report-to | {"group":"default","max_age":2592000,"endpoints":[{"url":"https://default.bbc-reporting-api.app/report-endpoint","priority":1}],"include_subdomains":true} |
| req-svc-chain | FASTLY,GTM,BELFRAGE,BBCX |
| server | BBC-GTM |
| strict-transport-security | max-age=31536000; preload |
| vary | X-BBC-Edge-Scheme,Accept-Encoding |
| via | 1.1 BBC-GTM, 1.1 Belfrage, 1.1 varnish |
| x-bbc-edge-cache-status | HIT |
| x-cache | HIT |
| x-cache-age | 25 |
| x-cache-hits | 1 |
| x-content-type-options | nosniff |
| x-correlation-id | 35466985-b7db-4c3c-90cd-ef2415abe72d |
| x-fastly-cache-status | HIT-CLUSTER |
| x-fastly-pre-flight-cache | MISS, MISS |
| x-fastly-pre-flight-cache-status | MISS-CLUSTER |
| x-frame-options | SAMEORIGIN |
| x-lb-nocache | true |
| x-robots-tag | bingbot: noarchive |
| x-served-by | cache-fra-etou8220098-FRA |
| x-timer | S1788486466.913123,VS0,VE17 |