Skip to content
Open
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
10 changes: 9 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -95,8 +95,16 @@ docker/shell: ## Open a shell inside the running backend container
$(COMPOSE) exec backend /bin/bash

.PHONY: docker/run
# Published on loopback, like compose, `docker/aio` and both smoke scripts. The
# server binds 127.0.0.1 by default precisely because it has no authentication,
# but the image's ENTRYPOINT pins --host 0.0.0.0 so a published port reaches it
# at all — which means the port mapping is the only thing left deciding who can
# talk to it. `-p $(COLLAPSE_PORT):8000` binds every interface, so on a laptop
# on a cafe network this target alone put an unauthenticated compression
# service on the LAN. Publish wider by hand, on a network you have thought
# about.
docker/run: docker/build ## Run a throwaway container — ARGS="--max-upload-mb 50"
docker run --rm -p $(COLLAPSE_PORT):8000 $(IMAGE) $(ARGS)
docker run --rm -p 127.0.0.1:$(COLLAPSE_PORT):8000 $(IMAGE) $(ARGS)

.PHONY: docker/smoke
docker/smoke: ## Build, start, drive a real compression through the published port, stop
Expand Down
39 changes: 39 additions & 0 deletions docs/server.md
Original file line number Diff line number Diff line change
Expand Up @@ -604,6 +604,45 @@ Everything below follows from that:
The full picture, including what the server does defend against when it unpacks
a tar someone sent it, is in [threat_model.md](threat_model.md#the-api-server).

### Putting it behind something that authenticates

"Put it behind a proxy" is the standing advice above, and it is worth spelling
out, because **the CLI already works with the most ordinary form of it** and
nothing in the codebase said so.

`ureq` sends HTTP Basic credentials from a URL's userinfo, so a reverse proxy
that asks for a password needs no client change:

```bash
collapse compress notes.txt --server http://user:secret@proxy.internal:8080
```

Verified against a stub that requires authentication: the request arrives with
`Authorization: Basic dXNlcjpzZWNyZXQ=`, and a rejection is reported legibly
rather than as a transport failure —

```
error: the server rejected the request (HTTP 401): authentication required
```

— because the CLI prefers the server's JSON `detail` field when it has one.

**What this costs, stated rather than left to be discovered.** The credential is
in the command, so it is in shell history and in the process list while the
command runs. If the same URL is saved as a server in the desktop app it is
written to the webview's `localStorage` in clear text (`apps/desktop/src/
sources.js`), which is a file on disk with no protection beyond the user
account. Prefer a credential minted for this purpose over a password that
unlocks anything else.

**What the client cannot do.** There is no way to send a bearer token, an API
key header, or a client certificate. Basic through a proxy is the whole of it
today, which is enough for "keep strangers off it" and is not enough for
per-client identity or revocation.

Terminating TLS at that proxy costs nothing extra on the client: `ureq` is built
with rustls, so `--server https://…` works the moment something answers on it.

## Known limitations

Worth knowing before deploying this unattended:
Expand Down
5 changes: 4 additions & 1 deletion docs/threat_model.md
Original file line number Diff line number Diff line change
Expand Up @@ -358,7 +358,10 @@ Stated plainly, because deploying it assumes these:
- **No authentication and no rate limiting.** Anyone who can reach the port can
submit jobs, and jobs consume CPU, memory and disk. Bind it to localhost (the
default, and what the container image publishes) or put it behind something
that authenticates.
that authenticates. That second half is written out in
[server.md](server.md#putting-it-behind-something-that-authenticates):
the CLI already carries Basic credentials through a proxy, and what that
costs is stated there too.
- **The web frontend proxies the API, so its port exposes the API too.** nginx
forwards `/compress` and `/jobs` to the backend, which is what keeps the
browser same-origin; the consequence is that publishing the web port to a
Expand Down
Loading