fix(core): reject invalid UTF-8 directory paths in serve - #38314
Conversation
base64Decode silently produced U+FFFD replacement characters for invalid UTF-8 bytes, which then poisoned workspace directories and broke the web UI. Use TextDecoder fatal mode and fall back to cwd when paths contain U+FFFD.
|
Thanks for your contribution! This PR doesn't have a linked issue. All PRs must reference an existing issue. Please:
See CONTRIBUTING.md for details. |
|
Thanks for updating your PR! It now meets our contributing guidelines. 👍 |
|
Hi @extencil, I am also experiencing this error and I tried your branch but still see the error :( |
|
1.16.2 version is working for me - Just want to add in possible investigation. |
|
Thanks for this fix. I hit the same bug and want to share a precise repro + a gap this PR doesn't cover. Repro (no malformed URL needed): opening Gap: this PR only guards the server (fatal decoder + cwd fallbacks). The client-side Suggested one-line client-side complement: export function decodeDirectory(dir: string): ProjectDirString | undefined {
const decoded = decode64(dir)
if (!decoded) return
if (base64Encode(decoded) !== dir) return // round-trip check, same pattern as requireServerKey()
return ProjectDirString.make(decoded)
}Verified on 1.18.16 and 1.17.20 (Windows, web UI). Happy to open a separate PR for the app-layer part if useful. |
|
Thanks for checking @cuddly-guacamole ! I tested this one line addition and I can confirm this works for me. |
Issue for this PR
Closes #38235
Closes #37764
Type of change
What does this PR do?
While running
opencode serve, I got a session whose directory ended with two U+FFFD replacement characters:.../bin/��. The file picker rejected that path, andprompt_asyncfailed becauseFileSystem.realPathtried to resolve it and gotENOENT.I could reproduce the same path shape with a malformed route segment. For example,
__8decodes to the bytesFF FF, and the non-fatalTextDecoderturns those into��. The app uses that as the directory, and the SDK sends it as%EF%BF%BD%EF%BF%BDinx-opencode-directory.InstanceContextMiddlewaredecodes the header, thenInstanceStorecallspath.resolve()on the relative��value and ends up with<cwd>/��. That matches what showed up in my log.I do not have the original browser URL from that run, so
__8is only a minimal reproduction of the bug, not a claim that it was the exact route segment. The database also had older session directories with literal U+FFFD characters, so values like this had already been persisted before.The fix makes
base64Decodeuse a fatal UTF-8 decoder. The app'sdecode64wrapper already handles decode errors, so malformed route segments now go through the existing invalid-route behavior. For values that already contain U+FFFD, the HTTP routing, location, decoded instance context, andInstanceStore.load/reloadpaths fall back toprocess.cwd()instead of initializing that directory.How did you verify your code works?
bun test test/server/httpapi-workspace-routing.test.ts test/project/instance.test.ts: 18 passed, 0 failedbun turbo typecheck: 30 tasks passedbun run script/build.ts --single --skip-embed-web-ui --skip-install: built the Linux x64 binary and passed its--versionsmoke test__8->��->%EF%BF%BD%EF%BF%BD-><cwd>/��with the old decoder, then confirmed the fatal decoder rejects the same bytesI reproduced the original failure with
./opencode serve --log-level DEBUG --pure --print-logs. I have not rerun that browser flow after adding the final instance-loading guard.Screenshots / recordings
N/A
Checklist