fix(security): require an authenticated session for preview routes (closes #51) - #159
Conversation
|
Warning Review limit reached
Next review available in: 3 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (2)
📝 WalkthroughWalkthroughPreview routes now validate session IDs, require authentication, and enforce session ownership before serving files. Route tests cover asset rewriting, injected preview markup, cache busting, malformed requests, unauthenticated denial, and authenticated access. ChangesPreview access control
Estimated code review effort: 3 (Moderate) | ~20 minutes Suggested labels: Suggested reviewers: Sequence Diagram(s)sequenceDiagram
participant Client
participant PreviewRoute
participant AuthMiddleware
participant SessionStore
participant PreviewFiles
Client->>PreviewRoute: Request /preview/:sessionId/*
PreviewRoute->>AuthMiddleware: Validate session and extract credentials
AuthMiddleware->>SessionStore: Check preview session owner
AuthMiddleware->>PreviewFiles: Authorize matching user
PreviewFiles-->>Client: Return preview content
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@backend/src/routes/preview.routes.ts`:
- Around line 112-124: Update the authentication guard around extractAccessToken
in the preview route so a bearer token is accepted only after credential
validation/introspection resolves a principal, or when req.authSession is
already authenticated; do not authorize based on token presence alone. Preserve
the owner comparison and ownerless-preview behavior only for authenticated
requests, and add a regression test confirming a random Bearer value returns
401.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 78f4c2dd-6974-474d-a761-f8ae513bb0d7
📒 Files selected for processing (2)
backend/src/routes/preview.routes.tsbackend/tests/server.routes.test.js
📜 Review details
⏰ Context from checks skipped due to timeout. (1)
- GitHub Check: test
…erified bearer token
ea1af86
into
harsharajkumar-273:main
Summary
Going with cookie-based auth, as you picked in the issue thread.
Two things about the suggested remediation that I want to flag, because I didn't
implement it as written and I think both are worth knowing.
app.use('/preview', requireAccessToken, checkWorkspaceOwner, ...)would not haveworked.
checkWorkspaceOwnerreadsreq.params.sessionId, and at mount levelreq.paramsis empty —:sessionIdbelongs to this router's route, not the mountpath. It would hit
if (!login || !sessionId) return next()and allow everythingthrough. The authentication half would have worked and the authorization half would
have been a no-op that looked like a fix. So the check lives on the route instead.
I also didn't reuse
checkWorkspaceOwner. It's fail-open in three other branches:an unknown session, a session with no
creatorLogin, and any bearer-token caller(whose
req.authSessionis null, sologinis undefined). That's reasonable as asupplementary check on routes already guarded elsewhere, but not as the only guard on
this one.
Related Issue
Closes #51
Type of Change
Changes Made
requirePreviewAccessruns onGET /:sessionId/*:id doesn't produce a misleading 401)
No cookie changes were needed.
extractAccessTokenalready reads the session cookiebefore falling back to a bearer token, which is exactly what makes this viable — a
browser can't attach an
Authorizationheader to an iframe navigation, andDemoPage.tsxloads the preview in an iframe. The cookie isSameSite=Lax, and sincecookies are scoped by host rather than origin, it's still sent when the app and API
differ only by port (dev) or subdomain (prod). It would only fail if the backend moved
to a genuinely different registrable domain.
One deliberate compromise, please overrule if you disagree
Build sessions live in an in-memory map, so after any restart every preview on disk
is an orphan with no ownership record. I've allowed those through for any
authenticated user rather than failing closed, because failing closed would break every
preview on every deploy.
The reported hole — anyone on the internet reading a preview without logging in — is
closed either way, and reaching an orphan still needs a correct 16-hex id. But it is
weaker than strict owner-only, so if you'd rather have 403 there, say the word and I'll
change it; it's a one-line difference plus reworking the preview tests, which currently
use a session id with no build record.
AI Usage
Used Claude for coding.
Testing
Three new cases in
server.routes.test.js, all passing:That last one is the one that matters, since the cookie is the only mechanism available
to an iframe. The four existing preview assertions now authenticate rather than
expecting anonymous 200s.
server.routes.test.js: 19 tests, 17 pass, 2 fail. The two arerewrites nested preview asset paths for knowls and shared CSSandserves the Prometheus metrics data(401 ≠ 200), both of which fail identically on aclean
mainand neither of which touches this change.npx tsc --noEmitclean.Checklist
mainSummary by CodeRabbit
New Features
Bug Fixes