feat(server): viewer-token view-only/control split - closes CRITICAL #1
Authz-strength fix (coord todo c8916c89), code-reviewed APPROVED. Replaces the
weak "view" gate (held by every role) with a permission-tiered access mode
stamped inside the signed viewer token:
- mint: is_admin() || has_permission("control") -> CONTROL token; else
has_permission("view") -> VIEW_ONLY token; else 403.
- enforce: the relay drops MouseEvent/KeyEvent/SpecialKey for a VIEW_ONLY token
before forwarding (video still streams); CONTROL tokens forward under the
Task-3 throttle. Mode is unforgeable (in the signature) and unbypassable
(all other viewer->agent payloads hit the catch-all and are never forwarded).
A low-privilege viewer-role user can now at most watch, never control. New
ViewerAccess enum (view_only|control) on ViewerClaims; 3 unit tests.
Audit CRITICAL #1 now fully closed (mechanism in Task 3 + this authz strength).
Not cargo-check-verified locally (no toolchain) - the push triggers CI
(clippy -D warnings + build + test) which is the verification gate.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,11 +1,15 @@
|
||||
# v2 Secure Session Core — Implementation Plan
|
||||
|
||||
> Spec created: 2026-05-29
|
||||
> Status: in progress — Tasks 1-3 DONE 2026-05-29 (Task 3 code-reviewed APPROVED). REQUIRED follow-up
|
||||
> before Phase-1 exit: viewer-token authz STRENGTH — the gate uses `view` (held by EVERY default role
|
||||
> incl. `viewer`) but a viewer token grants input CONTROL; flip to `control`, or split VIEW_ONLY/CONTROL
|
||||
> tokens (proto already models SCREEN_CONTROL vs VIEW_ONLY). PENDING Mike. Also: nothing revokes a minted
|
||||
> viewer token on logout (bounded by 5-min TTL) — follow-up todo. Task 4 (rate limiting + single-use codes) next.
|
||||
> Status: in progress — Tasks 1-3 DONE 2026-05-29 (Task 3 code-reviewed APPROVED). Viewer-token authz
|
||||
> STRENGTH split IMPLEMENTED 2026-05-29 (self-reviewed; no Rust toolchain on this machine — not yet
|
||||
> `cargo check`-verified; pending Code Review). This was the REQUIRED Phase-1-exit follow-up: the gate
|
||||
> previously used `view` (held by EVERY default role incl. `viewer`) but a viewer token granted input
|
||||
> CONTROL. DECIDED (Mike, 2026-05-29) + IMPLEMENTED: SPLIT VIEW_ONLY/CONTROL tokens — `view`-perm users
|
||||
> get a watch-only token (relay refuses their input), admin/`control` users get a control token. See the
|
||||
> "Task 3 authz-strength fix" block under Task 3 below. Resolves coord todo c8916c89 (coordinator marks
|
||||
> done after review). Remaining follow-up: nothing revokes a minted viewer token on logout (bounded by
|
||||
> 5-min TTL) — follow-up todo. Task 4 (rate limiting + single-use codes) next.
|
||||
> CARRY-FORWARD: Task 3 MUST add a viewer-token AUTHORIZATION check (admin/permission gate) — Task 2
|
||||
> fixed only the token *mechanism*; the authz gate is what actually closes audit CRITICAL #1.
|
||||
> Policy DECIDED (Mike, 2026-05-29): admin-or-view-permission (`is_admin() || has_permission(...)`).
|
||||
@@ -115,6 +119,40 @@ Reference: `relay/mod.rs:224` (`validate_agent_api_key` — the CRITICAL), `auth
|
||||
> `server/src/api/sessions.rs`, `server/src/db/machines.rs`, `server/src/auth/mod.rs`,
|
||||
> `server/src/auth/jwt.rs`, `server/src/main.rs`.
|
||||
|
||||
### Task 3 authz-strength fix — VIEW_ONLY/CONTROL token split [IMPLEMENTED 2026-05-29 — self-reviewed; no Rust toolchain on this machine, not yet `cargo check`-verified; pending Code Review]
|
||||
|
||||
> Closes audit CRITICAL #1 at full strength (coord todo c8916c89). The Task-3 gate
|
||||
> minted a viewer token for any `is_admin() || has_permission("view")` user, but `view`
|
||||
> is held by EVERY default role (incl. `viewer`) and the token granted input CONTROL —
|
||||
> intra-tenant privilege escalation. Now the token carries an ACCESS MODE inside its
|
||||
> signed claims and the relay enforces it:
|
||||
>
|
||||
> - `auth/jwt.rs`: new `ViewerAccess` enum (`ViewOnly` | `Control`, serde-renamed to
|
||||
> `"view_only"`/`"control"`); `ViewerClaims` gains an `access: ViewerAccess` field;
|
||||
> `create_viewer_token(..., access)` stamps it; `validate_viewer_token` returns it as
|
||||
> part of the claims (sig+exp+`purpose` checks unchanged). New unit tests cover the
|
||||
> round-trip, the lowercase wire form, and login-JWT rejection.
|
||||
> - `auth/mod.rs`: re-export `ViewerAccess`.
|
||||
> - `api/sessions.rs` (`mint_viewer_token`): TIERED mint — `is_admin() || has_permission("control")`
|
||||
> → CONTROL token; else `has_permission("view")` → VIEW_ONLY token; else → 403 (standard
|
||||
> envelope). Permission constants `SESSION_CONTROL_PERMISSION="control"` /
|
||||
> `SESSION_VIEW_PERMISSION="view"`. Response echoes `access` (advisory; the signed claim
|
||||
> is authoritative).
|
||||
> - `relay/mod.rs`: `viewer_ws_handler` reads `claims.access` from the VERIFIED token and
|
||||
> threads it into `handle_viewer_connection` (new `access: ViewerAccess` param). In the
|
||||
> input path, a view-only token's `MouseEvent`/`KeyEvent`/`SpecialKey` are refused (a
|
||||
> guarded match arm `if !access.can_control()` that silently drops + logs once-per-
|
||||
> power-of-two), BEFORE the throttle/`try_send`. A control token forwards as before (with
|
||||
> the Task-3 throttle). Video still streams to a view-only viewer; chat (not an injected-
|
||||
> input vector) is still relayed. The mode cannot be forged — it lives in the signed token.
|
||||
>
|
||||
> Everything else from Task 3 (session_id-claim match, blacklist, frame caps, throttle,
|
||||
> agent identity binding) is intact — this is purely additive access-mode enforcement.
|
||||
>
|
||||
> PHASE-2 REFINEMENT: this refuses to FORWARD input for a view-only token; it does NOT yet
|
||||
> tie the viewer mode to the agent-side `SessionType.VIEW_ONLY` capture mode (the agent still
|
||||
> does full capture). Deferred (deeper agent change).
|
||||
|
||||
Files touched: `server/src/relay/mod.rs`, `server/src/session/mod.rs`.
|
||||
|
||||
- **`viewer_ws_handler`** (`relay/mod.rs:242`): verify the viewer token's **signature + expiry +
|
||||
|
||||
Reference in New Issue
Block a user