How lwid works
An encrypted, zero-knowledge platform for sharing small web apps.
Security model
End-to-end encryption
Every file is encrypted with AES-256-GCM in your browser before it leaves your machine. The server only ever receives and stores opaque ciphertext blobs.
Zero-knowledge server
The server never sees your content, your keys, or your filenames. It stores content-addressed blobs identified by their IPFS CIDv1 hash — nothing more.
Keys live in the URL fragment
The decryption key is encoded in the #fragment part of
the URL. Browsers never send the fragment to the server
— it stays entirely client-side.
Architecture
You (browser / CLI) Server
┌──────────────────────┐ ┌──────────────────┐
│ │ │ │
│ 1. Generate keys │ │ Stores only: │
│ - AES-256-GCM │ encrypted │ - CID → blob │
│ - Ed25519 seed │──── blobs ────▶│ - project → CID │
│ │ │ │
│ 2. Encrypt files │ │ Never sees: │
│ 3. Compute CID │ │ - plaintext │
│ 4. Upload blobs │ │ - keys │
│ 5. Build manifest │ │ - filenames │
│ 6. Sign & upload │ │ │
│ │ └──────────────────┘
└──────────────────────┘
URL: /p/{project-id-OA4cCJ3}#{read-key}:{write-key}
└─── sent to server ──┘└───── never sent ────┘
Encryption & keys
When you create a project, two keys are generated client-side:
- Read key (32 bytes) — AES-256-GCM symmetric key used to encrypt and decrypt all file content.
- Write key (32 bytes) — Ed25519 seed used to derive a signing keypair. The server stores only the public key and verifies signatures on manifest updates.
Both keys are encoded as base64url (no padding) and
placed in the URL fragment: #readkey:writekey. A view-only
URL omits the write key: #readkey.
Content-addressed storage
Each encrypted blob is hashed with SHA2-256 and
stored under its IPFS CIDv1 (multicodec
raw, multibase base32lower). This means:
- Identical content is stored once (deduplication).
- Integrity is verifiable by recomputing the hash.
- The server cannot tamper with content without detection.
Manifests & versioning
A manifest is a JSON document listing all files in a version: path, CID, and size for each entry. The manifest itself is encrypted, uploaded as a blob, and its CID becomes the project's root.
Each manifest points to its predecessor via a parent
CID, forming a linked version chain. Updating the root requires
a valid Ed25519 signature from the write key.
Service Worker sandbox
In the browser, files are decrypted in the main thread and passed to
a Service Worker via postMessage.
The SW intercepts fetch requests under /sandbox/
and serves the decrypted files from memory, so the project renders
in a sandboxed <iframe> without any plaintext
hitting the network.
Usage
Web UI
- Go to lookwhatidid.xyz.
- Drop your project folder (must contain an
index.html). - Click Create & Share.
- You'll be redirected to your project's URL with both keys in the fragment.
- Share the view-only link (read key only) or the edit link (both keys).
CLI
Install the lwid CLI:
curl -fsSL https://raw.githubusercontent.com/Marlinski/lwid/main/install.sh | sh
First push (new project)
cd my-project/
lwid push --server https://lookwhatidid.xyz --dir .
This generates keys, creates the project, encrypts and uploads all files,
and prints the shareable URL. A .lwid.json config file is
saved in the directory.
.lwid.json to your
.gitignore immediately — it contains your encryption
and signing keys.
Update an existing project
lwid push
Reads config from .lwid.json in the current directory.
Pull a project
lwid pull --dir .
Project info
lwid info
Prints project ID, server URL, edit URL, and view-only URL.
AI agent integration
Any coding agent that supports skill files can publish to lwid automatically. Point your agent at the skill URL:
https://lookwhatidid.xyz/SKILL.md
The skill instructs the agent to install the CLI, run lwid push,
and return the shareable URL. No manual steps required.
URL scheme
| Type | Format | Who can… |
|---|---|---|
| View | /p/{project-id}#{read-key} |
Decrypt and view the project |
| Edit | /p/{project-id}#{read-key}:{write-key} |
View + push new versions |
Keys are base64url-encoded (no padding). The fragment
(#...) is never sent to the server by the browser.
Constraints
- Maximum project size: 10 MB.
- All files in the directory are uploaded (except hidden files,
node_modules, and.lwid.json). - The entry point must be
index.html. - Losing
.lwid.jsonmeans losing write access permanently — the server cannot recover your keys.
Source code
lwid is open source. The Rust workspace has three crates:
| Crate | Purpose |
|---|---|
lwid-common | Shared types, AES-256-GCM crypto, Ed25519 auth, CID utilities, blob/project stores |
lwid-server | Axum HTTP server, API endpoints, static file serving |
lwid-cli | CLI binary — push, pull, info commands |