Repository navigation
fix(cli): surface cloud-exec stream failures instead of silent success - #804
Conversation
Cloud `exec` streams stdout/stderr over a WebSocket; when that attach fails (e.g. a 401 on the upgrade) the client recovered the exit code via a status probe and reported a silent `exit 0` with no output — indistinguishable from a command that genuinely produced none. - `emit_or_fallback` now carries the disconnect cause in `ExecResult.error_message` (was `None`); it is only reached when the output stream failed, so the result is no longer reported as a clean exit. - `StreamManager::start()` surfaces a non-empty `error_message` as an error (exit 1 + message) instead of dropping it. - REST 401/403 mapping now reads `unauthorized (HTTP 401)` / `forbidden (HTTP 403)` rather than a bare `auth: <body>`. - Add `debug!`/`trace!` to the previously-uninstrumented WS attach path (connect, per-frame, stdout/stderr, exit-frame, status-probe fallback). Reproducer: terminal::tests::stream_manager_surfaces_exec_error_message_as_error
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (3)
📝 WalkthroughWalkthrough
ChangesWS Disconnect Error Propagation
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
… (#809) ## What The API's WS `/attach` proxy authenticated upgrades **only as an API key**, so a browser/OIDC login (Auth0 JWT) — which works for REST — was rejected 401 on the attach upgrade and cloud `exec` output never streamed. Closes #807. (PR #804 already made the CLI surface this loudly instead of a silent `exit 0`.) ## Change `BoxliteWsProxyService.authenticate` now falls through to JWT when the bearer is not an API key: - verify via the existing `JwtStrategy.verifyToken` (same JWKS/issuer/audience as the HTTP path — no hand-rolled verification), - map `sub`/`uid` like `JwtStrategy.validate`, - take the org from the URL `{prefix}` tenant (`ATTACH_PATH` now captures it), - require org membership — identical to the API-key path. **Mirrors current behavior exactly** (per scope discussion): membership-only — no permission gate (there is no `box:exec` permission and the HTTP exec/attach routes enforce none), no rate-limiting, and a JWT with no/`default` tenant is rejected (org ambiguous for a multi-org user). `JwtStrategy` is nullable under `skipConnections`; `authenticate` never throws, so the fire-and-forget upgrade caller can't leak a hung socket. ## Verification - `boxlite-ws-proxy.service.spec.ts`: API-key regression + JWT member / non-member / cross-org / no-tenant / invalid-token / skipConnections / `uid`-claim, plus `matchAttachPath` tenant capture. Verified RED→GREEN (the JWT acceptance + membership-check tests fail when the JWT path is neutered). - Full `nx test api` green (140 tests). ##⚠️ Security review Cross-org access boundary. The membership check `organizationUserService.findOne(urlTenant, userId)` is what stops a JWT user attaching to boxes in an org they don't belong to — please scrutinize that and the URL-tenant extraction. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Bug Fixes** * Improved WebSocket `/attach` upgrade authentication by adding tenant-aware routing and validating both API keys and JWTs with correct precedence. * Clarified legacy vs tenant-prefixed attach paths and ensured unauthorized connections are rejected cleanly. * **Tests** * Expanded WebSocket authentication tests to cover tenant extraction, matching/non-matching attach paths, API-key and JWT authorization rules, claim mapping, and failure cases (including missing/invalid tokens and missing tenant context). <!-- end of auto-generated comment: release notes by coderabbit.ai -->
What
Cloud
execstreams stdout/stderr over a WebSocket. When that attach fails(e.g. a 401 on the upgrade), the client recovered the exit code via a status
probe and reported a silent
exit 0with no output — indistinguishable from acommand that genuinely produced none.
Changes
emit_or_fallbacknow carries the disconnect cause inExecResult.error_message(was
None); it is only reached when the output stream failed.StreamManager::start()surfaces a non-emptyerror_messageas an error(exit 1 + message) instead of dropping it.
401/403mapping readsunauthorized (HTTP 401)/forbidden (HTTP 403)instead of a bare
auth: <body>.debug!/trace!to the previously-uninstrumented WS attach path(connect, per-frame, stdout/stderr, exit-frame, status-probe fallback).
Verification
exec: before = silentexit 0, empty; after =exit 1+ clear cause.terminal::tests::stream_manager_surfaces_exec_error_message_as_errorverified RED→GREEN.Summary by CodeRabbit
Bug Fixes
Chores