Repository navigation
fix(server): reject foreign directory hints before location lookup - #59414
romanilyin wants to merge 2 commits into
Conversation
There was a problem hiding this comment.
On Linux this fixes the reported problem, but it also starts rejecting valid POSIX paths that begin with //. I ran the #54617 reproduction from source on the base and on this branch. Drive and UNC hints in the query and the header went from 404 LocationNotFoundError to 400 InvalidRequestError with the right kind/field, and native paths still return 200. However, location[directory]=//tmp/<dir> returned 200 on the base and now returns 400 (inline comment). The new tests pass (20/20) and tsc -b for the server is clean. The Windows server branch is untested here and by you.
Public API: every route behind the location middleware (and the global form, debug eviction, PTY connect and client-MCP routes) can now return 400 InvalidRequestError for location[directory] / x-opencode-directory. InvalidRequestError is added to the protocol client's LocationMiddleware error union. This is backwards compatible for callers sending native paths. Breaking changes:
- Drive and UNC hints to a POSIX server: 404 → 400.
//-prefixed POSIX paths: 200 → 400./-rooted hints to a Windows server: now 400.- A Windows server now normalises hints (e.g.
C:/x→C:\x) before lookup.
Other concerns:
- New dependency: it is published by the PR author and pinned to a prerelease version. On POSIX the outcome comes entirely from the in-tree regexes, so the package mostly matters on Windows, where it rewrites the hint. I'd prefer the small in-tree version you offered.
- Tests:
location.test.tsis large for what it checks. Please trim it to the core cases.
#59162 changes the same code and handles relative paths the opposite way (it rejects them, this keeps them), so the two need reconciling.
| return /^[A-Za-z]:(?:$|[^\\/])/.test(value) | ||
| } | ||
|
|
||
| function hasWindowsUncRoot(value: string) { |
There was a problem hiding this comment.
On a POSIX server // followed by any character is a valid path (Linux treats //tmp/x as /tmp/x). Before this change location[directory]=//tmp/<dir> returned 200. Now it returns 400 "not native to this OpenCode server". Clients that join paths loosely would break. On POSIX I'd only treat backslash UNC (\\server\share) and the //?/ / //./ device forms as foreign, and leave plain //... alone.
There was a problem hiding this comment.
Fixed in 493321a. The adapter now preserves all forward-slash-rooted POSIX/WSL hints, including //tmp, //tmp/x, ///tmp/x and //server/share. Their spelling alone cannot distinguish a POSIX path from a forward-slash UNC representation. Drive roots and backslash UNC roots are still rejected on POSIX. Unit coverage and real HTTP query/header controls cover the regression.
| return raw as Directory | ||
| } | ||
|
|
||
| function parseWindows(raw: string): Directory { |
There was a problem hiding this comment.
On Windows this branch changes behaviour in two ways that no test covers end to end. /-rooted hints are now rejected. Drive/UNC hints are also rewritten through normalize/toWin32 before LocationServiceMap.get, so C:/x and C:\x now share a cache key, and the stored location directory may differ from what the client sent. That needs checking against how sessions and projects store directories on Windows. This is also the only place where the external @romanilyin/canonicalpath package affects the result. If the normalisation stays, an in-tree version would avoid a new runtime dependency on a prerelease package.
There was a problem hiding this comment.
Fixed the identity change in 493321a: Windows validation returns the original hint, discarding the normalized value. Drive casing, separators, UNC spellings, ordinary relative paths and drive-root-relative /... / \... hints are preserved before existing Location handling. This PR no longer introduces key rewriting.
Native Windows / Bun 1.4.4: 35 tests passed across the five affected/regression files, with one existing Windows-only PTY websocket skip. After correcting the HTTP fixture to use the current drive rather than the TEMP drive, its final native run also passed (6 tests / 54 assertions).
I am keeping the pinned MIT @romanilyin/canonicalpath dependency for shared Windows syntax validation and the subsequent stages in #54615. I maintain the package, and keeping one parser lets those stages receive the same fixes instead of maintaining a copied implementation. Identity migration remains separate from this boundary fix.
| expect(ref).toMatchObject({ kind: source === "query" ? "Query" : "Header", field: "location[directory]" }) | ||
| }) | ||
|
|
||
| it.live("rejects foreign directories before all request-based location lookups", () => |
There was a problem hiding this comment.
This test and the two after it boot full servers and loop over 5 endpoints × 4 paths × 2 sources, which is a lot for checking one boundary. One request per route kind (middleware, debug, PTY, MCP) with a single drive path in the query and the header, plus a native-path control, would cover the fix. The requestRef unit tests above already cover the parsing cases.
There was a problem hiding this comment.
Simplified in 493321a: one server fixture instead of three, with 10 rejection requests (five entry points × query/header) instead of 40. Native-path controls and the SDK error assertion reuse that fixture; parsing, exact spelling, precedence and decoding cases stay in unit tests. This retains coverage of each distinct requestRef consumer without repeating the full path matrix over HTTP.
benjaminkincses
left a comment
There was a problem hiding this comment.
hasWindowsUncRoot in server-directory.ts rejects //tmp but lets ///tmp through. same dir on linux, different verdict. the UNC check needs a server segment, not just // plus one char. //server/share is UNC, //tmp is not.
|
Thanks @Dextheking1 — the regression was in the OpenCode adapter. 493321a fixes it and preserves incoming path spelling on Windows as well. On POSIX, even WSL / Bun 1.4.4: 36 tests / 249 assertions passed; the two changed test files also passed 10 repeats (170 tests / 1,140 assertions). Native Windows: 35 tests passed with one existing PTY websocket skip; the final adjusted HTTP fixture was rechecked (6 tests / 54 assertions). Server checks passed with Bun and the pinned TypeScript compiler, and root I have also reduced the HTTP test matrix and kept the canonicalpath dependency for the staged work in #54615. The current scope of #59162 is session create/import body validation; query/header relative hints remain supported here, so the earlier overlap no longer applies. |
Issue for this PR
Closes #54617. Part of #54615. Replaces #54619 after development moved from
v2todev.Type of change
What does this PR do?
Return typed HTTP 400 for Windows drive/backslash-UNC hints sent to a POSIX server, and for invalid directory syntax, before Location lookup. Cover middleware, global forms, debug eviction, PTY connect and MCP client connect.
Preserve native path spelling: POSIX
//...and///..., Windows drive/UNC and drive-root-relative paths, relative hints, query precedence and single decoding. Windows validation discards the normalized value, leaving existing Location/session identity handling unchanged.Keep the pinned MIT dependency
@romanilyin/[email protected]for shared Windows syntax validation. I maintain this package; retaining one implementation lets subsequent path-contract stages in #54615 reuse its fixes instead of maintaining a copied parser. This PR does not migrate stored keys. #59162 separately validates locations in session create/import bodies.How did you verify your code works?
bun run checkpassed (35/35 tasks). The tracked App declaration symlink was temporarily materialized for the root check and its original bytes restored.Visual
Screenshots / recordings
Not applicable; HTTP request validation.
Checklist