sitekits.dev
security

Security tools

The everyday security chores of web engineering — hashing, password entropy, JWT claims, CSP authoring and redacting captures before sharing them — and which tool fits each job.

11 tools

§01 FIELD GUIDE

The chore layer of web security

Most security work in a normal engineering week is not adversarial. It is mechanical: pick a digest algorithm, generate a credential that satisfies somebody’s policy, read a token’s claims to see why auth failed, write a Content-Security-Policy that does not break the site, scrub a capture before pasting it into a ticket. None of it looks like a pentest, and all of it is where real incidents start — the failure mode is a bad habit, not a missing skill.

The model that prevents most of those habits is a three-way split people collapse into one. Encoding — Base64, percent-encoding, Punycode — is reversible and involves no secret; it changes the shape of data, never its confidentiality. HashingSHA-256, SHA-512 — is one-way and also involves no secret; it proves two byte strings are identical, nothing more. Keyed operations — HMAC, JWT signatures, TLS — are the only ones that prove who produced something, and only while the key stays secret. No key, no authentication: a Base64 blob, a bare digest and a decoded JWT payload are readable claims with nothing behind them.

The second axis is direction. Chores that emit policy — CSP directives, headers, generated secrets — are only correct once you verify the artifact in the deployed response. Chores that inspect something handed to you carry the opposite risk: what is inside the artifact. Redaction belongs here because the usual way secrets leave a company is a HAR attached to a bug report, not an exploit.

What each primitive actually guarantees

PrimitiveProvesDoes not provideCommon misuse
Base64 / URL-encodingSafe transport of bytes through text channelsConfidentiality — anyone can reverse it”Hiding” an API key in a payload
SHA-256 / SHA-512Integrity: same input, same digestSecrecy; brute-force cost on low-entropy inputStoring password hashes
SHA-1Legacy checksum compatibilityCollision resistance (practically broken)Signatures; dedupe of hostile input
HMAC (hash + secret key)Authenticity while the key stays secretConfidentiality — payload still readableShipping the key to a browser
Argon2id / bcrypt / scryptSalted, deliberately slow password storageSpeed — slowness is the featureSwapping in a fast hash “for perf”
JWT signature (HS256, RS256)Issuer authenticity — only after verificationAnything, if you merely decodeTrusting a decoded payload’s claims
Content-Security-PolicyBrowser-enforced allowlist for resource loadsA fix for the injection; non-browser clients'unsafe-inline' left in script-src
TLS + Strict-Transport-SecurityTransport confidentiality; no downgrade to httpAny statement about application logicReading a padlock as “app is secure”

How much entropy is enough

Entropy is length × log2(pool size), so the pool matters as much as the length.

Character poolBits per char8 chars14 chars20 chars
a–z (26)4.70386694
a–z0–9 (36)5.174172103
a–zA–Z0–9 (62)5.954883119
all four sets (87)6.445290129

Under 40 bits is broken; 80 is the floor for an account that matters; a random UUID v4 carries 122 bits.

Picking the right tool

For fingerprints, Hash Generator computes SHA-1, SHA-256, SHA-384 and SHA-512 at once via Web Crypto, so a digest matches echo -n "text" | shasum -a 256 byte for byte — the fast way to reconcile a checksum whose algorithm nobody recorded. MD5 is absent because Web Crypto does not implement it.

For secrets you create, the question is who holds the value. Password Generator fits when a human or password manager does and a site imposes a length or charset policy; it reports live entropy for the pool you enabled. UUID v4 Generator fits when the consumer is a machine and you want an opaque identifier with no charset negotiation. Both are CSPRNG-backed — the password generator draws from crypto.getRandomValues, the UUID generator from crypto.randomUUID() — so neither falls back to Math.random.

For tokens, JWT Token Decoder splits header.payload.signature, pretty-prints the first two parts, and renders iat, exp and nbf as ISO 8601 with an expiry verdict — enough to settle “is this stale, or are the claims wrong?” in one paste. Use Base64 Encode / Decode when all you salvaged from a log is a single segment; Base64url uses - and _, so swap them for + and / first.

