// security & threat model
Security explained, step by step.
h4|Bin follows Kerckhoffs's principle: security rests on keys generated in your browser, not on hiding the cryptographic design. This page documents the complete data flow, wire format, protections, verification steps, and limitations without publishing the private source code.
Architecture in one paragraph
There is no backend. The site is static files; the application runs entirely in your tab.
A paste is deflate-compressed, encrypted, and encoded into the URL fragment (after
#). Fragments are consumed by the browser and
never included in HTTP requests (RFC 9110 §4.2.5); the site additionally
sends Referrer-Policy: no-referrer so links never leak through referrers.
Opening a link runs the same machinery in reverse, locally.
Cryptographic specification (format v2 — current)
link = https://<host>/#v2.<blob>
blob = base64url( urlKey ‖ payload ) # urlKey: 32 CSPRNG bytes at fixed offset
payload = header ‖ ciphertext
header (2 bytes, bound as AES-GCM AAD):
[0] version = 0x02
[1] flags - bit0: password required
key = password ? PBKDF2-HMAC-SHA-256( password, salt = urlKey, 600 000 )
: urlKey # full-entropy 256-bit key, used raw
iv = 12 zero bytes # safe: every key seals exactly one message
cipher = AES-256-GCM( key, iv, deflate-raw(document), AAD = header ), 128-bit tag
document (binary):
docflags(1) ‖ created(u32) ‖ [expires u32] ‖ [len,lang] ‖ [len,title] ‖ content
v2 stores no salt and no nonce. The salt's job — a unique, public, per-paste value that defeats cross-target precomputation — is already done perfectly by the 32-byte urlKey, which travels in the link exactly like a stored salt would. The nonce can be a fixed zero because AES-GCM only requires uniqueness per key, and every paste mints a fresh single-use key. Without a password there is no KDF at all: a KDF stretches low-entropy secrets, and a 256-bit CSPRNG key has nothing to stretch — so password-less pastes also open instantly. Together with a compact binary document replacing the v1 JSON wrapper, links are roughly 80–100 characters shorter.
Format v1 (legacy — still opened, never created)
Older links (#v1.<payload>.<key>) carried a 30-byte header with an
explicit 16-byte PBKDF2 salt and 12-byte random nonce, derived every key as
PBKDF2(urlKey ‖ password, salt, 600k), and wrapped the document in JSON. Every v1 link
keeps decrypting forever; cross-implementation known-answer vectors for both formats are
pinned in the test suite so neither can drift.
All primitives are the browser's native WebCrypto and Compression Streams - the cryptographic path imports zero third-party code.
Why this holds up
-
256-bit keyspace. The link key comes from
crypto.getRandomValues. Knowing the documented design gives an attacker nothing better than brute-forcing 2256 keys. - One key, one message. Every paste generates a fresh 256-bit key that seals exactly one payload, so no AES-GCM (key, nonce) pair ever repeats - the classic nonce-reuse failure mode is eliminated by construction, not by discipline. That is also what makes v2's fixed zero nonce sound.
- Passwords are a real second factor. With a password set, the key is PBKDF2-SHA-256(password, salt = urlKey, 600,000 iterations). Someone who intercepts the link must grind the full KDF per guess, offline, against a salt unique to that one paste - no rate-limiting server to blame, no oracle to query, no precomputation to share.
- Tamper- and downgrade-proof. The header (version, flags, salt, nonce) is authenticated as AAD. Flip any bit - say, the "password required" flag - and decryption fails loudly. This is covered by an exhaustive bit-flip test matrix in CI.
- Cross-implementation verified. The test suite pins known-answer vectors produced by an independent implementation of the format (Node's OpenSSL-backed crypto), so the wire format cannot drift silently.
-
Defense in depth at the delivery layer. A strict Content-Security-Policy
(
connect-src 'none', no inline scripts, self-only sources) means even a hypothetical injected script has no way to exfiltrate; plus HSTS,frame-ancestors 'none',nosniff, and no-referrer.
Threat model - honestly
| Adversary | Outcome |
|---|---|
| Hosting provider / network observer | Sees static-asset requests only. Never sees fragments, so never sees pastes or keys. |
| Someone without the link | Nothing exists to attack - there is no listing, no database, no enumeration surface. |
| Someone with the link, paste password-sealed | Offline PBKDF2 grind at 600k iterations per guess. Strong passwords are effectively out of reach. |
| Ciphertext tampering / truncation | AES-GCM authentication fails; h4|Bin shows an integrity error and renders nothing. |
| Someone you shared the full link with (no password) | Can read the paste - that is the point of sharing. Choose recipients accordingly. |
| Link leakage: browser history, messenger logs, screenshots, shoulder surfing | Outside any pastebin's control. The link is the secret - handle it like one. |
| Compromised endpoint (malware, hostile extension) | No web cryptography survives a compromised device. h4|Bin is not an endpoint-security tool. |
| A malicious deployment serving altered code | Mitigated, not eliminated: strict delivery headers reduce injection paths, but a compromised deployment could still serve altered application files. Users can inspect the delivered assets and Network panel, but web applications cannot fully solve code-delivery trust. |
About expiry - advisory by design
An expiry date is sealed inside the encrypted payload. Conforming viewers (including this one) check it after decryption and refuse to display an expired paste. Because the ciphertext is authenticated, the date cannot be stripped or altered. But be clear about what this is: data inside a link cannot be remotely destroyed, and a determined link-holder could modify their own client to ignore the check. Expiry is a strong hint honored by honest software - not deletion. Services that promise "burn after reading" without holding your data server-side are promising something they cannot deliver; h4|Bin refuses to make that promise.
Verify everything yourself
- Open browser DevTools, select the Network panel, and clear the existing request list.
- Type a distinctive sample paste and mint its encrypted link.
- Confirm that minting creates no network request and that the sample text never appears in a request.
- Inspect the address: the encrypted payload and key exist only after
#, which browsers exclude from HTTP requests. - Open the link in a private window and confirm that decryption happens locally without a data request.
- Change one character in the encrypted payload and confirm that AES-GCM authentication rejects the altered link.
- Create a password-protected paste, then confirm that a missing or incorrect password cannot decrypt it.
- Check delivery headers with
curl -sI https://bin.h4rithd.comand look for CSP, HSTS, no-referrer, and nosniff.
Reporting a vulnerability
Found something? Contact the maintainer privately through h4rithd.com and do not publish details that could put users' pastes at risk. Reports are taken seriously and credited with permission.
format: v2 (v1 readable) · kdf: pbkdf2-sha256/600k · cipher: aes-256-gcm · last updated: 2026-07-10