Scan time: 2026-09-01 14:17:17
Overall Score
Compared with scan from 01.09.2026 14:16 (Score 93 → 61) · View full history
⚠ Protections lost: hsts_present, hsts_maxage_good, csp_found, csp_script_ok, csp_frame_ancestors, csp_default_self, refpol_set, refpol_strict, xcto_ok, xfo_csp, permpol_present, permpol_good
✓ Protections gained: cookies_summary
⚠ This website needs improvement regarding data protection.
GDPR Issues Detected (2):
⚠ 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-02-15).
Strong encryption method (TLS_AES_256_GCM_SHA384, 256 bit).
No HSTS header set. Browsers are not forced to use the encrypted connection.
HSTS (HTTP Strict Transport Security) tells the browser: "Always use HTTPS for this domain — no matter what." This prevents attackers on the same WLAN from intercepting the first, unprotected request. Prerequisite: your site is already stable on HTTPS.
File: .htaccess in the web root
<IfModule mod_headers.c>
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains"
</IfModule>⚠ max-age=31536000 equals 1 year (in seconds). includeSubDomains also covers blog.your-domain.com, shop.your-domain.com etc. — only enable if ALL subdomains support HTTPS, otherwise they become unreachable.
File: .htaccess in the WordPress root
# BEGIN WebForensik HSTS
<IfModule mod_headers.c>
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains"
</IfModule>
# END WebForensik HSTS⚠ Insert ABOVE the "# BEGIN WordPress" line. Only enable once HTTPS has been stable for a few days — the header is intentionally hard to roll back (browsers remember the instruction).
File: functions.php of your CHILD theme (Appearance → Theme File Editor → functions.php)
add_action('send_headers', function () {
header('Strict-Transport-Security: max-age=31536000; includeSubDomains');
});⚠ NEVER edit the parent theme — changes are lost on update. Back up functions.php first!
✓ How to verify it works: DevTools (F12) → Network tab → reload page → click the first request → "Response Headers" — must contain "strict-transport-security: max-age=31536000…".
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.
No Referrer-Policy set. When clicking external links, the full page URL is shared with other websites.
Without a Referrer-Policy the browser sends the full URL of your current page (incl. search terms, usernames in URL params) on every click to the destination site. Privacy-relevant per Art. 5 GDPR. Recommended safe setting: strict-origin-when-cross-origin.
File: .htaccess in the web root
<IfModule mod_headers.c>
Header always set Referrer-Policy "strict-origin-when-cross-origin"
</IfModule>⚠ "strict-origin-when-cross-origin" is the modern standard: on cross-domain clicks only your origin (no path/params) is sent — internal clicks include the full URL. Stricter is "no-referrer" (send nothing) but it breaks some analytics tools.
File: .htaccess in the WordPress root
<IfModule mod_headers.c>
Header always set Referrer-Policy "strict-origin-when-cross-origin"
</IfModule>⚠ WordPress 4.9+ already emits a meta-tag with this policy, but the HTTP header above applies to all resources (images, scripts) — not only the HTML document.
File: functions.php of your CHILD theme
add_action('send_headers', function () {
header('Referrer-Policy: strict-origin-when-cross-origin');
});⚠ Always back up functions.php before edits.
✓ How to verify it works: F12 → Network → first request → Response Headers — "referrer-policy: strict-origin-when-cross-origin" must be present.
No MIME type protection (X-Content-Type-Options missing). Browsers may misinterpret files.
Without the "X-Content-Type-Options: nosniff" header the browser guesses file types from content — which attackers can exploit (e.g. a HTML file disguised as .jpg is executed as HTML). The fix is one single line.
File: .htaccess in the web root
<IfModule mod_headers.c>
Header always set X-Content-Type-Options "nosniff"
</IfModule>⚠ No side effects expected — considered a safe standard and best practice for years.
File: .htaccess in the WordPress root
<IfModule mod_headers.c>
Header always set X-Content-Type-Options "nosniff"
</IfModule>⚠ Safe to add alongside other Header set entries.
File: functions.php of your CHILD theme
add_action('send_headers', function () {
header('X-Content-Type-Options: nosniff');
});⚠ Back up functions.php before edits.
✓ How to verify it works: F12 → Network → Response Header: "x-content-type-options: nosniff".
No clickjacking protection. The website could be embedded in other pages to trick users.
Without clickjacking protection your site can be invisibly embedded into a malicious page ("enter your password here" — the click actually lands on your overlayed login form). Fix: set SAMEORIGIN (only your own domain may embed).
File: .htaccess in the web root
<IfModule mod_headers.c>
Header always set X-Frame-Options "SAMEORIGIN"
Header always set Content-Security-Policy "frame-ancestors 'self'"
</IfModule>⚠ Use both headers: X-Frame-Options for older browsers, frame-ancestors for modern ones. If you already have a CSP, add "frame-ancestors 'self'" there — don’t duplicate.
File: .htaccess in the WordPress root
<IfModule mod_headers.c>
Header always set X-Frame-Options "SAMEORIGIN"
</IfModule>⚠ If your site is intentionally embedded elsewhere (e.g. booking widget on partner sites): instead of SAMEORIGIN, list allowed domains via CSP: Header always set Content-Security-Policy "frame-ancestors 'self' https://partner.example.com"
File: functions.php of your CHILD theme
add_action('send_headers', function () {
header('X-Frame-Options: SAMEORIGIN');
});⚠ WordPress already tries to set X-Frame-Options — this hook deliberately overrides it.
✓ How to verify it works: F12 → Network → Response Header: "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.
1 first-party and 0 third-party cookie(s).
| Name | Domain | Encrypted | Server only | SameSite |
|---|---|---|---|---|
| PHPSESSID | bbsovg.de | Yes | Yes | Lax |
No local storage (Web Storage) used — no tracking risk.
No third-party requests detected — all content comes from the website's own server.
No known trackers detected.
No external scripts or stylesheets loaded.
CAA records present: sectigo.com — only specified certificate authorities may issue certificates.
4 nameservers present — good redundancy.
IPv6 support present (AAAA records).
SPF record present: v=spf1 include:_spf-eu.ionos.com ~all — protects against email spoofing.
DMARC record present: v=DMARC1; p=none; — email authentication active.
security.txt found: https://bbsovg.de/.well-known/security.txt
Contact field present (required) — security researchers can report vulnerabilities.
Expires field present (required).
Preferred languages specified.
No external reporting endpoints detected.
No consent banner needed — no trackers or third-party cookies detected.
Privacy policy linked: "Datenschutzinformationen" (https://bbsovg.de/geschuetzer_lehrkraeftebereich/datenschutz_lehrerportal.html).
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 link is broken: HTTP/1.1 503 Service Temporarily Unavailable.
The privacy policy link returns an error (HTTP HTTP/1.1 503 Service Temporarily Unavailable). Effectively the same as no privacy policy — same legal status as missing.
WordPress plugin: Step 1: check the footer menu (Appearance → Menus → Footer menu → which URL does the "Privacy" item link to?). Step 2: does the target page still exist? Pages → All Pages. Step 3: if the page was renamed: update the menu link. Step 4: if deleted: create a new one. Step 5: on permalink issues, visit Settings → Permalinks → Save (no changes — rewrites .htaccess).
✓ How to verify it works: Privacy link in footer → opens the page with status 200, content visible.
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.
Append this block to the end of your .htaccess in the web root — done.
<IfModule mod_headers.c>
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains"
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 X-Content-Type-Options "nosniff"
Header always set X-Frame-Options "SAMEORIGIN"
Header always set Permissions-Policy "camera=(), microphone=(), geolocation=(), payment=(), usb=(), accelerometer=(), gyroscope=(), magnetometer=(), interest-cohort=(), browsing-topics=()"
</IfModule>
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 Strict-Transport-Security "max-age=31536000; includeSubDomains"
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 X-Content-Type-Options "nosniff"
Header always set X-Frame-Options "SAMEORIGIN"
Header always set Permissions-Policy "camera=(), microphone=(), geolocation=(), payment=(), usb=(), accelerometer=(), gyroscope=(), magnetometer=(), interest-cohort=(), browsing-topics=()"
</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("Strict-Transport-Security: max-age=31536000; includeSubDomains");
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("X-Content-Type-Options: nosniff");
header("X-Frame-Options: SAMEORIGIN");
header("Permissions-Policy: camera=(), microphone=(), geolocation=(), payment=(), usb=(), accelerometer=(), gyroscope=(), magnetometer=(), interest-cohort=(), browsing-topics=()");
});
| Header | Value |
|---|---|
| cache-control | no-store, no-cache, must-revalidate |
| content-encoding | gzip |
| content-type | text/html; charset=UTF-8 |
| date | Tue, 01 Sep 2026 12:17:14 GMT |
| expires | Thu, 19 Nov 1981 08:52:00 GMT |
| pragma | no-cache |
| server | Apache |
| set-cookie | PHPSESSID=5d6068d629d096210b36bc3bb0067afd; path=/; secure; HttpOnly; SameSite=Lax |
| x-ws-origin | available |
| x-ws-ratelimit-limit | 1000 |
| x-ws-ratelimit-remaining | 999 |