For policy, keep authoring and verification separate. Content Security Policy Generator assembles the header from 14 per-directive fields over a hardened baseline — default-src 'self', object-src 'none', base-uri 'self', frame-ancestors 'none' — and the CSP hub goes directive by directive. Then prove the deployed response carries it with HTTP Headers Checker, which fetches server-side and shows raw headers as the origin sent them. CDNs rewrite headers, and a <meta> tag cannot carry frame-ancestors.

Before sharing, HAR File Viewer reads your own capture — method, status, MIME type, size, timing per entry — while HAR File Sanitizer prepares the copy that leaves your machine, replacing cookie, authorization, x-api-key and similar headers with [REDACTED], blanking bodies, masking token-like query parameters. When the suspect is one URL, URL Parser & Analyzer decodes the query string to show whether a signed token sits in it, and IDN / Punycode Converter reveals whether a look-alike domain is really Cyrillic in xn-- form.

All of this runs locally except the headers checker, which sends your URL to api.sitekits.dev. /for/security/ collects most of these on one page — the general-purpose ones (UUID, Base64, the HAR viewer) sit in other role sets; the privacy hub covers what your browser leaks.

Gotchas that cause real incidents

A decoded token is not a verified token

Decoding proves only well-formed Base64url. Verification needs the issuer’s key, a pinned algorithm, and server-side exp / nbf / iss / aud checks. Never take the algorithm from the token’s own alg header — that is how alg: none and RS256-to-HS256 confusion work.

A fast hash is not password storage

A bare SHA-256 of a password is a GPU benchmark, not a defense; unsalted, one rainbow table cracks every user. Hashing to compare files is the opposite case — there a fast digest is correct.

'unsafe-inline' cancels most of CSP

It re-permits exactly the injected inline script CSP exists to block. Use nonces or hashes. A wildcard connect-src is the same trap from the other side: exfiltration stays open even when script execution is locked down.

Redaction is pattern matching, not comprehension

The sanitizer matches a fixed header list and parameter names containing token, key, secret, password, passwd, pwd, auth, session, sig or signature. A credential in a URL path segment, or named t, survives — and a redaction count of zero on an authenticated session is a signal, not a pass.

Entropy describes the generator, not the string

The formula holds only when every character was drawn randomly. P@ssw0rd!2024 has 13 characters and all four classes yet sits in every cracking wordlist. Only how a string was generated decides whether a meter’s score means anything.

FAQ
Is SHA-256 good enough for storing passwords?
No. SHA-256 is fast by design, and commodity GPUs compute billions of digests per second, so an attacker holding a leaked hash table cracks short or common passwords in hours. Use a salted, deliberately slow KDF — Argon2id, bcrypt or scrypt — with a tuned work factor. SHA-256 is the right choice for file integrity, content addressing and change detection, not for credentials.
Can I trust a JWT once I have decoded it?
No. A JWT's header and payload are Base64url-encoded, not encrypted or protected, so anyone can read them and anyone can mint a token with arbitrary claims. Only verifying the signature against the issuer's key proves who issued it. Decoding is for debugging — enforce exp, nbf and audience server-side, because a client clock can be wrong or deliberately lying.
How many characters does a password actually need?
It depends on the character pool, because entropy is length × log2(pool size). With uppercase, lowercase, digits and symbols — an 87-character pool — each character is worth about 6.44 bits, so 13 characters is roughly 84 bits and 20 characters about 129. Lowercase alone gives 4.70 bits per character, so 8 lowercase characters is about 38 bits and brute-forceable. Aim for 80 bits or more on accounts that matter.
Will a Content-Security-Policy stop XSS?
It contains XSS rather than fixing it. A strict policy stops injected script from executing or exfiltrating data, but only in browsers that enforce CSP, and only if script-src avoids 'unsafe-inline' and 'unsafe-eval'. Fix the injection with proper output encoding and use CSP to limit the damage of the bug you missed. Roll out with Content-Security-Policy-Report-Only first, then confirm the enforcing header is present in the real response.
Can I hide an API key by Base64-encoding it in a request payload?
No. Base64 is an encoding, not a cipher: it uses no key, and anyone holding the payload recovers the original bytes with one command, which is why a decoder needs nothing from you to read it. The deeper problem is location rather than format — any secret shipped to a browser or a mobile binary is already disclosed, whatever it is wrapped in. Keep the long-lived key server-side and give the client something that proves authenticity without being the secret: a short-lived token, or a request signed with HMAC where the key never leaves your infrastructure.