Skip to content

Commit 6680c94

Browse files
committed
wip
1 parent 063d7e8 commit 6680c94

3 files changed

Lines changed: 160 additions & 141 deletions

File tree

js/private/node-patches/fs.cjs

Lines changed: 75 additions & 65 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

js/private/node-patches/src/fs.cts

Lines changed: 82 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,18 @@ const PATCHED_FS_METHODS: ReadonlyArray<keyof typeof FsType> = [
5353

5454
/**
5555
* Function that patches the `fs` module to not escape the given roots.
56+
*
57+
* NOTE: internally Node may call back to `fs.*` methods for example
58+
* realpath: https://github.com/nodejs/node/blob/v24.12.0/lib/fs.js#L2927
59+
* https://github.com/nodejs/node/blob/v24.12.0/lib/fs.js#L2951-L2957
60+
*
61+
* writeFile: https://github.com/nodejs/node/blob/v24.12.0/lib/fs.js#L2372
62+
*
63+
* ... many other places.
64+
*
65+
* However in other scenarios such as ESM module resolution it uses internal invocations
66+
* that can not be patched via `fs.*` methods.
67+
*
5668
* @returns a function to undo the patches.
5769
*/
5870
export function patcher(
@@ -132,103 +144,98 @@ export function patcher(
132144
// fs.lstat
133145
// =========================================================================
134146

135-
if (!useInternalLstatPatch) {
136-
fs.lstat = function lstat(...args: Parameters<typeof FsType.lstat>) {
137-
// preserve error when calling function without required callback
138-
if (typeof args[args.length - 1] !== 'function') {
139-
return origLstat(...args)
140-
}
147+
fs.lstat = function lstat(...args: Parameters<typeof FsType.lstat>) {
148+
// preserve error when calling function without required callback
149+
if (typeof args[args.length - 1] !== 'function') {
150+
return origLstat(...args)
151+
}
141152

142-
const cb = once(args[args.length - 1] as Function)
153+
const cb = once(args[args.length - 1] as Function)
143154

144-
// override the callback
145-
args[args.length - 1] = function lstatCb(
146-
err: Error | null,
147-
stats: Stats | BigIntStats
148-
) {
149-
if (err) return cb(err)
155+
// override the callback
156+
args[args.length - 1] = function lstatCb(
157+
err: Error | null,
158+
stats: Stats | BigIntStats
159+
) {
160+
if (err) return cb(err)
150161

151-
if (!stats.isSymbolicLink()) {
152-
// the file is not a symbolic link so there is nothing more to do
153-
return cb(null, stats)
154-
}
162+
if (!stats.isSymbolicLink()) {
163+
// the file is not a symbolic link so there is nothing more to do
164+
return cb(null, stats)
165+
}
155166

156-
args[0] = resolvePathLike(args[0])
167+
args[0] = resolvePathLike(args[0])
157168

158-
if (!canEscape(args[0])) {
159-
// the file can not escaped the sandbox so there is nothing more to do
160-
return cb(null, stats)
161-
}
169+
if (!canEscape(args[0])) {
170+
// the file can not escaped the sandbox so there is nothing more to do
171+
return cb(null, stats)
172+
}
162173

163-
return guardedReadLink(args[0], guardedReadLinkCb)
174+
return guardedReadLink(args[0], guardedReadLinkCb)
164175

165-
function guardedReadLinkCb(str: string) {
166-
if (str != args[0]) {
167-
// there are one or more hops within the guards so there is nothing more to do
168-
return cb(null, stats)
169-
}
176+
function guardedReadLinkCb(str: string) {
177+
if (str != args[0]) {
178+
// there are one or more hops within the guards so there is nothing more to do
179+
return cb(null, stats)
180+
}
170181

171-
// there are no hops so lets report the stats of the real file;
172-
// we can't use origRealPath here since that function calls lstat internally
173-
// which can result in an infinite loop
174-
return unguardedRealPath(args[0], unguardedRealPathCb)
182+
// there are no hops so lets report the stats of the real file;
183+
// we can't use origRealPath here since that function calls lstat internally
184+
// which can result in an infinite loop
185+
return unguardedRealPath(args[0], unguardedRealPathCb)
175186

176-
function unguardedRealPathCb(
177-
err: Error | null,
178-
str?: string
179-
) {
180-
if (err) {
181-
if ((err as any).code === 'ENOENT') {
182-
// broken link so there is nothing more to do
183-
return cb(null, stats)
184-
}
185-
return cb(err)
187+
function unguardedRealPathCb(err: Error | null, str?: string) {
188+
if (err) {
189+
if ((err as any).code === 'ENOENT') {
190+
// broken link so there is nothing more to do
191+
return cb(null, stats)
186192
}
187-
return origLstat(str!, cb)
193+
return cb(err)
188194
}
195+
return origLstat(str!, cb)
189196
}
190197
}
191-
192-
origLstat(...args)
193198
}
194199

195-
fs.lstatSync = function lstatSync(
196-
...args: Parameters<typeof FsType.lstatSync>
197-
) {
198-
const stats = origLstatSync(...args)
200+
origLstat(...args)
201+
}
199202

200-
if (!stats?.isSymbolicLink()) {
201-
// the file is not a symbolic link so there is nothing more to do
202-
return stats
203-
}
203+
fs.lstatSync = function lstatSync(
204+
...args: Parameters<typeof FsType.lstatSync>
205+
) {
206+
const stats = origLstatSync(...args)
204207

205-
args[0] = resolvePathLike(args[0])
208+
if (!stats?.isSymbolicLink()) {
209+
// the file is not a symbolic link so there is nothing more to do
210+
return stats
211+
}
206212

207-
if (!canEscape(args[0])) {
208-
// the file can not escaped the sandbox so there is nothing more to do
209-
return stats
210-
}
213+
args[0] = resolvePathLike(args[0])
211214

212-
const guardedReadLink: string = guardedReadLinkSync(args[0])
213-
if (guardedReadLink != args[0]) {
214-
// there are one or more hops within the guards so there is nothing more to do
215-
return stats
216-
}
215+
if (!canEscape(args[0])) {
216+
// the file can not escaped the sandbox so there is nothing more to do
217+
return stats
218+
}
217219

218-
try {
219-
args[0] = unguardedRealPathSync(args[0])
220+
const guardedReadLink: string = guardedReadLinkSync(args[0])
221+
if (guardedReadLink != args[0]) {
222+
// there are one or more hops within the guards so there is nothing more to do
223+
return stats
224+
}
220225

221-
// there are no hops so lets report the stats of the real file;
222-
// we can't use origRealPathSync here since that function calls lstat internally
223-
// which can result in an infinite loop
224-
return origLstatSync(...args)
225-
} catch (err: any) {
226-
if (err.code === 'ENOENT') {
227-
// broken link so there is nothing more to do
228-
return stats
229-
}
230-
throw err
226+
try {
227+
args[0] = unguardedRealPathSync(args[0])
228+
229+
// there are no hops so lets report the stats of the real file;
230+
// we can't use origRealPathSync here since that function calls lstat internally
231+
// which can result in an infinite loop
232+
return origLstatSync(...args)
233+
} catch (err: any) {
234+
if (err.code === 'ENOENT') {
235+
// broken link so there is nothing more to do
236+
return stats
231237
}
238+
throw err
232239
}
233240
}
234241

js/private/test/node-patches/BUILD.bazel

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,9 @@ babel_bin.babel(
8686
env = {
8787
"NODE_PATCHES_TEST_ESM_LOADER": "1" if useEsmLoader else "",
8888
},
89-
node_options = ["--expose-internals"] if useEsmLoader else [],
89+
node_options = [
90+
# "--inspect-brk"
91+
] + (["--expose-internals"] if useEsmLoader else []),
9092
node_toolchain = toolchain,
9193
patch_node_esm_loader = False,
9294
patch_node_fs = False,

0 commit comments

Comments
 (0)