docs: document the /api proxy contract and env vars

Retire HONCHO_UPSTREAM and the same-origin sentinel; document the per-request
X-Honcho-Upstream header model, OPENCONCHO_DEFAULT_HONCHO_URL seeding, and the
optional OPENCONCHO_UPSTREAM_ALLOWLIST SSRF guard across compose, README,
AGENTS.md, and docs/docker.md.
This commit is contained in:
Offending Commit
2026-06-02 13:16:30 -05:00
parent ab8a1ba866
commit 9a35be7b15
4 changed files with 61 additions and 40 deletions

View File

@@ -5,6 +5,16 @@ builds the static bundle, then `nginx-unprivileged` serves it on port `8080` as
a non-root user) that also **reverse-proxies the Honcho API under its own
origin**, so the browser never makes a cross-origin request.
## How the proxy works
The browser issues every Honcho call same-origin to `/api/*` and names the real
upstream per request in an `X-Honcho-Upstream` header (sourced from the active
instance's base URL). nginx strips `/api`, forwards to that upstream server-side,
and returns the response. Because the browser→nginx hop is same-origin, **no CORS
applies**; the nginx→Honcho hop is server-side, where CORS is irrelevant. The
frontend stays the source of truth for which instance to talk to, so the
multi-instance switcher and the Fleet view keep working.
## Add it to a Honcho Compose stack (recommended)
Honcho's self-hosting path is Docker Compose. Drop the `openconcho` service from
@@ -16,8 +26,8 @@ services:
openconcho:
image: ghcr.io/offendingcommit/openconcho-web:latest
environment:
HONCHO_UPSTREAM: http://api:8000 # nginx proxies /v3 + /health here
OPENCONCHO_DEFAULT_HONCHO_URL: same-origin
# Seeds the first instance; nginx resolves this on the compose network.
OPENCONCHO_DEFAULT_HONCHO_URL: http://api:8000
ports:
- "127.0.0.1:8080:8080"
depends_on:
@@ -26,17 +36,17 @@ services:
restart: unless-stopped
```
`OPENCONCHO_DEFAULT_HONCHO_URL: same-origin` makes the UI default its Honcho
base URL to its own origin, so API calls go through the proxy → **no browser
CORS, and the API token never leaves the origin.** The published image is
multi-arch (amd64 + arm64); the first publish creates a private GHCR package —
make it public if you want unauthenticated pulls.
`OPENCONCHO_DEFAULT_HONCHO_URL` seeds the UI's first instance with an absolute
URL. The browser sends that URL in the `X-Honcho-Upstream` header; nginx (on the
compose network) forwards to it — **no browser CORS, and the API token never
leaves the origin.** The published image is multi-arch (amd64 + arm64); the first
publish creates a private GHCR package — make it public for unauthenticated pulls.
## Standalone
```bash
docker build -t openconcho-web .
docker run --rm -p 8080:8080 -e HONCHO_UPSTREAM=http://host.docker.internal:8000 openconcho-web
docker run --rm -p 8080:8080 -e OPENCONCHO_DEFAULT_HONCHO_URL=http://host.docker.internal:8000 openconcho-web
# → http://localhost:8080 · GET /healthz returns "ok"
```
@@ -44,24 +54,26 @@ Runtime knobs (no rebuild needed):
| Env | Default | Meaning |
|-----|---------|---------|
| `HONCHO_UPSTREAM` | `http://api:8000` | Where nginx proxies `/v3` and `/health` |
| `OPENCONCHO_DEFAULT_HONCHO_URL` | `same-origin` | SPA's default base URL — `same-origin`, an absolute URL, or empty (configure in Settings) |
| `OPENCONCHO_DEFAULT_HONCHO_URL` | _(empty)_ | Absolute URL seeding the first instance; empty = configure in Settings |
| `OPENCONCHO_UPSTREAM_ALLOWLIST` | _(empty)_ | Optional SSRF guard: comma-separated host globs (e.g. `honcho.example.net,*.honcho.dev`). Empty = forward anywhere |
Hardened run adds `--read-only --cap-drop ALL --security-opt no-new-privileges`
with `--tmpfs /tmp --tmpfs /var/cache/nginx`. Note: the runtime config writes
`config.js` into the web root at start, which a read-only root blocks — under
`--read-only` either bind-mount `config.js` or leave
`OPENCONCHO_DEFAULT_HONCHO_URL` empty and set the URL in Settings.
with `--tmpfs /tmp --tmpfs /var/cache/nginx`. Note: the entrypoint writes
`config.js` and the allowlist map at start, which a read-only root blocks — under
`--read-only` either bind-mount those paths or leave the env empty and configure
the URL in Settings.
## SSRF: when to set the allowlist
The header-driven proxy forwards to whatever upstream the client names. With the
default `127.0.0.1:8080` binding only your own machine can reach nginx, so leaving
the allowlist open is fine. **Before exposing the proxy** (e.g. behind a tunnel),
set `OPENCONCHO_UPSTREAM_ALLOWLIST` to the host globs you trust — non-matching
upstreams are rejected with `403` and an `X-Honcho-Proxy-Reject: allowlist` header.
## CORS, the short version
The desktop app routes HTTP through Rust and bypasses browser CORS; the web
build doesn't. The Compose setup above **solves CORS via the same-origin proxy**
— nothing to configure on Honcho. If instead you point the UI at a *different*
origin (absolute `OPENCONCHO_DEFAULT_HONCHO_URL` or a URL typed in Settings),
allow that origin in Honcho's FastAPI `CORSMiddleware`:
```python
app.add_middleware(CORSMiddleware, allow_origins=["https://your-ui-origin"],
allow_methods=["*"], allow_headers=["*"])
```
The desktop app routes HTTP through Rust (reqwest) and bypasses browser CORS; the
web build solves it with the same-origin `/api` proxy above — **nothing to
configure on Honcho.** The proxy makes a Honcho-side `CORSMiddleware` unnecessary
regardless of which instance you point at.