fix(doc): deny symlink escapes on /api/doc reads - #1437
Open
bendrucker wants to merge 2 commits into
Open
Conversation
`/api/doc` checked containment lexically only, so a symlink planted inside an allowed root resolved to an in-root path and was then read through to its target. All six content vectors leaked, plus the existence oracle on `/api/doc/exists`. Authorization was spread across 11 call sites in four branches of `handleDoc`, each interleaving resolve, authorize and read, and `apps/pi-extension/server/reference.ts` is a hand-maintained clone carrying the same sites and the same hole. Split resolve from read so authorization sits on one seam, in a shared module both runtimes consume. Resolution may stat but never reads, and the four render branches collapse to a document reader and a code reader, which also erases the missing size cap on the HTML branch rather than fixing it twice. Deliberate behavior changes: escaping symlinks are denied on every branch in both runtimes; the HTML branch enforces the 2MB cap; roots are realpath-normalized so a root reachable through a symlink resolves under either spelling. Claude-Session: https://claude.ai/code/session_01HcwNnm6LtBMewU4BzNiRnM
Drop prose that restates the adjacent code and keep the facts it carried: that resolveDocTarget's result is unauthorized, and why the annotate version endpoints derive their history slug from the contained path. Claude-Session: https://claude.ai/code/session_01HcwNnm6LtBMewU4BzNiRnM
Owner
|
Heads up while you are actively on this: the branch is conflicting with main, which moved substantially today (a large header rework in packages/editor and packages/review-editor, plus new code-nav endpoints in both server runtimes), and the conflict is blocking CI from running on your latest commits. A rebase onto current main will unblock checks. The doc-read path you are hardening was not restructured, so the rebase should be mostly mechanical. Once it is green we will get you a full review promptly; this fix is wanted. |
Author
|
No problem will rebase now! |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Consolidates file handling for the review UIs into one shared module. Fixes symlinks breaking project folder containment and reading arbitrary paths on disk.
Approach
Resolving a path and reading it are now separate steps, so the permission check can run between them.
Resolving may
stata path to see whether it exists. It now checks the realpath and requires that to be inside the project in addition to the input path.realpathfails on a path that does not exist, and the check still has to judge those, since an outside path must be refused whether or not the file is there. So it resolves the deepest part of the path that does exist and re-attaches the rest.That catches a missing file under a symlinked folder without turning an ordinary missing file into a denial. Anything unexpected back from
realpathdenies the request.Behavior Changes
base.tmpdir()is itself a symlink and normalizing one side breaks every test on a Mac.basedirectory that reaches outside the project through a symlink is also refused.Tests
Each way a symlink can escape has its own test on both servers, since a single test covering one of them passes against a fix that missed a branch. Also covers a project root named through a symlink, an outside path that does not exist, and the HTML size limit.