{
  "schema": "leumas.docs.page/1",
  "id": "pkg:@leumas/encrypt-web",
  "slug": "products/encrypt-web",
  "kind": "capabilities",
  "bucket": "product",
  "title": "@leumas/encrypt-web — the encrypt/decrypt lab at encrypt.leumas.tech",
  "name": "@leumas/encrypt-web",
  "eyebrow": "the encrypt/decrypt lab at encrypt.leumas.tech",
  "chip": null,
  "summary": "encrypt.leumas.tech — the Leumas encrypt/decrypt lab. A public, prerendered reference to applied cryptography with a page per cipher suite and per KDF, and behind a Leumas membership, a playground...",
  "keywords": [
    "encrypt",
    "decrypt",
    "encryption",
    "aes",
    "chacha20",
    "scrypt",
    "argon2",
    "pbkdf2",
    "cipher",
    "leuc1",
    "encrypt leumas"
  ],
  "audience": "both",
  "funnel": {
    "product": null,
    "cta": null
  },
  "body": "\n# @leumas/encrypt-web — the encrypt/decrypt lab at `encrypt.leumas.tech`\n\nA public, prerendered reference to applied cryptography — a page per cipher suite and per\nkey-derivation function — and behind a Leumas membership, a playground where **every parameter of an\nencrypt/decrypt round trip is yours to change** and the result is downloadable.\n\nThe default lane runs in the visitor's **own browser** over the Web Crypto API. The file and the\npassphrase never leave the device, there is no upload and no size limit. A server lane exists for the\nfour things a browser genuinely cannot do.\n\n```\npnpm dev:encrypt             # vite on :5049, API proxied from :3000\npnpm --filter @leumas/encrypt-web build\npnpm smoke:encrypt-browser   # the LOCAL lane, in a real browser (needs dev:encrypt, no API)\npnpm smoke:encrypt-site      # the build, served through the real Imperium dispatcher\npnpm smoke:encrypt-api       # /api/encrypt, gate included, over real HTTP\npnpm check:encrypt-public    # every page indexed or disallowed on purpose\n```\n\n## The shape\n\n| | |\n|---|---|\n| `/` | The scroll story, then the catalogue. Public, prerendered, indexed. |\n| `/algorithms` | The index — ciphers, KDFs, envelopes, and the classical teaching tier. |\n| `/algorithms/<id>` | One page per suite, KDF, envelope and classical cipher — **derived from `@leumas/crypt`'s catalogue**, so the engine and the sitemap cannot disagree. |\n| `/lab` | The playground — three benches. Members only, `noindex`, `Disallow`ed. |\n| `/login` | Sign-in. `noindex`. |\n\n## The three benches\n\n| Bench | What it does |\n|---|---|\n| **Encrypt & decrypt** | A file *or* a message, both directions. Every cipher, KDF and cost knob. The message lane armours to Base64 both ways, so a round trip is copy-then-paste rather than download-then-upload — the half a file-only tool cannot do. |\n| **Keys & envelopes** | Generate an RSA-2048 or P-256 keypair in the tab, seal a file to a public key, open it with the private one. No passphrase anywhere. This is how you encrypt something **for somebody else**. |\n| **Classical** | Caesar, ROT13, Atbash, Vigenère — live, and a breaker that takes a Caesar apart with no key and shows all 26 candidates. Not encryption, and every surface says so. |\n\nA **status rail** sits above all three and answers the question the product turns on before you act on\nit: *which computer is about to process this?* It also shows the cipher and derivation actually in\nforce — read from the FILE on a decrypt, not from the pickers.\n\nThe engine is **`@leumas/crypt`** (`shared/packages/crypt`), the API is **`/api/encrypt/*`**\n(`products/leumas-api/src/routes/encrypt.js`), and the subdomain is one `configs` row written at boot\nby `shared/services/index/src/encryptSite.js`.\n\n## Six things that will bite whoever edits this next\n\n**1. [critical] Never set `VITE_API_BASE`.** Every path is relative and same-origin through Imperium's\n`createApiProxy`. An absolute API origin silently drops the httpOnly `leumas_session` cookie and the\npage renders permanently signed out, with nothing in the console.\n\n**2. [critical] With `spa: true`, any unproxied path answers `index.html` at 200 `text/html`.** Every helper\nin `src/lib/api.js` returns `{ status, body, json }` and **every caller must check `json` before\nbelieving a 200**. A caller reading the status alone sees success with a truthy body and concludes\nthe visitor is signed in.\n\n**3. [critical] `ready` is a separate flag from `signedIn`.** For the first ~100ms of every page load the\naccount probe has not answered, so every visitor looks signed out. `Gate.jsx` renders nothing at all\nuntil `ready` — walling on `!signedIn` flashes \"sign in\" at a paying member on every navigation.\n\n**4. [critical] `three` reaches the bundle only through the `lazy()` in `App.jsx`,** and the crypto lanes only\nthrough `lib/lab.js`'s dynamic import. A static import of either puts hundreds of kilobytes on the\nfirst paint of every prerendered reference page. `check:encrypt-public` reads the built chunks and\nrefuses it.\n\n**5. [critical] The build must emit `sitemap.xml`, `robots.txt` and `llms.txt` into `dist/`.** `servesOwnSeo`\ndefers to a site's own copies *only when they exist on disk*; without them the platform generates a\nsitemap from the config row, which knows no routes, and silently publishes **one URL**.\n\n**6. [critical] Namespace every root asset `encrypt-`.** imperium-server mounts the apex leumas-web build's\n`express.static` before this site's dispatcher on every host, so `favicon.ico`, `favicon-32.png`,\n`favicon-192.png`, `apple-touch-icon.png` and `logo.svg` are shadowed by leumas.tech's copies.\nNothing errors; the wrong logo simply appears in the tab.\n\n## The gate, and what it actually proves\n\nServer-side: `requireAuth` then `entitlements.requireCapability('encrypt.run')`, applied to the\n**whole router** so a route added later is gated by default. Every paid plan grants `encrypt.run`\n(`MEMBER_CAPABILITIES` in `@leumas/entitlements`'s `policy.js`) and an admin bypasses — that is the\nowner's rule, \"any Leumas membership or an admin\", on the sanctioned axis. It is **not**\n`requireMembership('encrypt')`, which would match only a membership whose *service* was literally\n`encrypt` and therefore refuse every existing member.\n\n[warning] **The honest limit, which the site itself states.** A hosted site's `dist/` is world-readable by\ncurl, so a determined non-member who reads the bundle could run the **local** lane offline. The gate\nis genuinely enforced on the server lane, on the algorithm catalogue and on anything that persists.\nNobody should be told otherwise.\n\n## Where the words live\n\n`src/content/pages.js` and `src/content/story.js` — **as data, not JSX**, because\n`scripts/prerender.mjs` is plain Node and writes the same sentences into the static HTML that React\nrenders. The home page's copy exists *only* in `story.js`, so a prerenderer that lost the beats would\nship the page with the most to say as the emptiest document on the site — and it would look\ncompletely fine in a browser. `check:encrypt-public` asserts every beat's words reach the rendered\nbody.\n\n**No count is typed anywhere.** Every number on the home page interpolates from `@leumas/crypt`'s\ncatalogue, so the page cannot claim a vocabulary the engine does not have.\n\n## Guards\n\n`smoke:encrypt-browser` is the one worth knowing about. The Node suite runs the web lane under\nNode's `crypto.subtle` — a real WebCrypto, but not a *browser's* — and `smoke:encrypt-api` never\ntouches the local lane at all. So the lane the whole promise rests on was exercised by nothing that\nruns in Chrome. That guard drives `src/lib/lab.js`, the exact module `Lab.jsx` imports, served by\nvite, inside a real browser: every suite × KDF the catalogue claims for the web lane, the armour\ncarry, both envelopes, and every refusal.\n\n## Related\n\n- `shared/packages/crypt` — the engine: the LEUC1 container, the catalogue, and the two lanes.\n- `shared/engines/adapters/domain/crypto` — the same engine as an adapter, so agents reach it too.\n- `products/leumas-programming` — the skeleton this was copied from.\n",
  "source": {
    "path": "products/leumas-encrypt/README.md",
    "blobSha": "",
    "commit": "",
    "committedAt": "",
    "provenance": "no-git",
    "bytes": 7411,
    "hash": "943713d596242d391470a252fb57ad3b0fb39e5d"
  },
  "urls": {
    "html": "/p/products/encrypt-web",
    "json": "/docs/products/encrypt-web.json",
    "md": "/docs/products/encrypt-web.md"
  },
  "links": {
    "composes": [
      "pkg:@leumas/capability-sdk",
      "pkg:@leumas/leviathan",
      "pkg:@leumas/theme",
      "pkg:@leumas/ui"
    ],
    "usedBy": [],
    "product": [],
    "howTo": [],
    "skills": []
  },
  "exports": null
}
