@j0hanz/filesystem-mcp 2.8.1 → 2.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +32 -26
- package/dist/cli-help.d.ts.map +1 -1
- package/dist/cli-help.js +10 -1
- package/dist/cli-help.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +10 -3
- package/dist/cli.js.map +1 -1
- package/dist/core/concurrency.d.ts +4 -4
- package/dist/core/concurrency.d.ts.map +1 -1
- package/dist/core/concurrency.js +1 -1
- package/dist/core/concurrency.js.map +1 -1
- package/dist/core/config.d.ts +2 -0
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/config.js.map +1 -1
- package/dist/core/file-uri.d.ts +12 -3
- package/dist/core/file-uri.d.ts.map +1 -1
- package/dist/core/file-uri.js +35 -7
- package/dist/core/file-uri.js.map +1 -1
- package/dist/core/fmt.d.ts +2 -0
- package/dist/core/fmt.d.ts.map +1 -1
- package/dist/core/fmt.js +16 -0
- package/dist/core/fmt.js.map +1 -1
- package/dist/core/fs.d.ts +16 -1
- package/dist/core/fs.d.ts.map +1 -1
- package/dist/core/fs.js +127 -32
- package/dist/core/fs.js.map +1 -1
- package/dist/core/glob.d.ts +8 -0
- package/dist/core/glob.d.ts.map +1 -1
- package/dist/core/glob.js +84 -11
- package/dist/core/glob.js.map +1 -1
- package/dist/core/path-completer.d.ts.map +1 -1
- package/dist/core/path-completer.js +5 -3
- package/dist/core/path-completer.js.map +1 -1
- package/dist/core/path-utils.d.ts +42 -6
- package/dist/core/path-utils.d.ts.map +1 -1
- package/dist/core/path-utils.js +56 -10
- package/dist/core/path-utils.js.map +1 -1
- package/dist/core/path.d.ts +111 -7
- package/dist/core/path.d.ts.map +1 -1
- package/dist/core/path.js +453 -65
- package/dist/core/path.js.map +1 -1
- package/dist/core/read.d.ts +6 -0
- package/dist/core/read.d.ts.map +1 -1
- package/dist/core/read.js +26 -3
- package/dist/core/read.js.map +1 -1
- package/dist/core/schema.d.ts +9 -6
- package/dist/core/schema.d.ts.map +1 -1
- package/dist/core/schema.js +22 -19
- package/dist/core/schema.js.map +1 -1
- package/dist/core/search.d.ts +6 -0
- package/dist/core/search.d.ts.map +1 -1
- package/dist/core/search.js +159 -33
- package/dist/core/search.js.map +1 -1
- package/dist/core/sensitive.d.ts.map +1 -1
- package/dist/core/sensitive.js +64 -15
- package/dist/core/sensitive.js.map +1 -1
- package/dist/core/store.d.ts +1 -0
- package/dist/core/store.d.ts.map +1 -1
- package/dist/core/store.js +3 -0
- package/dist/core/store.js.map +1 -1
- package/dist/core/util.d.ts +6 -1
- package/dist/core/util.d.ts.map +1 -1
- package/dist/core/util.js +8 -1
- package/dist/core/util.js.map +1 -1
- package/dist/resources.d.ts.map +1 -1
- package/dist/resources.js +18 -9
- package/dist/resources.js.map +1 -1
- package/dist/server.js +1 -1
- package/dist/server.js.map +1 -1
- package/dist/tools/create.d.ts.map +1 -1
- package/dist/tools/create.js +121 -43
- package/dist/tools/create.js.map +1 -1
- package/dist/tools/define.d.ts +8 -4
- package/dist/tools/define.d.ts.map +1 -1
- package/dist/tools/define.js +36 -9
- package/dist/tools/define.js.map +1 -1
- package/dist/tools/delete.d.ts.map +1 -1
- package/dist/tools/delete.js +50 -11
- package/dist/tools/delete.js.map +1 -1
- package/dist/tools/diff.js +1 -1
- package/dist/tools/diff.js.map +1 -1
- package/dist/tools/edit.d.ts.map +1 -1
- package/dist/tools/edit.js +14 -12
- package/dist/tools/edit.js.map +1 -1
- package/dist/tools/find-files.d.ts.map +1 -1
- package/dist/tools/find-files.js +15 -9
- package/dist/tools/find-files.js.map +1 -1
- package/dist/tools/list.d.ts.map +1 -1
- package/dist/tools/list.js +138 -86
- package/dist/tools/list.js.map +1 -1
- package/dist/tools/move.d.ts.map +1 -1
- package/dist/tools/move.js +72 -26
- package/dist/tools/move.js.map +1 -1
- package/dist/tools/patch.js +13 -6
- package/dist/tools/patch.js.map +1 -1
- package/dist/tools/read.d.ts.map +1 -1
- package/dist/tools/read.js +114 -45
- package/dist/tools/read.js.map +1 -1
- package/dist/tools/replace-text.d.ts.map +1 -1
- package/dist/tools/replace-text.js +49 -35
- package/dist/tools/replace-text.js.map +1 -1
- package/dist/tools/search-text.d.ts.map +1 -1
- package/dist/tools/search-text.js +15 -33
- package/dist/tools/search-text.js.map +1 -1
- package/dist/tools/stat.d.ts.map +1 -1
- package/dist/tools/stat.js +26 -8
- package/dist/tools/stat.js.map +1 -1
- package/dist/transport/http.d.ts.map +1 -1
- package/dist/transport/http.js +1 -0
- package/dist/transport/http.js.map +1 -1
- package/dist/transport/stdio.js +1 -1
- package/dist/transport/stdio.js.map +1 -1
- package/package.json +5 -5
package/dist/core/fs.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fs.js","sourceRoot":"","sources":["../../src/core/fs.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAGzC,OAAO,EACL,KAAK,IAAI,OAAO,EAChB,EAAE,IAAI,IAAI,EACV,KAAK,IAAI,OAAO,EAChB,KAAK,IAAI,OAAO,EAChB,IAAI,IAAI,MAAM,EACd,OAAO,IAAI,SAAS,EACpB,QAAQ,IAAI,UAAU,EACtB,QAAQ,IAAI,UAAU,EACtB,MAAM,IAAI,QAAQ,EAClB,EAAE,IAAI,IAAI,EACV,KAAK,IAAI,OAAO,EAChB,IAAI,IAAI,MAAM,EACd,MAAM,IAAI,QAAQ,EAClB,SAAS,IAAI,WAAW,GACzB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEzD,OAAO,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC3D,OAAO,EACL,SAAS,EACT,yBAAyB,EACzB,OAAO,EACP,SAAS,EACT,WAAW,EACX,eAAe,GAChB,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,qBAAqB,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AACpE,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAE5C,OAAO,EAAE,aAAa,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AAGvE,OAAO,EACL,sBAAsB,EACtB,eAAe,EACf,eAAe,EACf,mBAAmB,EACnB,aAAa,EACb,iBAAiB,GAClB,MAAM,WAAW,CAAC;AACnB,OAAO,EAAE,kBAAkB,EAAE,MAAM,WAAW,CAAC;AAK/C,iFAAiF;AAEjF;;;;GAIG;AACH,KAAK,UAAU,eAAe,CAAC,SAAoB,EAAE,QAAgB;IACnE,IAAI,SAAS,GAAG,MAAM,SAAS,CAAC,oBAAoB,CAAC,QAAQ,CAAC,CAAC;IAE/D,IAAI,CAAC;QACH,MAAM,KAAK,GAAG,MAAM,OAAO,CAAC,SAAS,CAAC,CAAC;QACvC,IAAI,KAAK,CAAC,cAAc,EAAE,EAAE,CAAC;YAC3B,MAAM,MAAM,GAAG,MAAM,UAAU,CAAC,SAAS,CAAC,CAAC;YAC3C,MAAM,cAAc,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC,CAAC;YACzF,SAAS,GAAG,MAAM,SAAS,CAAC,oBAAoB,CAAC,cAAc,CAAC,CAAC;QACnE,CAAC;IACH,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,CAAC,eAAe,CAAC,KAAK,CAAC,EAAE,CAAC;YAC5B,MAAM,KAAK,CAAC;QACd,CAAC;IACH,CAAC;IAED,OAAO,SAAS,CAAC;AACnB,CAAC;AAED,MAAM,OAAO,GAAG,EAAE,CAAC;AAEnB,MAAM,sBAAsB,GAAG,EAAE,GAAG,IAAI,CAAC;AAEzC,MAAM,OAAO,iBAAiB;IACnB,SAAS,CAAY;IAE9B,YAAY,SAAoB;QAC9B,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;IAC7B,CAAC;IAED;;;;;OAKG;IACH,KAAK,CAAC,oBAAoB,CAAC,MAAkB,EAAE,aAAqB;QAClE,IAAI,MAAc,CAAC;QACnB,IAAI,CAAC;YACH,MAAM,GAAG,MAAM,UAAU,CAAC,iBAAiB,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;QAClE,CAAC;QAAC,MAAM,CAAC;YACP,OAAO;QACT,CAAC;QACD,sDAAsD;QACtD,MAAM,OAAO,GACX,UAAU,CAAC,MAAM,CAAC;YAClB,CAAC,MAAM,CAAC,QAAQ,CAAC,YAAY,CAAC;YAC9B,IAAI,CAAC,SAAS,CAAC,qBAAqB,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC,KAAK,SAAS;YACzE,CAAC,IAAI,CAAC,SAAS,CAAC,WAAW,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC,CAAC;QACrD,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,MAAM,IAAI,OAAO,CACf,SAAS,CAAC,aAAa,EACvB,oFAAoF,EACpF,aAAa,CACd,CAAC;QACJ,CAAC;IACH,CAAC;IAED,yEAAyE;IACzE,KAAK,CAAC,aAAa,CACjB,SAAiB,EACjB,aAAqB,EACrB,MAAoB;QAEpB,MAAM,OAAO,GAAG,MAAM,CAAC,SAAS,EAAE,aAAa,CAAC,CAAC;QACjD,IAAI,MAAkB,CAAC;QACvB,IAAI,CAAC;YACH,MAAM,GAAG,MAAM,SAAS,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;QAC5C,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,2DAA2D;YAC3D,KAAK,OAAO;iBACT,IAAI,CACH,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,EAAE,EACtB,GAAG,EAAE,CAAC,SAAS,CAChB;iBACA,KAAK,CAAC,CAAC,QAAiB,EAAE,EAAE;gBAC3B,MAAM,CAAC,IAAI,CACT,mCAAmC,SAAS,iBAAiB,yBAAyB,CAAC,QAAQ,CAAC,EAAE,CACnG,CAAC;YACJ,CAAC,CAAC,CAAC;YACL,MAAM,KAAK,CAAC;QACd,CAAC;QACD,IAAI,CAAC;YACH,MAAM,IAAI,CAAC,oBAAoB,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC;YACvD,OAAO,MAAM,CAAC;QAChB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,MAAM,CAAC,KAAK,EAAE,CAAC;YACrB,MAAM,KAAK,CAAC;QACd,CAAC;IACH,CAAC;IAED;;;;;OAKG;IACH,KAAK,CAAC,YAAY,CAAI,QAAgB,EAAE,EAA0C;QAChF,MAAM,GAAG,GAAG,MAAM,eAAe,CAAC,IAAI,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;QAC5D,OAAO,YAAY,CAAC,GAAG,EAAE,GAAG,EAAE,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IAC1C,CAAC;IAED,KAAK,CAAC,IAAI,CACR,QAAgB,EAChB,OAA8C;QAE9C,MAAM,SAAS,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC,oBAAoB,CAAC,QAAQ,CAAC,CAAC;QACtE,MAAM,KAAK,GAAG,MAAM,SAAS,CAAC,MAAM,CAAC,SAAS,CAAC,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC;QAClE,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC;IAC9B,CAAC;IAED,KAAK,CAAC,KAAK,CACT,QAAgB,EAChB,OAAkC;QAElC,MAAM,SAAS,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC,qBAAqB,CAAC,QAAQ,CAAC,CAAC;QACvE,MAAM,KAAK,GAAG,MAAM,SAAS,CAAC,OAAO,CAAC,SAAS,CAAC,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC;QACnE,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC;IAC9B,CAAC;IAED,KAAK,CAAC,QAAQ,CACZ,QAAgB,EAChB,OAA0C;QAE1C,8EAA8E;QAC9E,4EAA4E;QAC5E,4EAA4E;QAC5E,MAAM,SAAS,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC,qBAAqB,CAAC,QAAQ,CAAC,CAAC;QACvE,MAAM,GAAG,GAAG,MAAM,UAAU,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;QACjD,MAAM,UAAU,GAAG,MAAM,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC;QACtE,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,CAAC;IACnC,CAAC;IAED,KAAK,CAAC,KAAK,CAAC,QAAgB,EAAE,OAAuC;QACnE,MAAM,SAAS,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC,oBAAoB,CAAC,QAAQ,CAAC,CAAC;QACtE,MAAM,OAAO,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;IACpC,CAAC;IAED,KAAK,CAAC,MAAM,CAAC,OAAe,EAAE,OAAe;QAC3C,2EAA2E;QAC3E,4DAA4D;QAC5D,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC,qBAAqB,CAAC,OAAO,CAAC,CAAC;QACrE,IAAI,QAAQ,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC,oBAAoB,CAAC,OAAO,CAAC,CAAC;QAClE,wEAAwE;QACxE,IAAI,QAAQ,KAAK,QAAQ;YAAE,QAAQ,GAAG,qBAAqB,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;QAC/E,MAAM,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;IACrC,CAAC;IAED,KAAK,CAAC,SAAS,CACb,QAAgB,EAChB,OAAe,EACf,UAAgD,EAAE;QAElD,MAAM,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC;QAC3B,MAAM,SAAS,GAAG,MAAM,eAAe,CAAC,IAAI,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;QAElE,MAAM,UAAU,GAAG,UAAU,EAAE,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QAC/D,MAAM,QAAQ,GAAG,GAAG,SAAS,IAAI,UAAU,MAAM,CAAC;QAElD,uEAAuE;QACvE,0EAA0E;QAC1E,uEAAuE;QACvE,IAAI,YAAgC,CAAC;QACrC,IAAI,CAAC;YACH,YAAY,GAAG,CAAC,MAAM,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,GAAG,KAAK,CAAC;QACxD,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,yEAAyE;YACzE,2EAA2E;YAC3E,0EAA0E;YAC1E,sBAAsB;YACtB,IAAI,CAAC,eAAe,CAAC,KAAK,CAAC,EAAE,CAAC;gBAC5B,MAAM,CAAC,IAAI,CACT,qDAAqD,SAAS,0CAA0C,yBAAyB,CAAC,KAAK,CAAC,EAAE,CAC3I,CAAC;YACJ,CAAC;QACH,CAAC;QAED,IAAI,CAAC;YACH,MAAM,EAAE,cAAc,EAAE,CAAC;YACzB,qEAAqE;YACrE,wEAAwE;YACxE,yEAAyE;YACzE,wDAAwD;YACxD,MAAM,WAAW,CAAC,QAAQ,EAAE,OAAO,EAAE;gBACnC,MAAM;gBACN,IAAI,EAAE,IAAI;gBACV,GAAG,CAAC,YAAY,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,YAAY,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;aAC9D,CAAC,CAAC;YACH,IAAI,YAAY,KAAK,SAAS,EAAE,CAAC;gBAC/B,MAAM,OAAO,CAAC,QAAQ,EAAE,YAAY,CAAC,CAAC;YACxC,CAAC;YACD,wEAAwE;YACxE,mEAAmE;YACnE,yEAAyE;YACzE,sEAAsE;YACtE,MAAM,EAAE,cAAc,EAAE,CAAC;YACzB,MAAM,QAAQ,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC;QACtC,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC;gBACH,MAAM,QAAQ,CAAC,QAAQ,CAAC,CAAC;YAC3B,CAAC;YAAC,OAAO,YAAY,EAAE,CAAC;gBACtB,MAAM,CAAC,IAAI,CACT,gCAAgC,QAAQ,uBAAuB,yBAAyB,CAAC,KAAK,CAAC,MAAM,yBAAyB,CAAC,YAAY,CAAC,EAAE,CAC/I,CAAC;YACJ,CAAC;YACD,MAAM,KAAK,CAAC;QACd,CAAC;QACD,OAAO,EAAE,SAAS,EAAE,CAAC;IACvB,CAAC;IAED;;;;;OAKG;IACH,KAAK,CAAC,UAAU,CACd,QAAgB,EAChB,OAAe,EACf,UAAgD,EAAE;QAElD,MAAM,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC;QAC3B,MAAM,SAAS,GAAG,MAAM,eAAe,CAAC,IAAI,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;QAClE,sEAAsE;QACtE,wEAAwE;QACxE,oEAAoE;QACpE,sEAAsE;QACtE,yEAAyE;QACzE,yEAAyE;QACzE,IAAI,OAAO,GAAG,IAAI,CAAC;QACnB,IAAI,CAAC;YACH,eAAe,CAAC,SAAS,EAAE,MAAM,OAAO,CAAC,SAAS,CAAC,CAAC,CAAC;QACvD,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,eAAe,CAAC,KAAK,CAAC;gBAAE,MAAM,KAAK,CAAC;YACzC,OAAO,GAAG,KAAK,CAAC;QAClB,CAAC;QACD,oEAAoE;QACpE,oEAAoE;QACpE,qEAAqE;QACrE,qEAAqE;QACrE,uEAAuE;QACvE,wEAAwE;QACxE,mEAAmE;QACnE,oEAAoE;QACpE,uEAAuE;QACvE,sEAAsE;QACtE,qBAAqB;QACrB,MAAM,EAAE,cAAc,EAAE,CAAC;QACzB,IAAI,OAAO,GAAG,KAAK,CAAC;QACpB,IAAI,MAAkB,CAAC;QACvB,IAAI,OAAO,EAAE,CAAC;YACZ,MAAM,GAAG,MAAM,MAAM,CAAC,SAAS,EAAE,eAAe,CAAC,CAAC;QACpD,CAAC;aAAM,CAAC;YACN,IAAI,CAAC;gBACH,MAAM,GAAG,MAAM,MAAM,CAAC,SAAS,EAAE,sBAAsB,CAAC,CAAC;gBACzD,OAAO,GAAG,IAAI,CAAC;YACjB,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,IAAI,KAAK,QAAQ;oBAAE,MAAM,KAAK,CAAC;gBAChE,MAAM,GAAG,MAAM,MAAM,CAAC,SAAS,EAAE,eAAe,CAAC,CAAC;YACpD,CAAC;QACH,CAAC;QACD,IAAI,CAAC;YACH,MAAM,EAAE,cAAc,EAAE,CAAC;YACzB,8EAA8E;YAC9E,oDAAoD;YACpD,IAAI,CAAC,OAAO;gBAAE,MAAM,IAAI,CAAC,oBAAoB,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;YAChE,+DAA+D;YAC/D,oEAAoE;YACpE,mEAAmE;YACnE,gEAAgE;YAChE,eAAe;YACf,MAAM,MAAM,CAAC,SAAS,CAAC,OAAO,EAAE,EAAE,MAAM,EAAE,CAAC,CAAC;QAC9C,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,OAAO,EAAE,CAAC;gBACZ,MAAM,QAAQ,CAAC,SAAS,CAAC,CAAC,KAAK,CAAC,CAAC,WAAoB,EAAE,EAAE;oBACvD,MAAM,CAAC,IAAI,CACT,kCAAkC,SAAS,2BAA2B,yBAAyB,CAAC,WAAW,CAAC,EAAE,CAC/G,CAAC;gBACJ,CAAC,CAAC,CAAC;YACL,CAAC;YACD,MAAM,KAAK,CAAC;QACd,CAAC;gBAAS,CAAC;YACT,MAAM,MAAM,CAAC,KAAK,EAAE,CAAC;QACvB,CAAC;QACD,OAAO,EAAE,SAAS,EAAE,CAAC;IACvB,CAAC;IAED;;;;OAIG;IACH,KAAK,CAAC,iBAAiB,CAAC,QAAgB;;;YACtC,MAAY,MAAM,kCAAG,MAAM,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,OAAA,CAAC;YAC/C,MAAM,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,gBAAgB,CAAC,CAAC;YAC9C,MAAM,EAAE,SAAS,EAAE,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,gBAAgB,EAAE,CAAC,CAAC,CAAC;YACxE,OAAO,MAAM,CAAC,QAAQ,CAAC,CAAC,EAAE,SAAS,CAAC,CAAC;;;;;;;;;;;KACtC;IAED;;;;;;;;OAQG;IACH,KAAK,CAAC,cAAc,CAAC,QAAgB;;;YACnC,MAAY,MAAM,kCAAG,MAAM,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,OAAA,CAAC;YAC/C,IAAI,QAAQ,GAAG,CAAC,CAAC;YACjB,IAAI,QAAQ,GAAG,CAAC,CAAC;YACjB,IAAI,UAAU,GAAG,CAAC,CAAC;YACnB,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,sBAAsB,CAAC,CAAC;YACnD,SAAS,CAAC;gBACR,MAAM,EAAE,SAAS,EAAE,GAAG,MAAM,MAAM,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC,EAAE,KAAK,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;gBACtE,IAAI,SAAS,KAAK,CAAC;oBAAE,MAAM;gBAC3B,UAAU,IAAI,SAAS,CAAC;gBACxB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,SAAS,EAAE,CAAC,EAAE,EAAE,CAAC;oBACnC,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;oBAC3B,IAAI,IAAI,KAAK,OAAO;wBAAE,QAAQ,EAAE,CAAC;oBACjC,QAAQ,GAAG,IAAI,CAAC;gBAClB,CAAC;YACH,CAAC;YACD,yEAAyE;YACzE,qEAAqE;YACrE,OAAO,UAAU,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,KAAK,OAAO,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,QAAQ,GAAG,CAAC,CAAC;;;;;;;;;;;KAC9E;IAED,KAAK,CAAC,EAAE,CAAC,QAAgB,EAAE,OAAoC;QAC7D,MAAM,SAAS,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC,qBAAqB,CAAC,QAAQ,CAAC,CAAC;QACvE,MAAM,IAAI,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;IACjC,CAAC;IAED,KAAK,CAAC,KAAK,CAAC,QAAgB,EAAE,OAAuC;QACnE,MAAM,SAAS,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC,qBAAqB,CAAC,QAAQ,CAAC,CAAC;QACvE,MAAM,OAAO,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;IACpC,CAAC;IAED,KAAK,CAAC,EAAE,CACN,MAAc,EACd,WAAmB,EACnB,OAAoC;QAEpC,yEAAyE;QACzE,4DAA4D;QAC5D,MAAM,WAAW,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC,qBAAqB,CAAC,MAAM,CAAC,CAAC;QACvE,MAAM,SAAS,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC,oBAAoB,CAAC,WAAW,CAAC,CAAC;QACzE,MAAM,IAAI,CAAC,WAAW,EAAE,SAAS,EAAE,OAAO,CAAC,CAAC;IAC9C,CAAC;IAED,KAAK,CAAC,QAAQ,CAAC,QAAgB,EAAE,IAAc;QAC7C,IAAI,CAAC,MAAM,EAAE,cAAc,EAAE,CAAC;QAC9B,MAAM,SAAS,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC,oBAAoB,CAAC,QAAQ,CAAC,CAAC;QACtE,MAAM,KAAK,GAAG,MAAM,SAAS,CAAC,MAAM,CAAC,SAAS,CAAC,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC;QAC9D,OAAO,iBAAiB,CAAC,QAAQ,EAAE,SAAS,EAAE,KAAK,EAAE,IAAI,EAAE,GAAG,EAAE,CAC9D,IAAI,CAAC,aAAa,CAAC,SAAS,EAAE,QAAQ,EAAE,IAAI,CAAC,MAAM,CAAC,CACrD,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,gBAAgB,CACpB,QAAgB,EAChB,OAAiD;QAEjD,MAAM,EAAE,KAAK,EAAE,SAAS,EAAE,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;QAChE,MAAM,OAAO,GAAG,kBAAkB,EAAE,CAAC;QACrC,IAAI,KAAK,CAAC,IAAI,GAAG,OAAO,EAAE,CAAC;YACzB,6EAA6E;YAC7E,MAAM,IAAI,OAAO,CACf,SAAS,CAAC,SAAS,EACnB,sBAAsB,OAAO,EAAE,IAAI,IAAI,MAAM,KAAK,KAAK,CAAC,IAAI,YAAY,OAAO,SAAS,EACxF,QAAQ,EACR,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,WAAW,EAAE,OAAO,EAAE,CAC3C,CAAC;QACJ,CAAC;QACD,MAAM,EAAE,OAAO,EAAE,GAAG,MAAM,iBAAiB,CACzC,QAAQ,EACR,SAAS,EACT,KAAK,EACL;YACE,IAAI,EAAE,MAAM;YACZ,UAAU,EAAE,IAAI;YAChB,GAAG,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SACvD,EACD,GAAG,EAAE,CAAC,IAAI,CAAC,aAAa,CAAC,SAAS,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,CAAC,CAC/D,CAAC;QACF,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;IACvC,CAAC;IAED,KAAK,CAAC,OAAO,CACX,QAAgB,EAChB,OAAkC;;;YAElC,OAAO,EAAE,MAAM,EAAE,cAAc,EAAE,CAAC;YAClC,MAAM,SAAS,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC,oBAAoB,CAAC,QAAQ,CAAC,CAAC;YACtE,OAAO,EAAE,MAAM,EAAE,cAAc,EAAE,CAAC;YAClC,MAAM,KAAK,GAAG,MAAM,SAAS,CAAC,MAAM,CAAC,SAAS,CAAC,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC;YAClE,eAAe,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;YACjC,8EAA8E;YAC9E,6DAA6D;YAC7D,MAAM,eAAe,GAAG,kBAAkB,EAAE,CAAC;YAC7C,IAAI,KAAK,CAAC,IAAI,GAAG,eAAe,EAAE,CAAC;gBACjC,MAAM,mBAAmB,CAAC,KAAK,CAAC,IAAI,EAAE,eAAe,EAAE,QAAQ,CAAC,CAAC;YACnE,CAAC;YACD,IAAI,OAAe,CAAC;YACpB,MAAY,MAAM,kCAAG,MAAM,IAAI,CAAC,aAAa,CAAC,SAAS,EAAE,QAAQ,CAAC,OAAA,CAAC;YACnE,IAAI,CAAC;gBACH,OAAO,GAAG,MAAM,UAAU,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;YAC9C,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,8DAA8D;gBAC9D,IAAI,KAAK,YAAY,KAAK,IAAI,KAAK,CAAC,IAAI,KAAK,YAAY,EAAE,CAAC;oBAC1D,OAAO,EAAE,MAAM,EAAE,cAAc,EAAE,CAAC;gBACpC,CAAC;gBACD,MAAM,KAAK,CAAC;YACd,CAAC;YACD,MAAM,QAAQ,GAAG,qBAAqB,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;YAC3D,OAAO;gBACL,OAAO;gBACP,QAAQ,EAAE,QAAQ,CAAC,QAAQ;gBAC3B,QAAQ,EAAE,QAAQ,CAAC,IAAI,KAAK,MAAM;aACnC,CAAC;;;;;;;;;;;KACH;IAED,KAAK,CAAC,IAAI,CAAC,QAAgB;QACzB,OAAO,IAAI,CAAC,aAAa,CAAC,MAAM,IAAI,CAAC,SAAS,CAAC,oBAAoB,CAAC,QAAQ,CAAC,EAAE,QAAQ,CAAC,CAAC;IAC3F,CAAC;IAED,2EAA2E;IAC3E,4EAA4E;IAC5E,yEAAyE;IACzE,gEAAgE;IAChE,KAAK,CAAC,YAAY,CAChB,QAAgB,EAChB,OAAkC;QAElC,MAAM,EAAE,aAAa,EAAE,YAAY,EAAE,SAAS,EAAE,GAC9C,MAAM,IAAI,CAAC,SAAS,CAAC,4BAA4B,CAAC,QAAQ,CAAC,CAAC;QAC9D,MAAM,KAAK,GAAG,MAAM,SAAS,CAAC,MAAM,CAAC,YAAY,CAAC,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC;QACrE,OAAO,EAAE,aAAa,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC;IAC7C,CAAC;IAED,KAAK,CAAC,oBAAoB,CAAC,OAAe;QACxC,MAAM,GAAG,GAAG,MAAM,SAAS,CAAC,OAAO,CAAC,CAAC;QACrC,IAAI,CAAC;YACH,MAAM,KAAK,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC;YAC/B,OAAO,KAAK,KAAK,IAAI,CAAC;QACxB,CAAC;gBAAS,CAAC;YACT,MAAM,GAAG,CAAC,KAAK,EAAE,CAAC,KAAK,CAAC,CAAC,QAAiB,EAAE,EAAE;gBAC5C,MAAM,CAAC,IAAI,CACT,kCAAkC,OAAO,KAAK,yBAAyB,CAAC,QAAQ,CAAC,EAAE,CACpF,CAAC;YACJ,CAAC,CAAC,CAAC;QACL,CAAC;IACH,CAAC;CACF;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU,CAC9B,EAAmC,EACnC,IAAY,EACZ,KAAa;IAEb,IAAI,CAAC;QACH,MAAM,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACpB,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,OAAO,GAAG,eAAe,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,GAAG,CAAC,IAAI,KAAK,SAAS,CAAC,SAAS,CAAC,CAAC;QAC7F,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,MAAM,CAAC,IAAI,CAAC,GAAG,KAAK,wCAAwC,IAAI,MAAM,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QACvF,CAAC;QACD,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC","sourcesContent":["import { randomUUID } from 'node:crypto';\nimport type { Stats } from 'node:fs';\nimport type { FileHandle } from 'node:fs/promises';\nimport {\n chmod as fsChmod,\n cp as fsCp,\n lstat as fsLstat,\n mkdir as fsMkdir,\n open as fsOpen,\n opendir as fsOpendir,\n readFile as fsReadFile,\n readlink as fsReadlink,\n rename as fsRename,\n rm as fsRm,\n rmdir as fsRmdir,\n stat as fsStat,\n unlink as fsUnlink,\n writeFile as fsWriteFile,\n} from 'node:fs/promises';\nimport { dirname, isAbsolute, resolve } from 'node:path';\n\nimport { runExclusive, withAbort } from './concurrency.ts';\nimport {\n ErrorCode,\n formatUnknownErrorMessage,\n FsError,\n isFsError,\n isNodeError,\n isNotFoundErrno,\n} from './errors.ts';\nimport { detectMimeFromContent, MIME_SAMPLE_SIZE } from './mime.ts';\nimport { Logger } from './observability.ts';\nimport type { EntryType as FileType } from './path-utils.ts';\nimport { normalizePath, respellCaseOnlyTarget } from './path-utils.ts';\nimport type { PathGuard } from './path.ts';\nimport type { ReadFileResult, ReadSpec } from './read.ts';\nimport {\n APPEND_CREATE_NOFOLLOW,\n APPEND_NOFOLLOW,\n assertFileStats,\n createTooLargeError,\n READ_NOFOLLOW,\n readFileWithStats,\n} from './read.ts';\nimport { getMaxTextFileSize } from './util.ts';\n\nexport type { FileType };\nexport type { Stats };\n\n// ─── Domain primitives ────────────────────────────────────────────────────────\n\n/**\n * validatePathForWrite + symlink resolution: a write aimed at a link must land\n * on its revalidated target, not create a new file beside the link. ENOENT is\n * the normal new-file case, so it falls through with the original path.\n */\nasync function resolveForWrite(pathGuard: PathGuard, filePath: string): Promise<string> {\n let validPath = await pathGuard.validatePathForWrite(filePath);\n\n try {\n const stats = await fsLstat(validPath);\n if (stats.isSymbolicLink()) {\n const target = await fsReadlink(validPath);\n const resolvedTarget = isAbsolute(target) ? target : resolve(dirname(validPath), target);\n validPath = await pathGuard.validatePathForWrite(resolvedTarget);\n }\n } catch (error) {\n if (!isNotFoundErrno(error)) {\n throw error;\n }\n }\n\n return validPath;\n}\n\nconst CHAR_LF = 10;\n\nconst LINE_COUNT_CHUNK_BYTES = 64 * 1024;\n\nexport class GuardedFileSystem {\n readonly pathGuard: PathGuard;\n\n constructor(pathGuard: PathGuard) {\n this.pathGuard = pathGuard;\n }\n\n /**\n * O_NOFOLLOW only guards the final component; an ANCESTOR directory swapped\n * for a symlink after validation still passes. Re-read the open descriptor's\n * real path (Linux `/proc/self/fd`; skipped elsewhere) and require it to stay\n * inside the roots. Run before any byte is read or written through `handle`.\n */\n async #assertFdWithinRoots(handle: FileHandle, requestedPath: string): Promise<void> {\n let fdPath: string;\n try {\n fdPath = await fsReadlink(`/proc/self/fd/${String(handle.fd)}`);\n } catch {\n return;\n }\n // A replaced target reads back as \"<path> (deleted)\".\n const inRoots =\n isAbsolute(fdPath) &&\n !fdPath.endsWith(' (deleted)') &&\n this.pathGuard.allowedRootContaining(normalizePath(fdPath)) !== undefined &&\n !this.pathGuard.isSensitive(normalizePath(fdPath));\n if (!inRoots) {\n throw new FsError(\n ErrorCode.ACCESS_DENIED,\n 'Path changed under validation and now resolves outside the allowed roots (TOCTOU).',\n requestedPath,\n );\n }\n }\n\n /** The one read open: O_NOFOLLOW, then the descriptor re-check above. */\n async #openVerified(\n validPath: string,\n requestedPath: string,\n signal?: AbortSignal,\n ): Promise<FileHandle> {\n const opening = fsOpen(validPath, READ_NOFOLLOW);\n let handle: FileHandle;\n try {\n handle = await withAbort(opening, signal);\n } catch (error) {\n // An open the signal abandoned still lands; close it then.\n void opening\n .then(\n (late) => late.close(),\n () => undefined,\n )\n .catch((closeErr: unknown) => {\n Logger.warn(\n `Failed to close file handle for ${validPath} after abort: ${formatUnknownErrorMessage(closeErr)}`,\n );\n });\n throw error;\n }\n try {\n await this.#assertFdWithinRoots(handle, requestedPath);\n return handle;\n } catch (error) {\n await handle.close();\n throw error;\n }\n }\n\n /**\n * Serialize a read-modify-write on the resolved write target, so two writers\n * of one file (any spelling, including via a symlinked parent) cannot both\n * read the same base and have the last rename win. `writeFile`/`appendFile`\n * do not take the lock, so `fn` may call them.\n */\n async lockForWrite<T>(filePath: string, fn: (resolvedTarget: string) => Promise<T>): Promise<T> {\n const key = await resolveForWrite(this.pathGuard, filePath);\n return runExclusive(key, () => fn(key));\n }\n\n async stat(\n filePath: string,\n options?: { signal?: AbortSignal | undefined },\n ): Promise<{ stats: Stats; validPath: string }> {\n const validPath = await this.pathGuard.validateExistingPath(filePath);\n const stats = await withAbort(fsStat(validPath), options?.signal);\n return { stats, validPath };\n }\n\n async lstat(\n filePath: string,\n options?: { signal?: AbortSignal },\n ): Promise<{ stats: Stats; validPath: string }> {\n const validPath = await this.pathGuard.validatePathForDelete(filePath);\n const stats = await withAbort(fsLstat(validPath), options?.signal);\n return { stats, validPath };\n }\n\n async readlink(\n filePath: string,\n options?: Parameters<typeof fsReadlink>[1],\n ): Promise<{ linkString: string; validPath: string }> {\n // validateExistingPath resolves through the symlink, so readlink would always\n // be handed the final target — a regular file — and fail EINVAL. This guard\n // keeps the link itself while still enforcing containment and the denylist.\n const validPath = await this.pathGuard.validatePathForDelete(filePath);\n const raw = await fsReadlink(validPath, options);\n const linkString = Buffer.isBuffer(raw) ? raw.toString('utf-8') : raw;\n return { linkString, validPath };\n }\n\n async mkdir(filePath: string, options?: Parameters<typeof fsMkdir>[1]): Promise<void> {\n const validPath = await this.pathGuard.validatePathForWrite(filePath);\n await fsMkdir(validPath, options);\n }\n\n async rename(oldPath: string, newPath: string): Promise<void> {\n // Not validateExistingPath: that resolves through a symlink, so renaming a\n // link would rename its target and leave the link dangling.\n const validOld = await this.pathGuard.validatePathForDelete(oldPath);\n let validNew = await this.pathGuard.validatePathForWrite(newPath);\n // Renaming a path onto itself is only meaningful as a case-only rename.\n if (validOld === validNew) validNew = respellCaseOnlyTarget(newPath, validNew);\n await fsRename(validOld, validNew);\n }\n\n async writeFile(\n filePath: string,\n content: string,\n options: { signal?: AbortSignal | undefined } = {},\n ): Promise<{ validPath: string }> {\n const { signal } = options;\n const validPath = await resolveForWrite(this.pathGuard, filePath);\n\n const tempSuffix = randomUUID().replace(/-/g, '').slice(0, 12);\n const tempPath = `${validPath}.${tempSuffix}.tmp`;\n\n // The rename below swaps in the temp file's inode, so the target would\n // inherit fsWriteFile's default 0o666 & ~umask — silently widening a 0600\n // file to 0644 on every write. Carry the existing mode across instead.\n let existingMode: number | undefined;\n try {\n existingMode = (await fsStat(validPath)).mode & 0o777;\n } catch (error) {\n // ENOENT is the normal new-file case: the default mode is correct there.\n // Anything else (EACCES, EIO) means the mode about to be overwritten could\n // not be read, and the write will silently widen the file — say so rather\n // than swallowing it.\n if (!isNotFoundErrno(error)) {\n Logger.warn(\n `atomicWriteFile: cannot read the existing mode of ${validPath}; the write will use the default mode: ${formatUnknownErrorMessage(error)}`,\n );\n }\n }\n\n try {\n signal?.throwIfAborted();\n // Create the temp file already narrowed to the target's mode: a 0600\n // file's new bytes must never sit at 0644 while the write is in flight.\n // `wx` fails on a name collision instead of overwriting; the chmod below\n // still restores bits the umask masked off at creation.\n await fsWriteFile(tempPath, content, {\n signal,\n flag: 'wx',\n ...(existingMode !== undefined ? { mode: existingMode } : {}),\n });\n if (existingMode !== undefined) {\n await fsChmod(tempPath, existingMode);\n }\n // Last cancellation point. The rename IS the commit: once it starts the\n // target may already be replaced, so it is never raced against the\n // signal — a withAbort race would report a finished write as failed, and\n // a client retry would apply it twice (same reasoning as appendFile).\n signal?.throwIfAborted();\n await fsRename(tempPath, validPath);\n } catch (error) {\n try {\n await fsUnlink(tempPath);\n } catch (cleanupError) {\n Logger.warn(\n `Failed to clean up temp file ${tempPath} after write error (${formatUnknownErrorMessage(error)}): ${formatUnknownErrorMessage(cleanupError)}`,\n );\n }\n throw error;\n }\n return { validPath };\n }\n\n /**\n * Append to the existing file (created if missing). Deliberately NOT the\n * writeFile temp+rename dance: the rename would swap in a fresh inode,\n * and the whole point of appending is to extend the file that's already\n * there — inode and mode survive by construction.\n */\n async appendFile(\n filePath: string,\n content: string,\n options: { signal?: AbortSignal | undefined } = {},\n ): Promise<{ validPath: string }> {\n const { signal } = options;\n const validPath = await resolveForWrite(this.pathGuard, filePath);\n // Non-regular targets must be rejected before the open: 'a' on a FIFO\n // (POSIX) blocks until a reader appears, and fsOpen takes no signal, so\n // the call would hang unabortable and pin its concurrency slot. The\n // overwrite path can't hang — it writes a temp file and renames over.\n // resolveForWrite has already resolved any symlink to its target, so the\n // lstat here doubles as the new-vs-existing probe for the cleanup below.\n let existed = true;\n try {\n assertFileStats(validPath, await fsLstat(validPath));\n } catch (error) {\n if (!isNotFoundErrno(error)) throw error;\n existed = false;\n }\n // fs.promises.appendFile takes no signal, so a withAbort race would\n // report failure while the append still lands — a client retry then\n // appends twice. A handle opened with 'a' gets a genuinely abortable\n // FileHandle.writeFile instead, same as the writeFile path. 'a' on a\n // missing target CREATES it, so: check the signal before the open, and\n // if the call then fails after creating the file, remove what it made —\n // an aborted or errored call must not leave a phantom file behind.\n // The pre-open lstat only proves the file was missing a moment ago:\n // 'ax' makes creation exclusive, so a racer that created it in between\n // fails EEXIST, reopens plainly, and never claims cleanup rights over\n // the winner's file.\n signal?.throwIfAborted();\n let created = false;\n let handle: FileHandle;\n if (existed) {\n handle = await fsOpen(validPath, APPEND_NOFOLLOW);\n } else {\n try {\n handle = await fsOpen(validPath, APPEND_CREATE_NOFOLLOW);\n created = true;\n } catch (error) {\n if (!isNodeError(error) || error.code !== 'EEXIST') throw error;\n handle = await fsOpen(validPath, APPEND_NOFOLLOW);\n }\n }\n try {\n signal?.throwIfAborted();\n // A file we just O_EXCL-created is a fresh regular file; only an existing one\n // can have been reached through a swapped ancestor.\n if (!created) await this.#assertFdWithinRoots(handle, filePath);\n // Ceiling: an append is not transactional, so an abort landing\n // mid-write can leave a prefix of the content on disk. Unwiring the\n // signal would trade that for a silently-completed write; a client\n // that retries is double-appending either way, so the abortable\n // write stays.\n await handle.writeFile(content, { signal });\n } catch (error) {\n if (created) {\n await fsUnlink(validPath).catch((unlinkError: unknown) => {\n Logger.warn(\n `Failed to remove newly created ${validPath} after a failed append: ${formatUnknownErrorMessage(unlinkError)}`,\n );\n });\n }\n throw error;\n } finally {\n await handle.close();\n }\n return { validPath };\n }\n\n /**\n * Leading bytes of a file, for MIME sniffing without loading the whole\n * file — an append's result must describe the resulting file (which may be\n * a multi-GB log), not the chunk that was appended.\n */\n async readLeadingSample(filePath: string): Promise<Buffer> {\n await using handle = await this.open(filePath);\n const buffer = Buffer.alloc(MIME_SAMPLE_SIZE);\n const { bytesRead } = await handle.read(buffer, 0, MIME_SAMPLE_SIZE, 0);\n return buffer.subarray(0, bytesRead);\n }\n\n /**\n * Line count for a file on disk, counting newline bytes in fixed 64 KiB\n * chunks so an arbitrarily large file (one append's result metadata) costs\n * bounded memory. NOT FileHandle.readLines: it decodes each physical line\n * whole, so a multi-GB single-line log OOMs the server after the append has\n * already landed — a reported failure invites a retry that appends twice.\n * Matches read.ts's countLines semantics: a trailing newline does not open\n * a new line, an unterminated final line counts, an empty file is 0.\n */\n async countFileLines(filePath: string): Promise<number> {\n await using handle = await this.open(filePath);\n let newlines = 0;\n let lastByte = 0;\n let totalBytes = 0;\n const chunk = Buffer.alloc(LINE_COUNT_CHUNK_BYTES);\n for (;;) {\n const { bytesRead } = await handle.read(chunk, 0, chunk.length, null);\n if (bytesRead === 0) break;\n totalBytes += bytesRead;\n for (let i = 0; i < bytesRead; i++) {\n const byte = chunk[i] ?? 0;\n if (byte === CHAR_LF) newlines++;\n lastByte = byte;\n }\n }\n // totalBytes is the empty-file sentinel: a NUL (0x00) final byte is real\n // content and must count as an unterminated final line, not \"empty\".\n return totalBytes === 0 ? 0 : lastByte === CHAR_LF ? newlines : newlines + 1;\n }\n\n async rm(filePath: string, options?: Parameters<typeof fsRm>[1]): Promise<void> {\n const validPath = await this.pathGuard.validatePathForDelete(filePath);\n await fsRm(validPath, options);\n }\n\n async rmdir(filePath: string, options?: Parameters<typeof fsRmdir>[1]): Promise<void> {\n const validPath = await this.pathGuard.validatePathForDelete(filePath);\n await fsRmdir(validPath, options);\n }\n\n async cp(\n source: string,\n destination: string,\n options?: Parameters<typeof fsCp>[2],\n ): Promise<void> {\n // As in rename: keep the link itself so callers passing verbatimSymlinks\n // actually copy the link rather than a dereferenced target.\n const validSource = await this.pathGuard.validatePathForDelete(source);\n const validDest = await this.pathGuard.validatePathForWrite(destination);\n await fsCp(validSource, validDest, options);\n }\n\n async readFile(filePath: string, spec: ReadSpec): Promise<ReadFileResult> {\n spec.signal?.throwIfAborted();\n const validPath = await this.pathGuard.validateExistingPath(filePath);\n const stats = await withAbort(fsStat(validPath), spec.signal);\n return readFileWithStats(filePath, validPath, stats, spec, () =>\n this.#openVerified(validPath, filePath, spec.signal),\n );\n }\n\n async readEditableText(\n filePath: string,\n options?: { signal?: AbortSignal; tool?: string },\n ): Promise<{ validPath: string; content: string; stats: Stats }> {\n const { stats, validPath } = await this.stat(filePath, options);\n const maxSize = getMaxTextFileSize();\n if (stats.size > maxSize) {\n // `tool` labels the TOO_LARGE message so the client sees which tool refused.\n throw new FsError(\n ErrorCode.TOO_LARGE,\n `File too large for ${options?.tool ?? 'edit'} (${stats.size} bytes > ${maxSize} bytes)`,\n filePath,\n { size: stats.size, maxFileSize: maxSize },\n );\n }\n const { content } = await readFileWithStats(\n filePath,\n validPath,\n stats,\n {\n kind: 'full',\n strictUtf8: true,\n ...(options?.signal ? { signal: options.signal } : {}),\n },\n () => this.#openVerified(validPath, filePath, options?.signal),\n );\n return { validPath, content, stats };\n }\n\n async readRaw(\n filePath: string,\n options?: { signal?: AbortSignal },\n ): Promise<{ content: Buffer; mimeType: string; isBinary: boolean }> {\n options?.signal?.throwIfAborted();\n const validPath = await this.pathGuard.validateExistingPath(filePath);\n options?.signal?.throwIfAborted();\n const stats = await withAbort(fsStat(validPath), options?.signal);\n assertFileStats(filePath, stats);\n // Enforce size limit before reading to avoid loading large files into memory.\n // Binary detection is best-effort, but this is a hard limit.\n const maxTextFileSize = getMaxTextFileSize();\n if (stats.size > maxTextFileSize) {\n throw createTooLargeError(stats.size, maxTextFileSize, filePath);\n }\n let content: Buffer;\n await using handle = await this.#openVerified(validPath, filePath);\n try {\n content = await fsReadFile(handle, options);\n } catch (error) {\n // Node wraps even a TimeoutError signal reason in AbortError.\n if (error instanceof Error && error.name === 'AbortError') {\n options?.signal?.throwIfAborted();\n }\n throw error;\n }\n const mimeInfo = detectMimeFromContent(validPath, content);\n return {\n content,\n mimeType: mimeInfo.mimeType,\n isBinary: mimeInfo.kind !== 'text',\n };\n }\n\n async open(filePath: string): Promise<FileHandle> {\n return this.#openVerified(await this.pathGuard.validateExistingPath(filePath), filePath);\n }\n\n // Single resolution + stat: validateExistingPathDetailed resolves the real\n // path (following symlinks, re-checking sensitivity) once, then we stat the\n // resolved target. Replaces the tool-side validateExistingPathDetailed +\n // fs.stat pair that re-validated through the free stat() guard.\n async statDetailed(\n filePath: string,\n options?: { signal?: AbortSignal },\n ): Promise<{ requestedPath: string; isSymlink: boolean; stats: Stats }> {\n const { requestedPath, resolvedPath, isSymlink } =\n await this.pathGuard.validateExistingPathDetailed(filePath);\n const stats = await withAbort(fsStat(resolvedPath), options?.signal);\n return { requestedPath, isSymlink, stats };\n }\n\n async hasChildrenUnchecked(dirPath: string): Promise<boolean> {\n const dir = await fsOpendir(dirPath);\n try {\n const entry = await dir.read();\n return entry !== null;\n } finally {\n await dir.close().catch((closeErr: unknown) => {\n Logger.warn(\n `Failed to close dir handle for ${dirPath}: ${formatUnknownErrorMessage(closeErr)}`,\n );\n });\n }\n }\n}\n\n/**\n * Returns whether `path` exists, treating \"not found\" as absent and logging any\n * other stat failure. Used for TOCTOU re-checks between plan and execute phases.\n */\nexport async function destExists(\n fs: Pick<GuardedFileSystem, 'stat'>,\n path: string,\n label: string,\n): Promise<boolean> {\n try {\n await fs.stat(path);\n return true;\n } catch (err) {\n const missing = isNotFoundErrno(err) || (isFsError(err) && err.code === ErrorCode.NOT_FOUND);\n if (!missing) {\n Logger.warn(`${label}: dest stat failed unexpectedly for \"${path}\": ${String(err)}`);\n }\n return false;\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"fs.js","sourceRoot":"","sources":["../../src/core/fs.ts"],"names":[],"mappings":"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","sourcesContent":["import { randomUUID } from 'node:crypto';\nimport { constants, existsSync } from 'node:fs';\nimport type { Dir, Dirent, Stats } from 'node:fs';\nimport type { FileHandle } from 'node:fs/promises';\nimport {\n chmod as fsChmod,\n cp as fsCp,\n lstat as fsLstat,\n mkdir as fsMkdir,\n open as fsOpen,\n opendir as fsOpendir,\n readFile as fsReadFile,\n readlink as fsReadlink,\n rename as fsRename,\n rm as fsRm,\n rmdir as fsRmdir,\n stat as fsStat,\n unlink as fsUnlink,\n writeFile as fsWriteFile,\n} from 'node:fs/promises';\nimport { isAbsolute, join } from 'node:path';\n\nimport { runExclusive, withAbort } from './concurrency.ts';\nimport {\n ErrorCode,\n formatUnknownErrorMessage,\n FsError,\n isFsError,\n isNodeError,\n isNotFoundErrno,\n} from './errors.ts';\nimport { detectMimeFromContent, type FileKind, MIME_SAMPLE_SIZE } from './mime.ts';\nimport { Logger } from './observability.ts';\nimport type { EntryType as FileType } from './path-utils.ts';\nimport { isSamePath, normalizePath, respellSpellingOnlyTarget } from './path-utils.ts';\nimport type { PathGuard } from './path.ts';\nimport type { ReadFileResult, ReadSpec } from './read.ts';\nimport {\n APPEND_CREATE_NOFOLLOW,\n APPEND_NOFOLLOW,\n assertFileStats,\n createTooLargeError,\n O_NOFOLLOW,\n READ_NOFOLLOW,\n readFileWithStats,\n} from './read.ts';\nimport { getMaxTextFileSize } from './util.ts';\n\nexport type { FileType };\nexport type { Stats };\n\n// Directory open for countEntries. O_NOFOLLOW refuses a symlink named as the\n// directory; used only under COUNT_THROUGH_FD, like the re-check it feeds.\nconst DIR_NOFOLLOW = constants.O_RDONLY | constants.O_DIRECTORY | O_NOFOLLOW;\n// The descriptor re-check and the read through /proc/self/fd need Linux with procfs mounted,\n// as the read path's re-check does; elsewhere the count reads the directory by path.\nconst COUNT_THROUGH_FD = process.platform === 'linux' && existsSync('/proc/self/fd');\n\n// ─── Domain primitives ────────────────────────────────────────────────────────\n\nconst CHAR_LF = 10;\n\nconst LINE_COUNT_CHUNK_BYTES = 64 * 1024;\n\nexport class GuardedFileSystem {\n readonly pathGuard: PathGuard;\n\n constructor(pathGuard: PathGuard) {\n this.pathGuard = pathGuard;\n }\n\n /**\n * O_NOFOLLOW only guards the final component; an ANCESTOR directory swapped\n * for a symlink after validation still passes. Re-read the open descriptor's\n * real path (Linux `/proc/self/fd`; skipped elsewhere) and require it to stay\n * inside the roots. Run before any byte is read or written through `handle`.\n * Resolves to the normalized real path checked, or undefined when /proc is unreadable.\n */\n async #assertFdWithinRoots(\n handle: FileHandle,\n requestedPath: string,\n ): Promise<string | undefined> {\n let fdPath: string;\n try {\n fdPath = await fsReadlink(`/proc/self/fd/${String(handle.fd)}`);\n } catch {\n return undefined;\n }\n // A replaced target reads back as \"<path> (deleted)\".\n const inRoots =\n isAbsolute(fdPath) &&\n !fdPath.endsWith(' (deleted)') &&\n this.pathGuard.allowedRootContaining(normalizePath(fdPath)) !== undefined &&\n !this.pathGuard.isSensitive(normalizePath(fdPath));\n if (!inRoots) {\n throw new FsError(\n ErrorCode.ACCESS_DENIED,\n 'Path changed under validation and now resolves outside the allowed roots (TOCTOU).',\n requestedPath,\n );\n }\n return normalizePath(fdPath);\n }\n\n /** The one read open: O_NOFOLLOW, then the descriptor re-check above. */\n async #openVerified(\n validPath: string,\n requestedPath: string,\n signal?: AbortSignal,\n ): Promise<FileHandle> {\n const opening = fsOpen(validPath, READ_NOFOLLOW);\n let handle: FileHandle;\n try {\n handle = await withAbort(opening, signal);\n } catch (error) {\n // An open the signal abandoned still lands; close it then.\n void opening\n .then(\n (late) => late.close(),\n () => undefined,\n )\n .catch((closeErr: unknown) => {\n Logger.warn(\n `Failed to close file handle for ${validPath} after abort: ${formatUnknownErrorMessage(closeErr)}`,\n );\n });\n throw error;\n }\n try {\n await this.#assertFdWithinRoots(handle, requestedPath);\n return handle;\n } catch (error) {\n await handle.close();\n throw error;\n }\n }\n\n /**\n * Waits for `pending` until `signal` fires. A result that lands after the abort goes to\n * `release` without anyone waiting on it, so a stalled open or read never holds the caller.\n */\n async #abandonOnAbort<T>(\n pending: Promise<T>,\n signal: AbortSignal | undefined,\n release: (late: T) => Promise<unknown>,\n label: string,\n ): Promise<T> {\n try {\n return await withAbort(pending, signal);\n } catch (error) {\n void pending\n .then(release, () => undefined)\n .catch((releaseErr: unknown) => {\n Logger.warn(\n `Failed to release ${label} after abort: ${formatUnknownErrorMessage(releaseErr)}`,\n );\n });\n throw error;\n }\n }\n\n /**\n * Serialize a read-modify-write on the resolved write target, so two writers\n * of one file (any spelling, including via a symlinked parent) cannot both\n * read the same base and have the last rename win. `writeFile`/`appendFile`\n * do not take the lock, so `fn` may call them.\n */\n async lockForWrite<T>(filePath: string, fn: (resolvedTarget: string) => Promise<T>): Promise<T> {\n const key = await this.pathGuard.validatePathForWrite(filePath);\n return runExclusive(key, () => fn(key));\n }\n\n async stat(\n filePath: string,\n options?: { signal?: AbortSignal | undefined },\n ): Promise<{ stats: Stats; validPath: string }> {\n const validPath = await this.pathGuard.validateExistingPath(filePath);\n const stats = await withAbort(fsStat(validPath), options?.signal);\n return { stats, validPath };\n }\n\n async lstat(\n filePath: string,\n options?: { signal?: AbortSignal | undefined },\n ): Promise<{ stats: Stats; validPath: string }> {\n const validPath = await this.pathGuard.validatePathForDelete(filePath);\n const stats = await withAbort(fsLstat(validPath), options?.signal);\n return { stats, validPath };\n }\n\n async readlink(\n filePath: string,\n options?: Parameters<typeof fsReadlink>[1],\n ): Promise<{ linkString: string; validPath: string }> {\n // validateExistingPath resolves through the symlink, so readlink would always\n // be handed the final target — a regular file — and fail EINVAL. This guard\n // keeps the link itself while still enforcing containment and the denylist.\n const validPath = await this.pathGuard.validatePathForDelete(filePath);\n const raw = await fsReadlink(validPath, options);\n const linkString = Buffer.isBuffer(raw) ? raw.toString('utf-8') : raw;\n return { linkString, validPath };\n }\n\n async mkdir(filePath: string, options?: Parameters<typeof fsMkdir>[1]): Promise<void> {\n const validPath = await this.pathGuard.validatePathForWrite(filePath);\n await fsMkdir(validPath, options);\n }\n\n async rename(oldPath: string, newPath: string): Promise<void> {\n // Not validateExistingPath: that resolves through a symlink, so renaming a\n // link would rename its target and leave the link dangling.\n const validOld = await this.pathGuard.validatePathForDelete(oldPath);\n let validNew = await this.pathGuard.validatePathForWrite(newPath);\n // A new path naming the old entry itself, in another case or Unicode\n // spelling, renames that entry. Probed without following a final link:\n // validatePathForWrite resolved a link to its target, and renaming onto\n // that would replace the target with the link.\n const sameEntry = await this.pathGuard.validatePathForDelete(newPath).then(\n (entry) => isSamePath(entry, validOld),\n () => false,\n );\n if (sameEntry || validOld === validNew) {\n validNew = respellSpellingOnlyTarget(newPath, sameEntry ? validOld : validNew);\n }\n await fsRename(validOld, validNew);\n }\n\n async writeFile(\n filePath: string,\n content: string,\n options: { signal?: AbortSignal | undefined } = {},\n ): Promise<{ validPath: string }> {\n const { signal } = options;\n const validPath = await this.pathGuard.validatePathForWrite(filePath);\n\n const tempSuffix = randomUUID().replace(/-/g, '').slice(0, 12);\n const tempPath = `${validPath}.${tempSuffix}.tmp`;\n\n // The rename below swaps in the temp file's inode, so the target would\n // inherit fsWriteFile's default 0o666 & ~umask — silently widening a 0600\n // file to 0644 on every write. Carry the existing mode across instead.\n let existingMode: number | undefined;\n try {\n existingMode = (await fsStat(validPath)).mode & 0o777;\n } catch (error) {\n // ENOENT is the normal new-file case: the default mode is correct there.\n // Anything else (EACCES, EIO) means the mode about to be overwritten could\n // not be read, and the write will silently widen the file — say so rather\n // than swallowing it.\n if (!isNotFoundErrno(error)) {\n Logger.warn(\n `atomicWriteFile: cannot read the existing mode of ${validPath}; the write will use the default mode: ${formatUnknownErrorMessage(error)}`,\n );\n }\n }\n\n try {\n signal?.throwIfAborted();\n // Create the temp file already narrowed to the target's mode: a 0600\n // file's new bytes must never sit at 0644 while the write is in flight.\n // `wx` fails on a name collision instead of overwriting; the chmod below\n // still restores bits the umask masked off at creation.\n await fsWriteFile(tempPath, content, {\n signal,\n flag: 'wx',\n ...(existingMode !== undefined ? { mode: existingMode } : {}),\n });\n if (existingMode !== undefined) {\n await fsChmod(tempPath, existingMode);\n }\n // Last cancellation point. The rename IS the commit: once it starts the\n // target may already be replaced, so it is never raced against the\n // signal — a withAbort race would report a finished write as failed, and\n // a client retry would apply it twice (same reasoning as appendFile).\n signal?.throwIfAborted();\n await fsRename(tempPath, validPath);\n } catch (error) {\n try {\n await fsUnlink(tempPath);\n } catch (cleanupError) {\n Logger.warn(\n `Failed to clean up temp file ${tempPath} after write error (${formatUnknownErrorMessage(error)}): ${formatUnknownErrorMessage(cleanupError)}`,\n );\n }\n throw error;\n }\n return { validPath };\n }\n\n /**\n * Append to the existing file (created if missing). Deliberately NOT the\n * writeFile temp+rename dance: the rename would swap in a fresh inode,\n * and the whole point of appending is to extend the file that's already\n * there — inode and mode survive by construction.\n */\n async appendFile(\n filePath: string,\n content: string,\n options: { signal?: AbortSignal | undefined } = {},\n ): Promise<{ validPath: string }> {\n const { signal } = options;\n const validPath = await this.pathGuard.validatePathForWrite(filePath);\n // Non-regular targets must be rejected before the open: 'a' on a FIFO\n // (POSIX) blocks until a reader appears, and fsOpen takes no signal, so\n // the call would hang unabortable and pin its concurrency slot. The\n // overwrite path can't hang — it writes a temp file and renames over.\n // resolveForWrite has already resolved any symlink to its target, so the\n // lstat here doubles as the new-vs-existing probe for the cleanup below.\n let existed = true;\n try {\n assertFileStats(validPath, await fsLstat(validPath));\n } catch (error) {\n if (!isNotFoundErrno(error)) throw error;\n existed = false;\n }\n // fs.promises.appendFile takes no signal, so a withAbort race would\n // report failure while the append still lands — a client retry then\n // appends twice. A handle opened with 'a' gets a genuinely abortable\n // FileHandle.writeFile instead, same as the writeFile path. 'a' on a\n // missing target CREATES it, so: check the signal before the open, and\n // if the call then fails after creating the file, remove what it made —\n // an aborted or errored call must not leave a phantom file behind.\n // The pre-open lstat only proves the file was missing a moment ago:\n // 'ax' makes creation exclusive, so a racer that created it in between\n // fails EEXIST, reopens plainly, and never claims cleanup rights over\n // the winner's file.\n signal?.throwIfAborted();\n let created = false;\n let handle: FileHandle;\n if (existed) {\n handle = await fsOpen(validPath, APPEND_NOFOLLOW);\n } else {\n try {\n handle = await fsOpen(validPath, APPEND_CREATE_NOFOLLOW);\n created = true;\n } catch (error) {\n if (!isNodeError(error) || error.code !== 'EEXIST') throw error;\n handle = await fsOpen(validPath, APPEND_NOFOLLOW);\n }\n }\n try {\n signal?.throwIfAborted();\n // A file we just O_EXCL-created is a fresh regular file; only an existing one\n // can have been reached through a swapped ancestor.\n if (!created) await this.#assertFdWithinRoots(handle, filePath);\n // Ceiling: an append is not transactional, so an abort landing\n // mid-write can leave a prefix of the content on disk. Unwiring the\n // signal would trade that for a silently-completed write; a client\n // that retries is double-appending either way, so the abortable\n // write stays.\n await handle.writeFile(content, { signal });\n } catch (error) {\n if (created) {\n await fsUnlink(validPath).catch((unlinkError: unknown) => {\n Logger.warn(\n `Failed to remove newly created ${validPath} after a failed append: ${formatUnknownErrorMessage(unlinkError)}`,\n );\n });\n }\n throw error;\n } finally {\n await handle.close();\n }\n return { validPath };\n }\n\n /**\n * Leading bytes of a file, for MIME sniffing without loading the whole\n * file — an append's result must describe the resulting file (which may be\n * a multi-GB log), not the chunk that was appended.\n */\n async readLeadingSample(filePath: string): Promise<Buffer> {\n await using handle = await this.open(filePath);\n const buffer = Buffer.alloc(MIME_SAMPLE_SIZE);\n const { bytesRead } = await handle.read(buffer, 0, MIME_SAMPLE_SIZE, 0);\n return buffer.subarray(0, bytesRead);\n }\n\n /**\n * Line count for a file on disk, counting newline bytes in fixed 64 KiB\n * chunks so an arbitrarily large file (one append's result metadata) costs\n * bounded memory. NOT FileHandle.readLines: it decodes each physical line\n * whole, so a multi-GB single-line log OOMs the server after the append has\n * already landed — a reported failure invites a retry that appends twice.\n * Matches read.ts's countLines semantics: a trailing newline does not open\n * a new line, an unterminated final line counts, an empty file is 0.\n */\n async countFileLines(filePath: string): Promise<number> {\n await using handle = await this.open(filePath);\n let newlines = 0;\n let lastByte = 0;\n let totalBytes = 0;\n const chunk = Buffer.alloc(LINE_COUNT_CHUNK_BYTES);\n for (;;) {\n const { bytesRead } = await handle.read(chunk, 0, chunk.length, null);\n if (bytesRead === 0) break;\n totalBytes += bytesRead;\n for (let i = 0; i < bytesRead; i++) {\n const byte = chunk[i] ?? 0;\n if (byte === CHAR_LF) newlines++;\n lastByte = byte;\n }\n }\n // totalBytes is the empty-file sentinel: a NUL (0x00) final byte is real\n // content and must count as an unterminated final line, not \"empty\".\n return totalBytes === 0 ? 0 : lastByte === CHAR_LF ? newlines : newlines + 1;\n }\n\n async rm(filePath: string, options?: Parameters<typeof fsRm>[1]): Promise<void> {\n const validPath = await this.pathGuard.validatePathForDelete(filePath);\n await fsRm(validPath, options);\n }\n\n async rmdir(filePath: string, options?: Parameters<typeof fsRmdir>[1]): Promise<void> {\n const validPath = await this.pathGuard.validatePathForDelete(filePath);\n await fsRmdir(validPath, options);\n }\n\n async cp(\n source: string,\n destination: string,\n options?: Parameters<typeof fsCp>[2],\n ): Promise<void> {\n // As in rename: keep the link itself so callers passing verbatimSymlinks\n // actually copy the link rather than a dereferenced target.\n const validSource = await this.pathGuard.validatePathForDelete(source);\n const validDest = await this.pathGuard.validatePathForWrite(destination);\n await fsCp(validSource, validDest, options);\n }\n\n async readFile(filePath: string, spec: ReadSpec): Promise<ReadFileResult> {\n spec.signal?.throwIfAborted();\n const validPath = await this.pathGuard.validateExistingPath(filePath);\n const stats = await withAbort(fsStat(validPath), spec.signal);\n return readFileWithStats(filePath, validPath, stats, spec, () =>\n this.#openVerified(validPath, filePath, spec.signal),\n );\n }\n\n async readEditableText(\n filePath: string,\n options?: { signal?: AbortSignal; tool?: string },\n ): Promise<{ validPath: string; content: string; stats: Stats }> {\n const { stats, validPath } = await this.stat(filePath, options);\n const maxSize = getMaxTextFileSize();\n if (stats.size > maxSize) {\n // `tool` labels the TOO_LARGE message so the client sees which tool refused.\n throw new FsError(\n ErrorCode.TOO_LARGE,\n `File too large for ${options?.tool ?? 'edit'} (${stats.size} bytes > ${maxSize} bytes)`,\n filePath,\n { size: stats.size, maxFileSize: maxSize },\n );\n }\n const { content } = await readFileWithStats(\n filePath,\n validPath,\n stats,\n {\n kind: 'full',\n strictUtf8: true,\n ...(options?.signal ? { signal: options.signal } : {}),\n },\n () => this.#openVerified(validPath, filePath, options?.signal),\n );\n return { validPath, content, stats };\n }\n\n async readRaw(\n filePath: string,\n options?: { signal?: AbortSignal },\n ): Promise<{\n content: Buffer;\n mimeType: string;\n kind: FileKind;\n isBinary: boolean;\n validPath: string;\n modified: string;\n }> {\n options?.signal?.throwIfAborted();\n const validPath = await this.pathGuard.validateExistingPath(filePath);\n options?.signal?.throwIfAborted();\n const stats = await withAbort(fsStat(validPath), options?.signal);\n assertFileStats(filePath, stats);\n // Enforce size limit before reading to avoid loading large files into memory.\n // Binary detection is best-effort, but this is a hard limit.\n const maxTextFileSize = getMaxTextFileSize();\n if (stats.size > maxTextFileSize) {\n throw createTooLargeError(stats.size, maxTextFileSize, filePath);\n }\n let content: Buffer;\n await using handle = await this.#openVerified(validPath, filePath);\n try {\n content = await fsReadFile(handle, options);\n } catch (error) {\n // Node wraps even a TimeoutError signal reason in AbortError.\n if (error instanceof Error && error.name === 'AbortError') {\n options?.signal?.throwIfAborted();\n }\n throw error;\n }\n const mimeInfo = detectMimeFromContent(validPath, content);\n return {\n content,\n mimeType: mimeInfo.mimeType,\n kind: mimeInfo.kind,\n isBinary: mimeInfo.kind !== 'text',\n validPath,\n modified: stats.mtime.toISOString(),\n };\n }\n\n async open(filePath: string): Promise<FileHandle> {\n return this.#openVerified(await this.pathGuard.validateExistingPath(filePath), filePath);\n }\n\n // Single resolution + stat: validateExistingPathDetailed resolves the real\n // path (following symlinks, re-checking sensitivity) once, then we stat the\n // resolved target. Replaces the tool-side validateExistingPathDetailed +\n // fs.stat pair that re-validated through the free stat() guard.\n async statDetailed(\n filePath: string,\n options?: { signal?: AbortSignal },\n ): Promise<{ requestedPath: string; isSymlink: boolean; stats: Stats }> {\n const { requestedPath, resolvedPath, isSymlink } =\n await this.pathGuard.validateExistingPathDetailed(filePath);\n const stats = await withAbort(fsStat(resolvedPath), options?.signal);\n return { requestedPath, isSymlink, stats };\n }\n\n async hasChildrenUnchecked(dirPath: string): Promise<boolean> {\n const dir = await fsOpendir(dirPath);\n try {\n const entry = await dir.read();\n return entry !== null;\n } finally {\n await dir.close().catch((closeErr: unknown) => {\n Logger.warn(\n `Failed to close dir handle for ${dirPath}: ${formatUnknownErrorMessage(closeErr)}`,\n );\n });\n }\n }\n\n /**\n * Direct children of a directory, leaving out entries the guard treats as\n * sensitive so the count never reveals what listings hide. Hidden and\n * ignored entries count: this answers \"is it empty?\", not \"what would list show?\".\n * A symlinked child counts only when list would show it. The path validation, open, opendir,\n * each read and each link check race the signal, so a stalled mount ends the call at its\n * deadline.\n */\n async countEntries(dirPath: string, options?: { signal?: AbortSignal }): Promise<number> {\n const signal = options?.signal;\n // Refuse an already-cancelled call before any validation or I/O.\n signal?.throwIfAborted();\n // A late validation result is a plain object, so a stalled realpath needs no release.\n const { resolvedPath } = await withAbort(\n this.pathGuard.validateExistingPathDetailed(dirPath),\n signal,\n );\n // With procfs (COUNT_THROUGH_FD), open the directory itself with O_NOFOLLOW and check where\n // the open descriptor really is, so a directory or ancestor swapped for a symlink after\n // validation is refused. Then read entries through the descriptor, which a later swap cannot\n // redirect.\n const handle = COUNT_THROUGH_FD\n ? await this.#abandonOnAbort(\n fsOpen(resolvedPath, DIR_NOFOLLOW),\n signal,\n (late) => late.close(),\n `directory descriptor for ${resolvedPath}`,\n )\n : undefined;\n let dir: Dir | undefined;\n let pendingRead: Promise<Dirent | null> | undefined;\n try {\n const openedPath = handle ? await this.#assertFdWithinRoots(handle, dirPath) : undefined;\n // An in-root swap passes the roots check, but the children would be filtered under the\n // validated directory's spelling, not the one actually opened.\n if (openedPath !== undefined && !isSamePath(openedPath, resolvedPath)) {\n throw new FsError(\n ErrorCode.ACCESS_DENIED,\n 'Path changed under validation and now resolves to another directory (TOCTOU).',\n dirPath,\n );\n }\n const readPath = handle ? `/proc/self/fd/${String(handle.fd)}` : resolvedPath;\n dir = await this.#abandonOnAbort(\n fsOpendir(readPath),\n signal,\n (late) => late.close(),\n `directory stream for ${resolvedPath}`,\n );\n let count = 0;\n for (;;) {\n // Each read races the signal, so a read that stalls ends the call at its deadline.\n const read = dir.read();\n pendingRead = read;\n const entry = await withAbort(read, signal);\n pendingRead = undefined;\n if (entry === null) break;\n if (this.pathGuard.isSensitive(join(dirPath, entry.name))) continue;\n // list walks the real directory, so it checks every child, links\n // included, under its real spelling: a deny rule naming the real\n // directory hides the child under an alias too.\n if (this.pathGuard.isSensitive(join(resolvedPath, entry.name))) continue;\n // A link counts only when list would show it: same rule, same error policy.\n if (\n entry.isSymbolicLink() &&\n !(await withAbort(\n this.pathGuard.isEntryAccessible(join(resolvedPath, entry.name)),\n signal,\n ))\n ) {\n continue;\n }\n count += 1;\n }\n // A deadline that fired during the last read still ends the call, even with no entries left.\n signal?.throwIfAborted();\n return count;\n } finally {\n if (dir) {\n const stream = dir;\n if (pendingRead) {\n // close() queues behind a read still in flight, so awaiting it would hold the TIMEOUT\n // response on a stalled mount. Close once that read settles, without waiting for it.\n void pendingRead\n .then(\n () => stream.close(),\n () => stream.close(),\n )\n .catch((closeErr: unknown) => {\n Logger.warn(\n `Failed to close dir handle for ${resolvedPath} after abort: ${formatUnknownErrorMessage(closeErr)}`,\n );\n });\n } else {\n await stream.close().catch((closeErr: unknown) => {\n Logger.warn(\n `Failed to close dir handle for ${resolvedPath}: ${formatUnknownErrorMessage(closeErr)}`,\n );\n });\n }\n }\n // A failed close must not replace the error the count threw, so it is logged, not thrown.\n await handle?.close().catch((closeErr: unknown) => {\n Logger.warn(\n `Failed to close dir descriptor for ${resolvedPath}: ${formatUnknownErrorMessage(closeErr)}`,\n );\n });\n }\n }\n}\n\n/**\n * Returns whether `path` exists, treating \"not found\" as absent and logging any\n * other stat failure. Used for TOCTOU re-checks between plan and execute phases.\n */\nexport async function destExists(\n fs: Pick<GuardedFileSystem, 'stat'>,\n path: string,\n label: string,\n): Promise<boolean> {\n try {\n await fs.stat(path);\n return true;\n } catch (err) {\n const missing = isNotFoundErrno(err) || (isFsError(err) && err.code === ErrorCode.NOT_FOUND);\n if (!missing) {\n Logger.warn(`${label}: dest stat failed unexpectedly for \"${path}\": ${String(err)}`);\n }\n return false;\n }\n}\n"]}
|
package/dist/core/glob.d.ts
CHANGED
|
@@ -7,6 +7,14 @@ import type { DirentLike } from './path-utils.ts';
|
|
|
7
7
|
export declare const MAX_BRACE_ALTERNATIVES = 1024;
|
|
8
8
|
/** Bound synchronous glob compilation when checking candidate traversal segments. */
|
|
9
9
|
export declare const MAX_DOTDOT_CHECK_CHARS = 4096;
|
|
10
|
+
/**
|
|
11
|
+
* fs.glob tests each name against a regex compiled from the segment,
|
|
12
|
+
* synchronously. Every run of `*` and every extglob group (`?(`, `+(`, `@(`,
|
|
13
|
+
* `!(`, `*(`) adds a degree of backtracking: on a 255-character name three cost
|
|
14
|
+
* under 100 ms, four about 1 s, and five do not finish; a chain of `?(a|aa)`
|
|
15
|
+
* groups grows the same way.
|
|
16
|
+
*/
|
|
17
|
+
export declare const MAX_SEGMENT_WILDCARDS = 3;
|
|
10
18
|
export declare function isSafeGlobSyntax(pattern: string): boolean;
|
|
11
19
|
export interface GlobEntry {
|
|
12
20
|
path: string;
|
package/dist/core/glob.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"glob.d.ts","sourceRoot":"","sources":["../../src/core/glob.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAIlD;;;;GAIG;AACH,eAAO,MAAM,sBAAsB,OAAO,CAAC;AAE3C,qFAAqF;AACrF,eAAO,MAAM,sBAAsB,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"glob.d.ts","sourceRoot":"","sources":["../../src/core/glob.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAIlD;;;;GAIG;AACH,eAAO,MAAM,sBAAsB,OAAO,CAAC;AAE3C,qFAAqF;AACrF,eAAO,MAAM,sBAAsB,OAAO,CAAC;AAE3C;;;;;;GAMG;AACH,eAAO,MAAM,qBAAqB,IAAI,CAAC;AAqKvC,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAyBzD;AA8ND,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,UAAU,CAAC;CACpB;AAED,MAAM,WAAW,kBAAkB;IACjC,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC;IAChB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,kFAAkF;IAClF,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB;;;;OAIG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,4EAA4E;IAC5E,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAwPD,wBAAuB,WAAW,CAAC,OAAO,EAAE,kBAAkB,GAAG,cAAc,CAAC,SAAS,CAAC,CAyBzF"}
|
package/dist/core/glob.js
CHANGED
|
@@ -14,6 +14,14 @@ import { MIB } from "./util.js";
|
|
|
14
14
|
export const MAX_BRACE_ALTERNATIVES = 1024;
|
|
15
15
|
/** Bound synchronous glob compilation when checking candidate traversal segments. */
|
|
16
16
|
export const MAX_DOTDOT_CHECK_CHARS = 4096;
|
|
17
|
+
/**
|
|
18
|
+
* fs.glob tests each name against a regex compiled from the segment,
|
|
19
|
+
* synchronously. Every run of `*` and every extglob group (`?(`, `+(`, `@(`,
|
|
20
|
+
* `!(`, `*(`) adds a degree of backtracking: on a 255-character name three cost
|
|
21
|
+
* under 100 ms, four about 1 s, and five do not finish; a chain of `?(a|aa)`
|
|
22
|
+
* groups grows the same way.
|
|
23
|
+
*/
|
|
24
|
+
export const MAX_SEGMENT_WILDCARDS = 3;
|
|
17
25
|
// The range bodies fs.glob's brace expansion recognises, as it writes them.
|
|
18
26
|
const NUMERIC_RANGE_RE = /^-?\d+\.\.-?\d+(?:\.\.-?\d+)?$/u;
|
|
19
27
|
const ALPHA_RANGE_RE = /^[a-zA-Z]\.\.[a-zA-Z](?:\.\.-?\d+)?$/u;
|
|
@@ -123,6 +131,60 @@ const isTraversalSegment = (segment) => {
|
|
|
123
131
|
*/
|
|
124
132
|
const isRooted = (alternative) => win32.parse(alternative).root !== '' ||
|
|
125
133
|
(IS_WINDOWS && (alternative.split('/')[0] ?? '').endsWith(':'));
|
|
134
|
+
/**
|
|
135
|
+
* Index of the `)` that closes the group opened at `open`, or -1. A `[...]`
|
|
136
|
+
* class is skipped whole: a `)` or `|` inside one is a literal, not syntax.
|
|
137
|
+
*/
|
|
138
|
+
function groupClose(segment, open) {
|
|
139
|
+
let depth = 0;
|
|
140
|
+
for (let i = open; i < segment.length; i++) {
|
|
141
|
+
const c = segment[i];
|
|
142
|
+
if (c === '[') {
|
|
143
|
+
// A `]` first in the class (or after `!`/`^`) is literal, so search past it.
|
|
144
|
+
const first = segment[i + 1] === '!' || segment[i + 1] === '^' ? i + 2 : i + 1;
|
|
145
|
+
const end = segment.indexOf(']', first + 1);
|
|
146
|
+
if (end !== -1) {
|
|
147
|
+
i = end;
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
if (c === '(')
|
|
152
|
+
depth++;
|
|
153
|
+
else if (c === ')' && --depth === 0)
|
|
154
|
+
return i;
|
|
155
|
+
}
|
|
156
|
+
return -1;
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Whether matching this brace-free segment can backtrack for seconds on one
|
|
160
|
+
* long name. Two shapes do: a repeated group (`*(…)`, `+(…)`) whose body can
|
|
161
|
+
* match more than one way (`|`, `*`, or a nested group), and more than
|
|
162
|
+
* MAX_SEGMENT_WILDCARDS `*` runs and extglob groups together.
|
|
163
|
+
*/
|
|
164
|
+
function canBacktrack(segment) {
|
|
165
|
+
if (segment === '**')
|
|
166
|
+
return false;
|
|
167
|
+
let wildcards = (segment.match(/\*+/gu) ?? []).length;
|
|
168
|
+
for (let i = segment.indexOf('('); i !== -1; i = segment.indexOf('(', i + 1)) {
|
|
169
|
+
const opener = segment[i - 1];
|
|
170
|
+
if (opener === undefined || !'*+?@!'.includes(opener))
|
|
171
|
+
continue;
|
|
172
|
+
// Each group can match more than one way, so it adds a degree of
|
|
173
|
+
// backtracking like a `*` run does (`*(` is already counted as one).
|
|
174
|
+
if (opener !== '*')
|
|
175
|
+
wildcards++;
|
|
176
|
+
if (opener !== '*' && opener !== '+')
|
|
177
|
+
continue;
|
|
178
|
+
const close = groupClose(segment, i);
|
|
179
|
+
// Classes are literals: only `|`, `*` or a group outside one can backtrack.
|
|
180
|
+
const body = segment
|
|
181
|
+
.slice(i + 1, close === -1 ? undefined : close)
|
|
182
|
+
.replace(/\[[!^]?\]?[^\]]*\]/gu, '');
|
|
183
|
+
if (/[|*(]/u.test(body))
|
|
184
|
+
return true;
|
|
185
|
+
}
|
|
186
|
+
return wildcards > MAX_SEGMENT_WILDCARDS;
|
|
187
|
+
}
|
|
126
188
|
export function isSafeGlobSyntax(pattern) {
|
|
127
189
|
if (!pattern || pattern.trim().length === 0) {
|
|
128
190
|
return false;
|
|
@@ -145,6 +207,8 @@ export function isSafeGlobSyntax(pattern) {
|
|
|
145
207
|
}
|
|
146
208
|
let dotdotCheckChars = 0;
|
|
147
209
|
for (const segment of segments) {
|
|
210
|
+
if (canBacktrack(segment))
|
|
211
|
+
return false;
|
|
148
212
|
if (!segment.includes('.') || !DOTDOT_SPELLING_RE.test(segment))
|
|
149
213
|
continue;
|
|
150
214
|
dotdotCheckChars += segment.length;
|
|
@@ -220,6 +284,7 @@ class GitignoreManager {
|
|
|
220
284
|
matchers = new Map();
|
|
221
285
|
/** Rules from directories above the walk root, farthest first; `prefix` re-bases a walk-relative path to that directory. */
|
|
222
286
|
ancestors = [];
|
|
287
|
+
dirCache = new Map();
|
|
223
288
|
addMatcher(dir, matcher) {
|
|
224
289
|
this.matchers.set(dir, matcher);
|
|
225
290
|
}
|
|
@@ -279,12 +344,20 @@ class GitignoreManager {
|
|
|
279
344
|
if (!part)
|
|
280
345
|
continue;
|
|
281
346
|
currentDir = currentDir ? `${currentDir}/${part}` : part;
|
|
282
|
-
if (this.
|
|
347
|
+
if (this.checkDir(currentDir)) {
|
|
283
348
|
return true;
|
|
284
349
|
}
|
|
285
350
|
}
|
|
286
351
|
// 2. Check the file/directory itself
|
|
287
|
-
return this.checkPath(normalized,
|
|
352
|
+
return isDirectory ? this.checkDir(normalized) : this.checkPath(normalized, false);
|
|
353
|
+
}
|
|
354
|
+
checkDir(dir) {
|
|
355
|
+
const cached = this.dirCache.get(dir);
|
|
356
|
+
if (cached !== undefined)
|
|
357
|
+
return cached;
|
|
358
|
+
const res = this.checkPath(dir, true);
|
|
359
|
+
this.dirCache.set(dir, res);
|
|
360
|
+
return res;
|
|
288
361
|
}
|
|
289
362
|
checkPath(posixPath, isDirectory) {
|
|
290
363
|
const parts = posixPath.split('/');
|
|
@@ -363,12 +436,12 @@ function splitPatternPrefix(normalizedPattern) {
|
|
|
363
436
|
}
|
|
364
437
|
/**
|
|
365
438
|
* The segment matching the dot-prefixed names `segment` would match if fs.glob
|
|
366
|
-
* did not skip them, or `undefined` when there are none. Only a leading `*` or
|
|
367
|
-
*
|
|
439
|
+
* did not skip them, or `undefined` when there are none. Only a leading `*` or `?`
|
|
440
|
+
* stands for that dot: a literal never does, and a character class or a
|
|
368
441
|
* brace set is left as written rather than parsed.
|
|
369
442
|
*/
|
|
370
443
|
function dotVariant(segment) {
|
|
371
|
-
// `*(…)` and
|
|
444
|
+
// `*(…)`, `?(…)` and `!(…)` are extglobs, not wildcards.
|
|
372
445
|
if (segment[1] === '(')
|
|
373
446
|
return undefined;
|
|
374
447
|
if (segment.startsWith('*'))
|
|
@@ -500,11 +573,11 @@ function createWalkFilter(cwd, skipIgnored, gitignoreMatcher, maxDepth, signal)
|
|
|
500
573
|
isExcluded: (match) => excluded(relativeOf(match), match.isDirectory()),
|
|
501
574
|
};
|
|
502
575
|
}
|
|
503
|
-
async function*
|
|
576
|
+
async function* processGlobPatterns(patterns, plan, seen, onlyFiles, filter, signal) {
|
|
504
577
|
const { cwd, maxDepth, suppressErrors } = plan;
|
|
505
578
|
let iterable;
|
|
506
579
|
try {
|
|
507
|
-
iterable = fsGlob(
|
|
580
|
+
iterable = fsGlob([...patterns], {
|
|
508
581
|
cwd,
|
|
509
582
|
exclude: filter.prune,
|
|
510
583
|
withFileTypes: true,
|
|
@@ -530,7 +603,7 @@ async function* processGlobPattern(pattern, plan, seen, onlyFiles, filter, signa
|
|
|
530
603
|
catch (error) {
|
|
531
604
|
if (!suppressErrors)
|
|
532
605
|
throw error;
|
|
533
|
-
Logger.warn(`globEntries: suppressed mid-walk error for pattern "${
|
|
606
|
+
Logger.warn(`globEntries: suppressed mid-walk error for pattern "${patterns.join(', ')}": ${formatUnknownErrorMessage(error)}`);
|
|
534
607
|
}
|
|
535
608
|
// A pruned walk ends like a finished one. Say it was cut short, so no caller
|
|
536
609
|
// reads "the walk ended" as "the whole tree was seen".
|
|
@@ -544,9 +617,9 @@ export async function* globEntries(options) {
|
|
|
544
617
|
const seen = new Set();
|
|
545
618
|
const onlyFiles = options.onlyFiles ?? true;
|
|
546
619
|
const filter = createWalkFilter(plan.cwd, options.skipIgnored ?? false, gitignoreMatcher, plan.maxDepth, options.signal);
|
|
547
|
-
for
|
|
548
|
-
|
|
549
|
-
|
|
620
|
+
// One walk for every variant: fs.glob reads each directory once and matches
|
|
621
|
+
// all of them, where a walk per variant re-read the tree for each.
|
|
622
|
+
yield* processGlobPatterns(plan.patterns, plan, seen, onlyFiles, filter, options.signal);
|
|
550
623
|
}
|
|
551
624
|
/** Names a `skipIgnored` walk never enters or yields, at any depth. */
|
|
552
625
|
const DEFAULT_EXCLUDED_NAMES = new Set([
|