NoteKillnotes that do not survive

Every claim, where it's enforced, how you can verify it

This page exists to be checked, not believed. Each guarantee below names the place it is enforced and gives you a way to test it.

This service stores self-destructing notes, encrypted files up to 25 MB, and chat rooms that are deleted once nobody has them open. It is built so that we cannot read any of it. This page states each guarantee, where it is enforced, and how you can check it — including the one limit we cannot engineer away. Where a claim cannot be verified from outside, we say so.

The guarantees

Security guarantees: each claim, the mechanism that enforces it, and how a visitor can verify it.
ClaimWhere it is enforcedHow you can verify
AES-256-GCM, authenticated encryption In your browser, via the native Web Crypto API. No third-party crypto library is loaded. Read /assets/app.js — unminified, no build step. Search for crypto.subtle.
The key cannot reach the server The 256-bit key sits in the URL fragment, after #. Browsers never transmit fragments in HTTP requests, so the key cannot reach our logs or any proxy's. DevTools → Network, open any share link: the request carries the 32-hex path only. Nothing after # is sent.
Burn-after-read cannot race Server-side, inside a database transaction using SELECT … FOR UPDATE. Our test: 8 simultaneous readers of a one-view note — exactly one got the note, seven got 404. Create a one-view note and request it from two clients at once. One succeeds.
File bytes are not directly fetchable Ciphertext is stored outside the web root under a random 32-hex name; a server-side script streams it and enforces expiry and the download counter. The download URL is handled by a script, not served as a file. Once spent or expired, the same URL stops serving.
Filenames are encrypted too The original filename and MIME type are encrypted in your browser, separately from the file bytes. Upload with DevTools open: no plaintext filename appears anywhere in the request.
We cannot tell who is talking in a chat Messages are ciphertext rows in the database. Your chosen display name travels inside the ciphertext. Watch the network panel while chatting: the name you typed never appears outside the encrypted payload.
No access logs The web server is configured not to write an access log for this site. No record exists of who read which note. Not verifiable from outside. Stated here and in our privacy policy; its consequences are enumerated under legal orders below.
Rate limiting stores no IP We store a truncated HMAC-SHA256(IP, server secret), deleted when its hour-long window closes, so it lives at most an hour plus the minute until the next sweep. The raw IP is never written to the database. Not verifiable from outside. The residual risk is stated plainly below.
CSP allows nothing by default Content-Security-Policy: default-src 'none', with no unsafe-inline and no unsafe-eval. curl -sI https://notekill.com/ | grep -i content-security
HSTS for two years max-age=63072000; includeSubDomains on every response. The header also carries the preload token, but carrying the token is not the same as being on the list: preloading requires submitting the domain to the browser vendors' list separately, and we have not done that yet. Treat this as HSTS, not as preloading, until it appears at hstspreload.org. curl -sI https://notekill.com/ | grep -i strict-transport
No Referer leakage Referrer-Policy: no-referrer: a site you open from one of our pages never learns the URL you came from. curl -sI https://notekill.com/ | grep -i referrer-policy
Every script is pinned Subresource Integrity on the stylesheet and every script, plus modulepreload integrity for the imported ES modules. View source, then compare the hashes against /integrity.json.
No third party in the TLS path TLS terminates on the server we run. No CDN, no reverse proxy, nothing between your browser and us that could read the connection. Inspect the certificate in your browser: it is issued to this domain, not to a content delivery network.

The honest limit

One thing you must take on trust — or verify.

The encryption code arrives from our server. Whoever controls a server can, in principle, serve altered code to future visitors — code that leaks keys. Subresource Integrity and our Content-Security-Policy do not prevent this, because the same server publishes the hashes. This is true of every in-browser encryption tool ever built. Most of them don't mention it.

Two things limit the damage. First, it is forward-looking only: notes already encrypted and sent stay sealed no matter what code is served later. Second, it is detectable: every script we serve is pinned with Subresource Integrity, the hashes are published at /integrity.json, and you — or anyone — can archive them and check that the code served tomorrow is the code that was served today. Our frontend is hand-written vanilla JavaScript with no build step, so what you audit is what runs.

