Repository navigation
fix(fs): 修复文件系统服务器的路径越界检查漏判同级目录 - #4397
Merged
Merged
Conversation
packages/fs/src/index.ts 的 isInProject 用 path.normalize(join(url)).startsWith(dirname) 判断路径是否在项目内,这是纯前缀匹配。join 就是 path.join(dirname, url),而 path.join 会先把 .. 规约掉,所以 dirname 为 /a/noname 时,url 传 ../noname-backup/x 得到的是 /a/noname-backup/x,它以 /a/noname 开头,检查通过。 结果是所有走 ensureSafe 的路由(/readFile、/readFileAsText、/getFileList、/checkFile、/checkDir、/createDir、/writeFile、/removeFile、/removeDir)都能读写到 dirname 同级、且名字以 dirname 为前缀的目录。../../../etc/hosts 这类结果不以 dirname 开头的越界一直是拦得住的。 改为先 path.resolve 拿到规约后的绝对路径,再要求它等于 dirname 或以 dirname 加路径分隔符开头;dirname 本身是根目录时不重复补分隔符。
rintim
approved these changes
Oct 3, 2026
rintim
pushed a commit
that referenced
this pull request
Oct 15, 2026
(cherry picked from commit 4df78f4)
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.
变更内容
packages/fs/src/index.ts的isInProject改成先path.resolve拿到规约后的绝对路径,再要求它等于dirname或以dirname + path.sep开头。原来是纯前缀匹配。
join就是path.join(dirname, url),而path.join会先把..规约掉,所以dirname为/a/noname时,url传../noname-backup/flag.txt得到/a/noname-backup/flag.txt,这个字符串以/a/noname开头,startsWith返回 true,ensureSafe放行。../../../etc/hosts这类结果不以dirname开头的越界一直是拦得住的,漏掉的只有“同级且名字以dirname为前缀”的目录。所有走
ensureSafe的路由都受影响:/readFile、/readFileAsText、/getFileList、/checkFile、/checkDir、/createDir、/writeFile、/removeFile、/removeDir。@fastify/cors配的是origin: "*",所以浏览器里任何页面都能对本机这个服务器发这些请求。验证方式
写了个脚本直接
importpackages/fs/src/index.ts起服务器,dirname指向临时目录probe/root,同级放一个probe/root-secret,再用 fetch 打 13 个用例:7 个目录内的正常操作(读根目录文件、读子目录文件、dir=空串列根目录、dir=.列根目录、建子目录、写子目录文件、checkFile)和 6 个越界(../../../etc/hosts,以及同级目录的读文件、列目录、checkFile、建目录、写文件)。同一份脚本在main和本 PR 上各跑一次:mainmain上失败的 5 个全是同级目录那 5 条,都返回success: true,../root-secret/pwned.txt也真的被写出来了。../../../etc/hosts两边都被拦住,7 个正常操作两边都通过。又按
pnpm dev的方式起了真实的开发服务器(pnpm -F @noname/fs dev --dirname=../../apps/core)配 vite(pnpm -F noname dev):客户端的启动探针/checkFile?fileName=noname.js返回"file",/index.html是 200,/getFileList?dir=character正常列出全部武将包,经 vite 代理的/writeFile→/readFileAsText→/removeFile一整轮存档读写正常。同时../electron/package.json和临时造的同前缀目录apps/core-x/leak.txt都被拦住了。Lint 按 CI 的命令跑的,
packages/fs、apps/core、apps/electron三个包全过:pnpm start没跑:本地是稀疏检出,image/、audio/这些资源目录没拉下来,pnpm build出不了完整的 dist。pnpm serve跑的就是@noname/fs,上面已经用apps/core当dirname直接起过同一个服务器了。