Headed-browser audit of every domain in the WholeTech fleet — security headers, TLS, screenshots, console errors. One command, ~2 minutes, A-through-F grade per site.
A single Node script (scan.mjs) drives a real Chromium tab with Playwright through every domain in the GoDaddy CSV export. For each domain it captures:
The output is a self-contained dark-mode HTML report: thumbnail grid, color-coded by grade, sortable mentally at a glance. Use it as a fleet-wide snapshot or as a diff target after config changes.
fetch)npm install — pulls Chromium browser bundle~/Downloads/domainexport_*.csv — first column is the domain namecd /var/www/wholetech.com/playwright
npm install
npx playwright install chromium
node scan.mjs
A Chromium window opens and walks each domain. Watch it go, or minimize and let it run. ~2 minutes for 102 domains.
HEADLESS=1 node scan.mjs
SCAN_CSV=/path/to/your-list.csv node scan.mjs
Two files land in ./out/ on every run, timestamped:
The summary bar at the top shows total scanned, OK count, error count, average header score, and the grade distribution (A / B / C / D / F). Each card shows a thumbnail, HTTP status, TLS protocol, page title, and a row of six pills — green if the header is present, red if missing.
| Header | What it does |
|---|---|
Strict-Transport-Security | Forces browsers to use HTTPS only |
Content-Security-Policy | Whitelist of script/style/image sources — XSS defense |
X-Frame-Options | Blocks the page from being iframed (clickjacking defense) |
X-Content-Type-Options | Stops MIME-type sniffing |
Referrer-Policy | Controls what's leaked to outbound links |
Permissions-Policy | Disables browser features (camera/mic/geolocation/etc.) per page |
Score = % present. A 90%+ C 50%+ F below 30%.
The shared include /etc/nginx/conf.d/performance.conf applies to every server block that doesn't define its own add_header. Add headers there once, every static site benefits.
add_header Content-Security-Policy "default-src 'self' 'unsafe-inline' 'unsafe-eval' https: data: blob:; img-src * data: blob: https:; frame-ancestors 'self'; base-uri 'self'" always;
add_header Permissions-Policy "geolocation=(), microphone=(), camera=(), payment=(), usb=(), magnetometer=(), gyroscope=(), accelerometer=()" always;
Then: nginx -t && systemctl reload nginx
add_header directive, all parent add_header directives are nullified for that block. To keep inheritance, those server blocks need every header re-declared, or the more_set_headers directive (ngx_headers_more module) which doesn't have this rule.If a site is grading F despite the shared include, it means the server block defines its own add_header. Open the site's config:
nano /etc/nginx/sites-available/<domain>
Add the full security-headers block at the top of the server { } block, or remove the conflicting add_header so the inherited ones apply.
Some F-grade results are domains pointed at GoDaddy parking (AWS IPs like 76.223.54.146) instead of the droplet. Headers can't be added remotely — either point DNS at the droplet and create a server block, or accept the grade for parked domains.
page.goto(result.url) to walk a sitemap or an explicit URL listcontext.storageState after a manual login, replay the cookiechrome-launcher on the same page — performance + accessibility scoringnpx playwright install chromiumdig +short <domain>