Self-hosted service
CorentinTh/enclosed avatar
CorentinTh/enclosed

Enclosed: a zero-knowledge note sharer where the key never leaves the URL fragment

Minimalistic web app designed for sending private and secure notes.

2,081 stars182 forksTypeScriptApache-2.0

At a glance

What is it?
Enclosed is an Apache-2.0 TypeScript monorepo for sending end-to-end encrypted notes and files, with a browser client, a HonoJS server, a CLI, and a Docker image. The design is sound and the deployment path is one command, but the persistence and configuration details live in external documentation rather than the repository README.
Who is it for?
Adopt Enclosed if you need a small, self-hostable service for one-off secrets and you are willing to read docs.enclosed.cc before putting it in front of real users: verify the storage backend, the TTL defaults and the password policy first, because the README does not cover them. Skip it if you need shared team vaults, audit trails, or a note store you can search later, since the model is one link, one read, and no server-side knowledge of content.
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 received new commits within the last day.
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 15, 2026, and from our analysis. They are not legal advice.

DEEP OPEN-SOURCE ANALYSIS

The problem Enclosed solves, and the people it is aimed at

Pasting a password or an API key into a chat message leaves a copy in a channel log, a notification email and a search index. Enclosed exists to replace that with a single-use link whose contents the server cannot read. The README frames the project as a minimalistic web application for sending private and secure notes, with client-side encryption and a server that has zero knowledge of the content. The intended user is narrow: a developer, an ops person or a small team that needs to hand a secret to one recipient once, and is willing to run or trust a separate service to do it. It is not a wiki, not a shared notebook, and not a password manager. The repository topics (end-to-end-encryption, pastebin, secure, self-hosted, minimalist) describe the same scope. If your requirement is a durable document store with access control per user, this is the wrong shape of tool, and the README does not pretend otherwise.

The key flow: base key in the fragment, master key from PBKDF2

The README lays out a thirteen-step flow, and the important part is where the keys live. On creation, the client generates a base key so that 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, and the note itself is encrypted with AES-GCM under the master key. Only the ciphertext and a small set of metadata fields (TTL, whether the note is password-protected, whether it should self-destruct after reading) go to the server. The server stores the blob and returns an ID. The shareable link carries that ID plus the base key, and the README is explicit that the base key goes into the URL hash fragment because fragments are not sent to the server. Retrieval runs the same steps in reverse: fetch by ID, read the base key out of the fragment, prompt for the password if the metadata says one is needed, re-derive the master key, decrypt. Two consequences follow directly. First, anyone holding the full link can decrypt a note that has no password, so the link is the secret. Second, a password adds a second factor to the derivation rather than replacing the base key, which is why the README can say encryption happens even with no password set. The README does not state the PBKDF2 iteration count, so treat that as something to confirm in the source or the docs before you rely on the password layer for high-value material.

Monorepo layout: five packages and what actually runs where

The project is a pnpm workspace monorepo, and the README lists five packages. packages/app-client is the frontend, built with SolidJS. packages/app-server is the backend, using HonoJS. packages/lib holds the core functionality, which is the part that presumably contains the encryption helpers shared by the client and the CLI. packages/cli is the command-line interface. packages/deploy-cloudflare contains Cloudflare Pages build scripts and configuration, which tells you the maintainer runs a hosted build on Cloudflare in addition to the Docker path. That split matters when you self-host: the Docker image is the server plus the built client, while the Cloudflare package is an alternative deployment target rather than a component you need. The presence of a shared lib package is also why the CLI can create and view notes against the same instance the web app uses. If you plan to audit the cryptography, packages/lib is where to start reading, not the client.

Getting an instance running: Docker, compose, and the CLI

The README gives one command for a quick run: docker run -d --name enclosed --restart unless-stopped -p 8787:8787 corentinth/enclosed. That maps container port 8787 to the host, so the app is reachable on localhost:8787. For anything beyond a test, the README points to the self-hosting documentation for persistent storage, rootless and non-rootless images, and Docker Compose, and to a separate configuration page for environment settings. Those two links are where the actual deployment decisions live; the README itself does not list storage backends or environment variable names, so I cannot state them here. On the client side, the CLI installs globally with npm install -g @enclosed/cli, yarn global add @enclosed/cli, or pnpm add -g @enclosed/cli. Creating a note is enclosed create "Hello, World!", and it also accepts stdin, as in cat file.txt | enclosed create. The full-option form in the README is enclosed create --deleteAfterReading --password "password" --ttl 3600 "Hello, World!", which names three flags: deleteAfterReading, password, and ttl in seconds. Reading is enclosed view <note-url>, with an optional --password flag. The CLI defaults to the public instance at enclosed.cc, and you repoint it with enclosed config set instance-url https://enclosed.cc, substituting your own URL. Note that this last command is the documented way to change the target; nothing in the README suggests an environment variable for the same purpose.

