Self-hosted service
CorentinTh/enclosed avatar
CorentinTh/enclosed

CorentinTh/enclosed: self-hosted end-to-end encrypted notes and files

Minimalistic web app designed for sending private and secure notes.

2,093 stars186 forksTypeScriptApache-2.0

At a glance

What is it?
Enclosed is a minimalistic TypeScript web app for sending encrypted notes and files, with a zero-knowledge server, an optional password, a TTL and read-once destruction. It is easy to run with Docker, but the README leaves several operational questions open.
Who is it for?
Adopt Enclosed if you want a small, self-hostable note service where the server never sees plaintext, and you accept that the link hash is the real secret. Do not adopt it as a password manager or as a long-term document store: the design assumes short-lived, read-once notes.
Can I use it commercially?
Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 1 day ago.
What is it written in?
Mainly TypeScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on September 29, 2026, and from our analysis. They are not legal advice.

Editorial analysis

What Enclosed solves, and who it is for

Pasting a secret into a chat message leaves it in a chat history, in backups and on a third-party server. Enclosed replaces that with a link that carries the decryption key in the URL hash fragment. The README states the goal plainly: the server and storage have zero knowledge of the content. The intended user is someone who needs to hand a password, an API key or a short file to one person, once, and wants that handover to expire. The project is a monorepo written in TypeScript, published under Apache-2.0, with a hosted instance at enclosed.cc. It is not a general note-taking app. There is no folder tree, no search across notes, no editing after creation. The unit of work is a single note with a TTL, an optional password and an optional self-destruct flag. If your requirement is a shared knowledge base, Enclosed is the wrong shape.

How the key never reaches the server

The README describes a two-key scheme. A base key is generated in the browser, which means encryption happens even when the sender sets no password. A master key is then derived from that base key plus the optional password using PBKDF2 with SHA-256. The note body is encrypted with the master key using AES-GCM. Only the ciphertext and some metadata travel to the server: the TTL, whether the note is password-protected, and whether it should be destroyed after reading. The server stores the ciphertext and returns an ID. The share link combines that ID with the base key, and the base key lives in the URL hash fragment. That placement is the design decision that makes the zero-knowledge claim hold, because browsers do not transmit the fragment to the server. On retrieval the recipient's browser fetches the ciphertext by ID, extracts the base key from the fragment, prompts for the password if the metadata says one is required, re-derives the master key and decrypts. The practical consequence: whoever holds the full link holds the base key. A password adds a second factor, but it does not rescue a link that was pasted into the wrong channel.

Running the Docker image and creating your first note

The README gives a one-line Docker command. It publishes the container on port 8787 and names the container enclosed. After it starts, the app is reachable on that port, and the same image is what the project publishes as corentinth/enclosed.

bash
docker run -d --name enclosed --restart unless-stopped -p 8787:8787 corentinth/enclosed

The README warns that this first command has no persistent storage, so notes disappear when the container is replaced. The repository's docker-compose.yml shows the volume the project expects: the data directory inside the image is /app/.data.

yaml
services:
  enclosed:
    image: corentinth/enclosed
    ports:
      - 8787:8787
    volumes:
      - enclosed-data:/app/.data
    restart: unless-stopped

volumes:
  enclosed-data:
    driver: local

For terminal use there is a separate CLI published as @enclosed/cli. The README shows a global install through npm, yarn or pnpm, then a create call with the three options that matter: deleteAfterReading, password and ttl in seconds.

bash
npm install -g @enclosed/cli
enclosed create --deleteAfterReading --password "password" --ttl 3600 "Hello, World!"

The command prints a note URL. The CLI defaults to the public instance at enclosed.cc, and the README shows how to point it elsewhere with enclosed config set instance-url, which is what you want after standing up your own container. Reading a note back is enclosed view with the URL, and the CLI prompts for the password unless you pass it with the password flag.

Where the design gets in your way

The link fragment is both the convenience and the weak point. Anyone who obtains the full URL can decrypt a note that has no password, and the server cannot revoke that, because it never held the key. That is not a bug in the implementation; it is the cost of zero knowledge. Treat the link itself as the credential. The second constraint is that the README does not document rollback, key rotation or a recovery path for a lost link. There is no account-level escrow. The third is scope: files are supported as attachments, but the README does not describe a size limit, so you should determine your own before announcing the service internally. Finally, the TTL is the only cleanup mechanism described. If a recipient never opens a note, destruction-on-read never fires, and the ciphertext sits in /app/.data until the TTL expires. That makes the TTL setting an operational decision, not a cosmetic one.

Enclosed against PrivateBin and a plain password manager

PrivateBin occupies the same niche and is the obvious comparison. Both are self-hostable and both keep the server ignorant of plaintext. The difference is in the key handling. PrivateBin derives its key from the password the sender types, so a note without a password is not meaningfully protected. Enclosed generates a base key on the client regardless, which the README presents as the reason encryption holds even with no password set. That is a real architectural difference, and it changes the sharing ritual: with Enclosed the link is the secret, while with a password-first tool the password is the secret and the link is just a pointer. A password manager is the other alternative, and it solves a different problem. It is built for repeated retrieval of long-lived credentials, not for a one-shot handover that expires. Enclosed is worse at storage and better at ephemerality. If you need an auditable vault, use the vault; if you need a link that dies, Enclosed fits.

Maintenance, licensing and upgrade cost

The repository is not archived, and the last push was on 2026-09-10. The most recent tagged release listed is v1.16.0 from 2025-07-22, with v1.15.0 in 2025-03 and v1.14.0 in 2024-12. That cadence suggests a small, steady project rather than a fast-moving one, and the version in the root package.json matches v1.16.0. Upgrades are container replacements, so the cost is mostly in preserving the volume mounted at /app/.data and in re-checking the self-hosting configuration page after a major bump. The licence is Apache-2.0, which permits commercial and private use and requires that you keep the licence and attribution notices; it also includes a patent grant. That is a summary of the identifier, not legal advice, and if you redistribute a modified image you should read the LICENSE file in the repository root. The codebase is a pnpm workspace with separate packages for the client (SolidJS), the server (HonoJS), shared libraries and the CLI, and the root package.json requires Node 22 or newer, which matters if you build from source instead of pulling the image.

Editorial conclusion

Adopt Enclosed if you want a small, self-hostable note service where the server never sees plaintext, and you accept that the link hash is the real secret. Do not adopt it as a password manager or as a long-term document store: the design assumes short-lived, read-once notes. Verify first that your deployment persists /app/.data, that your reverse proxy forwards the hash fragment untouched (browsers never send it to the server, so this is mostly about not rewriting the redirect), and that you have a TTL policy for notes that are never opened.

Frequently asked questions

How do I install Enclosed on my own server?

The README gives a single Docker command that runs corentinth/enclosed on port 8787. For anything beyond a quick trial it points to the self-hosting documentation for persistent storage, Docker Compose and a rootless image.

Does the Enclosed server ever see the content of a note?

According to the README, encryption happens on the client and the server stores only ciphertext plus metadata such as the TTL and whether the note is password-protected. The base key is placed in the URL hash fragment, which browsers do not send to the server.

How do I create an Enclosed note from the terminal?

Install the CLI globally as @enclosed/cli, then run enclosed create with a message. The README shows options for deleteAfterReading, password and ttl, and enclosed config set instance-url to point the CLI at your own instance instead of enclosed.cc.

Official sources

  1. CorentinTh/enclosed on GitHub
  2. License: Apache-2.0
  3. Project website
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/corentinth-enclosed.svg)](https://hysenlabs.com/projects/corentinth-enclosed)