Pre-fix, dashcaddy-api/routes/exec.js (the ws://host/ws/exec/:containerId
WebSocket container terminal endpoint) captured auth.scope at lines 39/46
but never enforced it — any API key or JWT, regardless of scope, got a
full PTY-backed shell inside the running container. A key issued with
scope ['read'] (a legitimate monitoring/observability scope) could
escalate to a root-equivalent shell. Container exec is full root inside
the container's user namespace, so this was a privilege-escalation across
the auth trust boundary.
Fix:
1. assertExecScope(auth) requires scope.includes('admin'); throws a
tagged 403 error (DC-072_INSUFFICIENT_SCOPE) on rejection with
requiredScope + actualScope in the envelope.
2. Called BEFORE wss.handleUpgrade so the WS gate cannot be bypassed.
3. 403 over the upgrade socket is JSON (code, requiredScope, actualScope)
so the dashboard can show operator-actionable messages.
4. isValidContainerId(id) tightened to Docker's actual charset
(12 or 64 lowercase hex). Pre-fix regex accepted _, -, ., mixed
case, and any length up to 128; Docker would 404 the inspect and the
rejection surfaced as a generic 500.
5. Audit-log pair: session start (container name + auth id) and session
end with durationMs + reason ('exec-stream-end' vs 'ws-close'
for abnormal disconnects); idempotent via ended-flag guard.
6. Both helpers exported via __test for unit tests (no live WS).
Tests: 20 new tests in __tests__/routes/exec.routes.test.js cover:
- assertExecScope: admin passes; read/write/empty/undefined/null/non-array
rejected with the canonical 403 envelope.
- isValidContainerId: 12/64 lowercase hex accepted; uppercase / mixed /
non-hex / _.- / wrong length / null / non-string / padded / CRLF
payload rejected.
Full suite: 2327/2327 tests passing across 100 suites (zero regressions).
GLM-5.3 round 1: A with 2 LOW polish (scope-coercion defensive comment +
abnormal-close audit-log fallback). Both folded into the same commit.
Round 2: A. Ship.
1. WebSocket exec auth bypass (exec.js): Require valid JWT or API key
before accepting WebSocket upgrade. Reject unauthenticated requests
with 401 before the upgrade completes.
2. Shell injection in router auto-login (session-handlers.js): Validate
baseUrl against safe hostname pattern before embedding in wget shell
command. Reject with null session if invalid.
3. Path traversal in credentials routes (services.js): Add explicit
serviceId validation (alphanumeric + dash/underscore/dot, max 100
chars) to all three credential endpoints. Removed redundant
try/catch wrapper.
4. execSync injection in CA CSR generation (ca.js): Add sanitize step
replacing any non-alphanumeric domain chars with underscore before
interpolation into shell subj argument. Redundant with existing
validation but provides defense-in-depth.
5. Auth bypass when TOTP disabled (middleware.js): Split the logic
cleanly — disabled TOTP means no auth (initial setup state), enabled
TOTP means all auth methods checked (session/JWT/API key). Removed
the sessionDuration:never conflating shortcut.
- Container exec/shell via WebSocket + xterm.js (subtle >_ button on cards)
- Live dashboard updates via SSE (resource alerts, health changes, update notices)
- Docker Compose import with YAML parsing, preview, and dependency-ordered deploy
- Volume & network management modal with disk usage overview
- CPU/memory resource limits on deploy and live update
- Email SMTP notifications (nodemailer) alongside Discord/Telegram/ntfy
- Scheduled auto-update scheduler with maintenance windows (daily/weekly/monthly)
New deps: ws, js-yaml, nodemailer
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>