# @leumas/converter-web — the converter at converter.leumas.tech

The public converter microservice at converter.leumas.tech — every pure, offline Leumas adapter as a tool anyone can run without an account. The tool list, every form on it and the sitemap are all...


Every pure, offline Leumas adapter as a tool anyone can run — **no account, nothing uploaded,
nothing stored**. Base64 and hashes, case conversion and slugs, JSON reshaping, markdown and CSV,
image resizing, unit conversion, and one date rendered in twenty-five calendars.

## The one thing to know

**There is no tool list in this product.** The page fetches `GET /api/convert` and builds the whole
directory — and every form on it — from what comes back. A pack added to
`shared/engines/adapters/` that declares `safe: true` appears here on the next page load, with no
change to this product and no redeploy of it.

That is only possible because a tool's schema is now **its own**. Until recently the adapter registry
published the *pack-level* input list for every function of a system, so all eighty of `a-text`'s
verbs described themselves as taking `{text}` and nothing else. A catalogue built from that would
have rendered eighty identical one-box forms, and `Truncate` would have had nowhere to put its
length. See `@leumas/schemas/adapter-spec` for the tier order that fixed it.

## Why a public lane exists at all

`/api/adapters` is behind `requireAuth` and PassNode metering. That is right for a surface that can
shell out to ffmpeg and read files off the server — and it is why a public converter could not exist:
every tool on it, including `uppercase`, needed an account.

`/api/convert` (`shared/engines/middleware/src/adapters/convertRouter.js`) is a **second, narrower
door onto the same registry**. `/api/adapters` is untouched; nothing about this lane weakens it.

Three things make it safe, and each is load-bearing:

| | |
|---|---|
| **An allowlist that defaults to closed** | `spec.safe` is false unless a pack positively said otherwise, so every pack nobody has reviewed is absent *by construction* rather than by anyone remembering to exclude it. A denylist would have been wrong the first time somebody added a pack. |
| **`untrusted: true` on the call** | The allowlist alone would **not** have made `image` safe: its `image` argument accepts a filesystem path, which is a convenience for an authenticated operator and an arbitrary-file-read for a visitor. `normalizeInput` refuses paths when this flag is set. |
| **A body cap** | These tools take their payload inline — an image arrives as base64 — so without a cap the cheapest denial of service is one request. |

A tool that is *well described* is not a tool that is *safe*: `a-audio` has a per-function
`metadata.json` for all thirty-two of its tools and is the best-documented pack in the tree, and it
is not on this lane. Conflating the two is how an allowlist built from "has a schema" would have
opened ffmpeg to the internet.

## Running it

```bash
pnpm --filter @leumas/converter-web dev      # :5044, proxying /api to the API on :3000
pnpm --filter @leumas/converter-web build    # -> dist/, which the site row serves
pnpm smoke:converter-site                    # the row AND the lane, offline
```

**Never set `VITE_API_BASE`.** Most of this site is anonymous, so the damage is subtler than on a
signed-in surface: the converter would keep working while the signed-in half silently stopped. An
absolute API origin makes every call cross-origin, which drops the httpOnly `leumas_session` cookie.

## How the subdomain answers

A platform-owned Imperium site row — `shared/services/index/src/converterSite.js` — provisioned on
every API boot. Read `platformSite.js`'s header before changing it: the hosted-not-first-party lane,
the deliberately empty `ownerUserId`, the non-pruning upsert and the `static` target are each a trap
that has been hit once already, and none of them is visible from the file itself.

The edge needs nothing. IIS binds `*.leumas.tech` to the Imperium worker, `converter` is not in
`RESERVED_SUBDOMAINS`, and `createApiProxy` already forwards `/api` — which `/api/convert` is inside —
to leumas-api on every hosted host. **Do not** add `/convert` to `PROXIED_PREFIXES` to shorten the
URL: that list applies to every host the worker serves, so claiming it globally would take the path
away from every customer site on the platform.

## Root-level filenames are shadowed

imperium-server mounts `express.static` over the **apex** leumas-web build before the target
dispatcher, on every host. So `favicon.ico`, `favicon-32.png`, `favicon-192.png`,
`apple-touch-icon.png` and `logo.svg` at this site's root are served from leumas.tech's copy instead.
Nothing errors; the wrong logo simply appears in the tab. Every root-level file this product adds is
namespaced `converter-`. Hashed `/assets/*` are safe.


---
Source: products/leumas-converter/README.md
Canonical: https://docs.leumas.tech/p/products/converter-web
