diff --git a/docs/api-reference.md b/docs/api-reference.md index 7d892c48..f909ea00 100644 --- a/docs/api-reference.md +++ b/docs/api-reference.md @@ -1219,7 +1219,7 @@ A local case whose folder is the top of a git repository can keep **worktrees** | ------ | ----------------------------- | ------------------------------------ | ----- | | `GET` | `/api/settings` | none | The stored settings object. Never writes. For a non-admin in multi-user mode, without `voiceSettings.apiKey`, `caseOrder` and `lastUsedCase`. | | `PUT` | `/api/settings` | partial settings (strict schema) | Merged into the stored file; services (watchers, tunnel, tmux history limit) reconcile to the merged result. `403 FORBIDDEN` for a non-admin in multi-user mode. A tunnel enable with no login (no `CODEMAN_PASSWORD`, not multi-user) is refused separately (`403 OPERATION_FAILED`, resend with `acknowledgeUnauthTunnel: true`) unless the server runs with `CODEMAN_ALLOW_UNAUTHENTICATED_TUNNEL=1` or `--allow-unauthenticated-tunnel`. `CODEMAN_ALLOW_UNAUTHENTICATED_NETWORK` does not count; when it is set, the refusal says so. An enable without that authorization is accepted only while the tunnel is already running and never stores `tunnelEnabled: true`; if the tunnel stops before the request would start it, it is not restarted and the answer is the same `403 OPERATION_FAILED` (the request's other settings are saved). | -| `GET` | `/api/tunnel/status` | none | `{ running, url, ..., loginConfigured }`. `loginConfigured` is `true` when a login protects the server (`CODEMAN_PASSWORD` set or multi-user mode on), the same test the tunnel guard above applies; with `false` the browser asks before every tunnel enable. Neither opt-in variable makes it `true`. | +| `GET` | `/api/tunnel/status` | none | `{ running, url, loginConfigured }`. `loginConfigured` is `true` when a login protects the server (`CODEMAN_PASSWORD` set or multi-user mode on), the same test the tunnel guard above applies; with `false` the browser asks before every tunnel enable. Neither opt-in variable makes it `true`. | | `GET` | `/api/execution/model-config` | none | `modelConfig` (cron's and the plan orchestrator's default model). | | `PUT` | `/api/execution/model-config` | `{ defaultModel?, agentTypeOverrides?, ... }` | Replaces `modelConfig`. `403 FORBIDDEN` for a non-admin in multi-user mode. | | `PUT` | `/api/cases/order` | `{ order: string[] }` | The one case order every user's `GET /api/cases` is sorted by. `403 FORBIDDEN` for a non-admin in multi-user mode. | diff --git a/src/web/routes/system-routes.ts b/src/web/routes/system-routes.ts index c7245363..52deb24f 100644 --- a/src/web/routes/system-routes.ts +++ b/src/web/routes/system-routes.ts @@ -1228,9 +1228,9 @@ export function registerSystemRoutes( reply.code(403); return createErrorResponse(ApiErrorCode.OPERATION_FAILED, unauthenticatedTunnelRefusal(true)); } - // Loud warning whenever a public tunnel is started with no password, whether + // Loud warning whenever a public tunnel is started with no login in front of it, whether // acknowledged via the tunnel opt-in or the per-request UI confirmation. - if (!process.env.CODEMAN_PASSWORD) { + if (!isLoginConfigured()) { console.warn( '⚠️ [tunnel] Starting an UNAUTHENTICATED public Cloudflare tunnel — no CODEMAN_PASSWORD set. ' + 'Anyone with the tunnel URL gets full terminal control (effectively RCE). ' + diff --git a/test/install-sh-invariants.test.ts b/test/install-sh-invariants.test.ts index 31eee927..0fd588be 100644 --- a/test/install-sh-invariants.test.ts +++ b/test/install-sh-invariants.test.ts @@ -534,4 +534,5 @@ describe('install.sh review fixes for #460', () => { it('install.sh never contains the unauthenticated tunnel opt-in', () => { expect(SOURCE).not.toContain('CODEMAN_ALLOW_UNAUTHENTICATED_TUNNEL'); + expect(SOURCE).not.toContain('--allow-unauthenticated-tunnel'); });