Where the model breaks down

The fragment-based key delivery is the project's main strength and its main failure mode. If the recipient's chat client, mail gateway or link preview bot rewrites or truncates the URL, the base key is lost with it, and the note becomes unreadable even though the ciphertext is still sitting on the server. Self-destruct-after-reading adds a second sharp edge: a link preview fetch or a corporate mail scanner that follows URLs can consume the note before the human clicks it. The README describes the metadata flag but says nothing about how, or whether, the server distinguishes a real read from a prefetch, so I cannot claim it does. Password-protected notes reduce the blast radius if a link leaks, but the README does not document the PBKDF2 parameters or any rate limiting on retrieval attempts, so brute force resistance is unverified from the material available. Operationally, the README also does not describe what happens to expired notes, whether deletion is immediate or swept later, or what the storage layer is. For a tool whose whole promise is that the server knows nothing, those are the questions an operator needs answered before trusting it with real secrets. Finally, this is a single-maintainer project with releases spaced roughly three to nine months apart (v1.14.0 in December 2024, v1.15.0 in March 2025, v1.16.0 in July 2025), which is a reasonable cadence for a small utility but not a security-critical dependency you can assume is patched within hours.

How it differs from PrivateBin and similar pastebins

The obvious comparison is PrivateBin, the long-running PHP pastebin with client-side encryption. The difference is in where the key is derived and what the server is asked to do. PrivateBin's model is a paste with optional discussion and syntax highlighting, and its encryption is applied to the paste content before upload; the server is a generic paste store with a retention policy. Enclosed drops the pastebin features (no comments, no syntax highlighting, no revision history in the README's feature list) and narrows the product to a note with a password, a TTL and an optional one-time read, plus file attachments. The key derivation is spelled out in the README as PBKDF2-SHA-256 feeding AES-GCM with a 256-bit key, and the base key travels in the URL fragment rather than being derived from a passphrase alone. The practical difference for an operator is deployment surface: PrivateBin is a PHP application you drop behind a web server, while Enclosed ships as a Docker image and a pnpm monorepo with a Cloudflare deployment package. If your infrastructure is Node and containers, Enclosed fits; if it is a shared LAMP host, PrivateBin will be less work. Neither one gives you a searchable archive, and that is by design in both cases.

Maintenance, licence, and what to check before you commit

The licence is Apache-2.0, which permits commercial use, modification and redistribution provided you keep the notices and state changes; it also includes a patent grant. That is the permissive end of the spectrum and imposes no copyleft obligation on your own code, but it is not legal advice and the NOTICE and attribution requirements are worth reading in full if you plan to redistribute a modified build. Maintenance cost is dominated by the deployment, not the code: the README's quick-start command uses no volume, so notes vanish when the container is replaced, and the persistent-storage and Compose guides are separate documents you have to follow. Upgrades between releases are not described in the README, so the safe assumption is that you re-pull the image and check the docs for migration notes. The features list also mentions optional email/password authentication for creating notes, which implies a configuration surface the README does not enumerate. Before putting this in front of users, confirm four things in docs.enclosed.cc: the storage backend and its persistence guarantees, the environment variables that control authentication, the PBKDF2 iteration count used for the master key, and the exact deletion semantics for expired and read-once notes.

Editorial conclusion

Adopt Enclosed if you need a small, self-hostable service for one-off secrets and you are willing to read docs.enclosed.cc before putting it in front of real users: verify the storage backend, the TTL defaults and the password policy first, because the README does not cover them. Skip it if you need shared team vaults, audit trails, or a note store you can search later, since the model is one link, one read, and no server-side knowledge of content. The single command that gets you a running instance is docker run -d --name enclosed --restart unless-stopped -p 8787:8787 corentinth/enclosed, and everything else you need to decide on sits behind the configuration page of the docs site.

Official sources

  1. CorentinTh/enclosed on GitHub
  2. License: Apache-2.0
  3. Project website
  4. README
  5. Releases
Community notes

Community notes