Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ All notable changes to LLooM will be documented in this file. The format follows

### Added

- Opt-in egress redaction (`security.redactEgress`, per-backend `redact`). Credentials such as API keys, tokens, PEM private keys, JWTs, URL passwords and labelled secrets in request text are replaced with `[secret]` before the gateway forwards a Chat Completions, Responses, Anthropic Messages or JSON media request to a backend outside the local network. Local, private, tailnet and federated LLooM backends are left alone, and embeddings are never rewritten. See [egress redaction](docs/architecture.md#egress-redaction).

- Built-in reservations and a job log (`lloom schedule`, `reserve`, `renew`, `release`, `job`; `/gateway/schedule`). Expiring leases on configured resources run route and suspend actions, possibly on other cluster nodes, and undo them in reverse with health-gated restore. There are queues, renewal and heartbeat-extended leases, stalled-job detection, and an event feed for boards such as Tessera. Schedule refusals now reach the CLI as redacted messages.

- Atlas SparkQwen RC16 two-Spark recipe (`linux-nvidia-dgx-spark-2x-qwen38-flash-next-atlas`, profile `8x262k`) with a fail-closed pin gate and a digest-only image installer, and explicit per-node `${fabricHca}` settings for distributed recipes. Both Atlas recipes pin the published images with exact rail selection; missing settings and ambiguous discovery fail clearly.
Expand Down
1 change: 1 addition & 0 deletions SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ The maintainers will acknowledge reports as soon as practical, validate the issu
- A signature proves which key signed a recipe pack. Trusting keys served by the same community host is equivalent to trusting that host and its TLS connection; use explicit local trusted keys for stronger publisher pinning.
- Development keys and the checked-in public seed key are not production trust roots.
- Model weights and external runtimes have their own licenses and security posture. LLooM does not make untrusted model code safe.
- Requests routed to cloud providers carry whatever text the client sent. Set `security.redactEgress` to scrub common credential formats from prompts before they leave the local network. It is a pattern-based safety net, not a guarantee; see [egress redaction](docs/architecture.md#egress-redaction).
- Browser management requests must come from the gateway's own origin. First-run setup additionally requires a local session token and applies only reviewed plans. Keep the setup terminal and tokens private. See [the browser security boundary](docs/browser-experience.md#local-security).
- The public community MVP is read-only by design. Proposals arrive through reviewed pull requests; anonymous recipe and benchmark uploads are disabled in production.
- Remote community feeds must use HTTPS and a locally pinned public signing key. Do not treat a signing key downloaded from the same remote host as an independent trust root.
Expand Down
58 changes: 58 additions & 0 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,12 +87,70 @@ See [OpenRouter provider routing](https://openrouter.ai/docs/guides/routing/prov
| `security.allowWildcardCors` | `false` | Non-loopback CORS origin is `null` unless enabled |
| `security.apiKeys` | `["sk-lloom-local"]` in default config | Inference Bearer / `x-api-key` values |
| `security.adminApiKeys` | `[]` | When non-empty, admin routes require these keys even on loopback |
| `security.redactEgress` | `false` | Scrub credentials from request text sent to external backends (see below) |
| `logging.requestLog` | `false` | Append NDJSON request lines to `~/.lloom/logs/requests.ndjson` (or `LLOOM_REQUEST_LOG=1`) |
| `logging.metricsPersistence` | `true` | Persist aggregate gateway metrics across restarts (or disable with `LLOOM_METRICS_PERSISTENCE=0`) |
| `logging.metricsPath` | `null` | Override `~/.lloom/metrics-history.json` (or set `LLOOM_METRICS_PATH`) |

Prefer binding `server.host` to `127.0.0.1`. If you expose the gateway on a LAN or public interface, set `allowNonLoopbackBind`, strong API keys, keep `allowRemoteAdmin` false unless you intentionally operate remote admin, and treat install/start endpoints as privileged. `GET /gateway/security` returns public auth metadata (no secrets) for dashboards.

### Egress redaction

With `security.redactEgress: true`, the gateway replaces credentials in a
request's text with `[secret]` before forwarding it to an external backend,
such as DeepSeek or OpenRouter. It is off by default.

```json
{
"security": { "redactEgress": true },
"backends": {
"deepseek": { "type": "openai", "baseUrl": "https://api.deepseek.com/v1", "apiKeyEnv": "DEEPSEEK_API_KEY" },
"lab-proxy": { "type": "openai", "baseUrl": "https://llm.lab.example/v1", "redact": false }
}
}
```

**Which backends.** A backend is external unless its `baseUrl` host is
loopback (`127.0.0.0/8`, `::1`, `localhost`), RFC 1918 (`10/8`, `172.16/12`,
`192.168/16`), link-local, CGNAT/tailnet (`100.64.0.0/10`), IPv6 ULA
(`fc00::/7`), a name ending in `.ts.net`, `.local`, `.internal` or
`.localhost`, or an unqualified single-label name such as `ennspark03`. The
host is not resolved, so a public name that points at a private address still
counts as external. Backends with `audioProvider` or `videoProvider` are
always external, because those adapters call OpenRouter or Replicate directly.
A federated LLooM gateway (`lloom-node-*`) is internal: the downstream gateway
applies its own policy when it egresses. `backends.<id>.redact: true | false`
overrides the automatic decision in either direction, and `true` applies even
when `security.redactEgress` is off.

**What is scrubbed.** The outgoing JSON body after protocol translation, so
Chat Completions, Responses and Anthropic Messages (both bridged to Chat
Completions), streaming or not, share one code path. The scrubbed fields are
message `content` (strings and text parts), assistant `tool_calls` arguments,
`role: tool` results, replayed `reasoning_content`, and `prompt`, `input`,
`instructions` and `system`, which also covers JSON image, video, speech and
audio-generation prompts. Image, audio and file parts are left as they are.
Embeddings are never rewritten, because changing their input changes the
vectors, and neither are transcriptions, whose payload is audio. Multipart
uploads (image edits and voice-clone speech) and web search queries are not
scrubbed.

**Patterns.** PEM private keys; `sk-` keys, including Anthropic, OpenAI
project and OpenRouter `sk-or-v1-` keys; `lloom_*` keys; GitHub, Slack, AWS
access key, Google API, Replicate, Hugging Face, Jina and xAI tokens; JWTs;
the password in `scheme://user:password@host` URLs such as `mongodb+srv://`,
`postgres://` and `redis://`; and labelled values such as `Bearer …`,
`API_KEY=…`, `password: …` or `"client_secret": "…"`, where the label is kept.
A labelled value needs at least six characters, so `token budget: 500` stays
intact. Unquoted values that read as code, such as `getToken()`,
`process.env.API_KEY`, `$TOKEN` or a word without digits or symbols, are left
alone. This catches common credential formats and is not a guarantee: a
letters-only password after an unquoted label passes through.

The caller's request is never modified. Matched values are never logged; when
anything is replaced, the gateway logs one line per request with the number of
replacements and the backend ID.

## API Surface

LLooM currently fronts these local contracts:
Expand Down
Loading
Loading