Every few months I'd find myself doing the same thing. A CSP review at work, a JWT showing up wrong in production, a security audit asking why HSTS wasn't preloaded. I'd open a tab, search for an online tool, paste my data into a SaaS site I didn't know, and hope it wouldn't get logged somewhere.
After enough of this, I started writing my own. The result is HeaderLab: four web security tools bundled into one open-source site, all running in your browser. This post is about the architectural choices I made and what I learned along the way.
Live: headerlab.dev
Code: github.com/kadirbg/headerlab
License: MIT
What's in the toolkit
Four tools, each solving a problem I kept hitting in real work:
HTTP Security Headers Checker. Scans any URL, evaluates six headers against current best practices, returns severity-ranked findings with copy-paste fix snippets for Nginx, Apache, and Cloudflare.
CSP Builder. A visual editor for constructing strict Content Security Policies, with inline warnings for common misconfigurations.
CSP Evaluator. Paste any existing CSP, get 15+ weakness checks with plain-language explanations. Useful when inheriting a policy you didn't write.
JWT Decoder + Verifier. Decodes JWTs and actually verifies signatures using the browser's Web Crypto API. Supports HS/RS/ES 256/384/512.
The privacy promise is one sentence: nothing you paste leaves your browser. That sounds like marketing copy, so let me explain how it's actually implemented.
The privacy architecture, in detail
Three of the four tools run entirely client-side:
- CSP Builder. Pure DOM manipulation. State held in memory. No fetch, no XHR, nothing.
- CSP Evaluator. Same model. The CSP you paste is parsed and analyzed by JavaScript running in your tab.
-
JWT Decoder + Verifier. Token, payload, signature, and any verification keys stay in the browser. Cryptographic operations happen via
window.crypto.subtle.
The headers checker is the exception. Browsers can't fetch arbitrary cross-origin URLs because of CORS, so a Cloudflare Worker proxies the request server-side. The Worker receives the URL, fetches it, returns the response headers to the browser, and stores nothing.
You can verify this yourself: open DevTools, switch to the Network tab, then decode a JWT or evaluate a CSP. You'll see no requests fire. This isn't a claim, it's something you can check in 30 seconds.
Why no framework
The site is built with Astro 5 + TypeScript, no client-side framework. I went back and forth on this for a while. The arguments against a framework here:
- The site is fundamentally a static document with interactive islands. Astro is built exactly for this.
- A full SPA would mean shipping a runtime to do work that vanilla TypeScript handles in 50 lines.
- For security tools, fewer dependencies means a smaller supply-chain surface. Every package is a potential audit headache.
- I wanted the code to be readable by someone who lands on the repo without a build setup. Astro gives you static HTML with progressive enhancement.
The interactive bits (CSP Builder's drag-and-drop directives, JWT Decoder's verification flow) are scoped vanilla TypeScript in each tool's page. No global state, no shared bundle.
Page-level state lives in useState-equivalent vanilla patterns:
// CSP Builder, simplified
let directives: Map<string, Set<string>> = new Map();
function addSource(directive: string, source: string) {
if (!directives.has(directive)) {
directives.set(directive, new Set());
}
directives.get(directive)!.add(source);
renderPolicy();
}
function renderPolicy() {
const policy = Array.from(directives.entries())
.map(([d, sources]) => `${d} ${Array.from(sources).join(' ')}`)
.join('; ');
document.querySelector('#policy-output')!.textContent = policy;
}
Not glamorous, but you can read it. The whole CSP Builder is around 600 lines.
The JWT verifier is ~220 lines of TypeScript
This was the piece I'm proudest of. Most online JWT tools just base64-decode the header and payload. Some claim to verify signatures but use JavaScript crypto libraries that ship hundreds of kilobytes of code to your browser.
I used the browser's Web Crypto API directly. Zero dependencies. The full decoder + verifier is approximately 220 lines.
Here's the core verification function:
async function verifyJWT(token: string, key: CryptoKey): Promise<boolean> {
const [headerB64, payloadB64, signatureB64] = token.split('.');
if (!headerB64 || !payloadB64 || !signatureB64) {
throw new Error('Malformed JWT');
}
const data = new TextEncoder().encode(`${headerB64}.${payloadB64}`);
const signature = base64UrlDecode(signatureB64);
const header = JSON.parse(atob(headerB64));
const algParams = getAlgorithmParams(header.alg);
return await window.crypto.subtle.verify(
algParams,
key,
signature,
data
);
}
The getAlgorithmParams function maps JWT algorithm names (HS256, RS384, ES512, and so on) to Web Crypto API parameters. The full mapping covers nine algorithms:
function getAlgorithmParams(alg: string) {
const map: Record<string, AlgorithmIdentifier> = {
'HS256': { name: 'HMAC', hash: 'SHA-256' },
'HS384': { name: 'HMAC', hash: 'SHA-384' },
'HS512': { name: 'HMAC', hash: 'SHA-512' },
'RS256': { name: 'RSASSA-PKCS1-v1_5', hash: 'SHA-256' },
'RS384': { name: 'RSASSA-PKCS1-v1_5', hash: 'SHA-384' },
'RS512': { name: 'RSASSA-PKCS1-v1_5', hash: 'SHA-512' },
'ES256': { name: 'ECDSA', hash: 'SHA-256' },
'ES384': { name: 'ECDSA', hash: 'SHA-384' },
'ES512': { name: 'ECDSA', hash: 'SHA-512' },
};
if (!map[alg]) throw new Error(`Unsupported algorithm: ${alg}`);
return map[alg];
}
What's missing: PS256/PS384/PS512 (RSA-PSS) and EdDSA. Web Crypto's PSS support is awkward to use correctly, and EdDSA arrived in browsers only recently. Both are on the roadmap. If anyone has battle-tested patterns for these, I'd appreciate a pointer.
The other thing the tool does, which I think matters for production use: it warns on dangerous algorithm configurations. alg: none is flagged as critical. RS256-then-HS256 confusion attacks (where a server uses an RSA public key as an HMAC secret) are detected when the user provides verification context.
The headers checker grades 6 headers and writes fix snippets
Six headers, evaluated against current best practices: HSTS, CSP, X-Frame-Options/frame-ancestors, X-Content-Type-Options, Referrer-Policy, Permissions-Policy.
The interesting design decision wasn't the scoring (that's documented in /methodology on the site), it was the fix snippets. For each non-passing finding, the tool generates copy-paste configuration for three servers:
# Nginx
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
add_header X-Frame-Options "DENY" always;
add_header X-Content-Type-Options "nosniff" always;
# Apache
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
Header always set X-Frame-Options "DENY"
Header always set X-Content-Type-Options "nosniff"
// Cloudflare Workers (or _headers file)
response.headers.set('Strict-Transport-Security', 'max-age=31536000; includeSubDomains; preload');
response.headers.set('X-Frame-Options', 'DENY');
response.headers.set('X-Content-Type-Options', 'nosniff');
This came from real experience. Every header scanner I'd used told me what was missing. None of them told me exactly what line to add and where. The gap between "you're missing HSTS" and "here's the Nginx directive, paste it inside your server block, reload" is the gap between a tool that diagnoses and a tool that helps.
There's also one piece of behavior I'm specifically proud of: the headers checker refuses to scan headerlab.dev itself. It returns an explanation and points to an external scanner.
if (isHeaderLabDomain(url)) {
return {
error: 'self-scan-blocked',
message: "We don't grade our own homework. Try Mozilla Observatory or securityheaders.com.",
externalLinks: [
'https://observatory.mozilla.org/',
'https://securityheaders.com/'
]
};
}
The grading should come from someone who isn't me.
The infrastructure is boring on purpose
The whole thing runs on Cloudflare Workers' free tier. Static assets served from Cloudflare's edge, one Worker endpoint for the headers checker proxy, Cloudflare Web Analytics for cookieless visit counting.
Why boring: the cost model has implications. When hosting is effectively free at the scale I expect, there's no operational pressure to monetize aggressively. No ads at launch. No signup wall. No rate limits on the core tools (besides a per-IP cap on the headers proxy to prevent abuse, currently 20 requests per minute).
If I had to pay for hosting that scaled with traffic, the temptation to add "premium features" or run ads would grow over time. Building on a cost model that stays near zero is part of how I plan to keep the project's principles intact.
What's on the roadmap
The honest list of what's missing right now:
- PS256/PS384/PS512 (RSA-PSS) in the JWT verifier
- EdDSA (Ed25519, Ed448) in the JWT verifier
- JWE (encrypted JWT) decoding
- Cookie security analyzer (Secure, HttpOnly, SameSite flags, third-party leakage)
- IP/WHOIS tool with geolocation and ASN lookup
- Public API for the Headers Checker, properly rate-limited and documented
- Brotli header inspection for sites using compressed responses
- i18n (Turkish first, since KVKK/GDPR is a real developer pain point in TR)
All of these are tracked in GitHub issues. Contributions welcome.
A note on what this isn't
HeaderLab analyzes HTTP response headers, CSP configurations, and JWTs. That's it. It doesn't analyze TLS configuration, certificate chains, DNS records, application-layer vulnerabilities, or cookie flags (yet). For those, Qualys SSL Labs, dnschecker.org, and your favorite DAST scanner are the right tools.
An A+ in HeaderLab means your headers are in good shape. It doesn't mean your site is secure. I'd rather be clear about that than imply the grade is the whole picture.
Try it, break it, file issues
If this is useful to you, give it a try at headerlab.dev. If you find an edge case in the CSP evaluator or a JWT that breaks the decoder, file an issue or send a PR. The whole project is MIT-licensed, and the code is meant to be read and contributed to.
I'd especially appreciate feedback on:
- CSP evaluator edge cases. If you have a real-world CSP that's broken in a way the evaluator misses, I want to see it.
- JWT parsing edge cases. Particularly malformed tokens, unusual header combinations, or algorithm-confusion patterns.
- Self-hosting setups. If you've deployed this on something other than Cloudflare Workers and hit issues, the docs need expanding.
Thanks for reading. This is v1. The next few months will be about responding to feedback, fixing rough edges, and adding the cookie analyzer.
Top comments (0)