Deploy Workshop safely¶
Workshop is a developer-facing web UI with full control over worker configs, pipeline configs, eval runs, RAG ingest, and deployed apps. The default bind is loopback-only with no auth — fine for local development, not safe for anything else without the steps below.
The authoritative threat model lives in SECURITY_MODEL.md. This runbook is the operational procedure that follows from it.
Symptom¶
You need to stand up Workshop on:
- A LAN bind (so a teammate can reach it).
- A jump host (so you can reach it through SSH port-forwarding + someone else can too).
- Anything that isn't
127.0.0.1.
Diagnosis¶
Three layers of safety, in order of decreasing strength:
| Layer | Why it matters | Default state |
|---|---|---|
| Bind address | Loopback is unreachable from off-host; anything else needs auth | Loopback (127.0.0.1) |
HEDDLE_WORKSHOP_TOKEN |
Bearer/cookie auth on every mutating route | Unset (no auth) |
| App capability preview | Two-step deploy gates filesystem/subprocess capability acquisition | Enabled (cannot be disabled outside CI) |
The CLI emits workshop.insecure_bind at startup if bind != loopback
AND token is unset. Treat that warning as a P1 — it means anyone on
the network can mutate your configs.
Mitigation¶
Procedure for a LAN bind:
-
Generate a high-entropy token. No fewer than 32 bytes.
Store the value somewhere a reload can reach (env file, secrets manager). Workshop reads it once at startup — rotating requires a restart.
-
Start Workshop with the explicit non-loopback bind.
Confirm the startup log does not include
workshop.insecure_bind. If it does, the token didn't reach the process — check the env-var name. -
Distribute the token. For browser use, visit once:
Paste the token into the form and submit. Workshop sets a cookie; subsequent requests reuse it. The token is sent in the POST body so it never appears in uvicorn access logs. For programmatic access, pass
Authorization: Bearer <token>on every mutating request. -
Confirm app deployment requires capability review. Try deploying an app with elevated capabilities (e.g. subprocess backend, non-loopback MCP). The
/apps/deployflow should hand you a confirmation page enumerating capabilities; nothing extracts onto disk until you POST to the/confirm/{token}route. If it deploys silently, you are running in a CI/auto- approve mode — turn that off for any deployment outside a test harness. -
Plan for the data Workshop sees. Workshop reads worker configs (sometimes containing API key references), reads knowledge silos (sometimes containing customer data), and accepts uploaded app bundles. Treat the bind address as the blast radius of any compromise.
Verify¶
# Without the token, every mutating endpoint should 401
curl -i -X POST http://your-host:8000/workers/test_worker
# Expect: HTTP/1.1 401 Unauthorized
# With the token, the request goes through to the validator
curl -i -X POST http://your-host:8000/workers/test_worker \
-H "Authorization: Bearer $HEDDLE_WORKSHOP_TOKEN" \
-F "yaml_content=name: test_worker\nsystem_prompt: ok\n"
# Expect: HTTP/1.1 303 (redirect to detail) or 400 (validation error)
GET routes are not auth-gated by default — this is intentional, but worth knowing. If a read-only leak is also unacceptable, front Workshop with a reverse proxy that requires auth for all routes.
Followup¶
Workshop is not designed to be an internet-facing service. Even fully token-gated, the surface area (multipart uploads, app extraction, subprocess backend deploys) is larger than a production-hardened admin UI would tolerate. For multi-user team deployments:
- Front it with a reverse proxy that adds SSO and audit logging.
- Run it in a network namespace separate from the production NATS.
- Read SECURITY_MODEL.md end-to-end before the first external user touches it.