If a tool in this category tells you it has no such limit, it is describing its marketing, not its architecture.

VERIFY

curl -s https://notekill.com/assets/app.js | sha256sum

Record the hash, then run the same command tomorrow from a different network and compare: serving altered code selectively to one visitor is exactly what that comparison catches.

What a legal order could actually obtain

We answer this precisely, because "we have nothing" is never entirely true. Served with valid legal process, we could produce three things.

  • CIPHERTEXT The stored blob and its metadata: creation time, byte size, expiry, view counter. We cannot decrypt it — the key never reached us.
  • ERROR LOG Access logging is switched off, but the server still keeps an error log. A failed request can leave a line containing an IP address.
  • RATE-LIMITER SECRET We keep truncated HMACs of client IP addresses, deleted when its hour-long window closes, so it lives at most an hour plus the minute until the next sweep. We will not understate this one: with the HMAC secret in hand, the IPv4 address space is small enough to search exhaustively offline, so for the length of that window those hashes could be worked back to the addresses that produced them. What limits it is the window itself: the rows are deleted when their hour closes. What does NOT limit it — and an earlier version of this page implied otherwise, wrongly — is that those rows are stamped with the hour and separated by which endpoint was called, while every note, file and room carries its own creation time in the same database. Anyone holding both the database and the secret could line those up, and on a quiet service that narrows to very few candidates. We would rather correct the sentence than leave a comfortable one standing.

The following does not exist and therefore cannot be produced: access logs (there is no record of who read which note), accounts, email addresses, any means of decryption, and any way to warn an affected user — we do not know who our users are.

We respond to valid legal process only, and we contest requests that are overbroad. We cannot hand over what was never recorded.

There is no warrant canary. Under the confidentiality duties that EU legal orders can carry, a canary that lapses is a legal risk for the operator rather than a reliable signal for you, so we do not publish one.

Infrastructure

A single server inside the European Union. TLS terminates on it directly — no CDN, no reverse proxy, no third party anywhere in the path between your browser and us. Nothing sits in the middle that could read the connection even if it wanted to.

A page load fetches four first-party files and nothing else. No cookie is set for anyone using the service, no analytics, no third-party scripts, no fonts pulled from someone else's domain. The operator's settings page at /admin is the one place a cookie exists, scoped to that path — a visitor's browser never receives it and never sends it. The only browser storage used is a single sessionStorage entry holding the chat display name you typed, and it dies with the tab. Expired notes, files and chat rooms are removed by a scheduled sweep every minute, not only when the next visitor happens to arrive.

We do not publish the hosting provider, the region or the software versions we run. Those are operational details; naming them would help someone attacking this service more than it would help you, and no law requires us to disclose them. What you can hold us to is on this page — the guarantees, where each is enforced, and how to check.

How encryption actually runs, step by step

  1. Key generation. Your browser calls crypto.getRandomValues to produce a 256-bit key. Native Web Crypto; no third-party crypto library.
  2. IV. A fresh random 96-bit IV for every encryption. Tested: 500 encryptions under one key produced 500 distinct IVs.
  3. Encrypt. AES-256-GCM through crypto.subtle. GCM is authenticated encryption: in our tests, flipping a single bit in the body, the tag, or the IV always makes decryption fail. Tampering produces an error, never garbage output.
  4. Pack. IV, ciphertext and authentication tag are concatenated as IV||ciphertext||tag and base64-encoded. For files, the filename and MIME type are encrypted separately from the bytes.
  5. POST. The browser sends the ciphertext and the retention settings the server must enforce — expiry and view limit. Neither the key nor the plaintext is in the request.
  6. Store. The server assigns a 32-hex identifier from random_bytes — 128 bits from a CSPRNG, never a weak PRNG. Encrypted file bytes go to a directory outside the web root.
  7. Link. The share link is https://notekill.com/n/<32 hex chars>#<43 base64 chars>. Everything after # stays in your browser. Decryption reverses these steps locally; what we hold is ciphertext we cannot read.

The code that does all of this is served unminified at /assets/app.js. Shorter answers live in the FAQ; what we retain and why is in the privacy policy.