diff --git a/.gitattributes b/.gitattributes
index e693f6782..7c0a76dc8 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -6,4 +6,5 @@ js/private/coverage/coverage.js linguist-generated=true
js/private/devserver/js_run_devserver.mjs linguist-generated=true
js/private/watch/aspect_watch_protocol.mjs linguist-generated=true
js/private/watch/aspect_watch_protocol.d.mts linguist-generated=true
+js/private/fs.*.cjs linguist-generated=true
js/private/js_image_layer.mjs linguist-generated=true
diff --git a/.prettierignore b/.prettierignore
index 59e391e51..84c376e1e 100644
--- a/.prettierignore
+++ b/.prettierignore
@@ -6,6 +6,7 @@ examples/**/*-docs.md
js/private/coverage/coverage.js
js/private/devserver/js_run_devserver.mjs
js/private/node-patches/fs.cjs
+js/private/node-patches/fs_stat.cjs
js/private/watch/aspect_watch_protocol.mjs
js/private/watch/aspect_watch_protocol.d.mts
min/
diff --git a/MODULE.bazel b/MODULE.bazel
index 538c8c71d..4bde9792a 100644
--- a/MODULE.bazel
+++ b/MODULE.bazel
@@ -13,7 +13,7 @@ bazel_dep(name = "aspect_tools_telemetry", version = "0.2.8")
bazel_dep(name = "bazel_features", version = "1.9.0")
bazel_dep(name = "bazel_skylib", version = "1.5.0")
bazel_dep(name = "platforms", version = "0.0.5")
-bazel_dep(name = "rules_nodejs", version = "6.3.0")
+bazel_dep(name = "rules_nodejs", version = "6.4.0")
tel = use_extension("@aspect_tools_telemetry//:extension.bzl", "telemetry")
use_repo(tel, "aspect_tools_telemetry_report")
diff --git a/js/private/js_binary.bzl b/js/private/js_binary.bzl
index d49d1e235..18d1924ec 100644
--- a/js/private/js_binary.bzl
+++ b/js/private/js_binary.bzl
@@ -204,6 +204,13 @@ _ATTRS = {
which can lead to non-hermetic behavior.""",
default = True,
),
+ "patch_node_esm_loader": attr.bool(
+ doc = """Apply the internal lstat patch to prevent the program from following symlinks out of
+ the execroot, runfiles and the sandbox even when using the ESM loader.
+
+ This flag only has an effect when `patch_node_fs` is True.""",
+ default = True,
+ ),
"include_sources": attr.bool(
doc = """When True, `sources` from `JsInfo` providers in `data` targets are included in the runfiles of the target.""",
default = True,
@@ -319,7 +326,10 @@ _ATTRS = {
"_windows_constraint": attr.label(default = "@platforms//os:windows"),
"_node_patches_files": attr.label_list(
allow_files = True,
- default = [Label("@aspect_rules_js//js/private/node-patches:fs.cjs")],
+ default = [
+ Label("@aspect_rules_js//js/private/node-patches:fs.cjs"),
+ Label("@aspect_rules_js//js/private/node-patches:fs_stat.cjs"),
+ ],
),
"_node_patches": attr.label(
allow_single_file = True,
@@ -564,11 +574,18 @@ def _create_launcher(ctx, log_prefix_rule_set, log_prefix_rule, fixed_args = [],
)
def _js_binary_impl(ctx):
+ # Only apply lstat patch if it's requested
+ JS_BINARY__PATCH_NODE_ESM_LOADER = "1" if ctx.attr.patch_node_esm_loader else "0"
+ fixed_env = {
+ "JS_BINARY__PATCH_NODE_ESM_LOADER": JS_BINARY__PATCH_NODE_ESM_LOADER,
+ }
+
launcher = _create_launcher(
ctx,
log_prefix_rule_set = "aspect_rules_js",
log_prefix_rule = "js_test" if ctx.attr.testonly else "js_binary",
fixed_args = ctx.attr.fixed_args,
+ fixed_env = fixed_env,
)
runfiles = launcher.runfiles
diff --git a/js/private/node-patches/BUILD.bazel b/js/private/node-patches/BUILD.bazel
index d6af777ee..ae23a883f 100644
--- a/js/private/node-patches/BUILD.bazel
+++ b/js/private/node-patches/BUILD.bazel
@@ -4,10 +4,12 @@ write_source_files(
name = "checked_in_compile",
files = {
"fs.cjs": "//js/private/node-patches/src:fs-generated.cjs",
+ "fs_stat.cjs": "//js/private/node-patches/src:fs_stat.cjs",
},
)
exports_files([
"fs.cjs",
+ "fs_stat.cjs",
"register.cjs",
])
diff --git a/js/private/node-patches/fs.cjs b/js/private/node-patches/fs.cjs
index 3b7a267bd..38137fe72 100644
--- a/js/private/node-patches/fs.cjs
+++ b/js/private/node-patches/fs.cjs
@@ -39,6 +39,7 @@ var __asyncGenerator = (this && this.__asyncGenerator) || function (thisArg, _ar
Object.defineProperty(exports, "__esModule", { value: true });
exports.patcher = patcher;
exports.isSubPath = isSubPath;
+exports.resolvePathLike = resolvePathLike;
exports.escapeFunction = escapeFunction;
const path = require("path");
const util = require("util");
@@ -65,7 +66,7 @@ const PATCHED_FS_METHODS = [
* Function that patches the `fs` module to not escape the given roots.
* @returns a function to undo the patches.
*/
-function patcher(roots) {
+function patcher(roots, useInternalLstatPatch = false) {
if (fs._unpatched) {
throw new Error('FS is already patched.');
}
@@ -100,82 +101,93 @@ function patcher(roots) {
.native;
const { canEscape, isEscape } = escapeFunction(roots);
// =========================================================================
+ // fsInternal.lstat (to patch ESM resolve's `realpathSync`!)
+ // =========================================================================
+ let unpatchEsm;
+ if (useInternalLstatPatch) {
+ const lstatEsmPatcher = new (require('./fs_stat.cjs').FsInternalStatPatcher)({ canEscape, isEscape }, guardedReadLink, guardedReadLinkSync, unguardedRealPath, unguardedRealPathSync);
+ lstatEsmPatcher.patch();
+ unpatchEsm = lstatEsmPatcher.revert.bind(lstatEsmPatcher);
+ }
+ // =========================================================================
// fs.lstat
// =========================================================================
- fs.lstat = function lstat(...args) {
- // preserve error when calling function without required callback
- if (typeof args[args.length - 1] !== 'function') {
- return origLstat(...args);
- }
- const cb = once(args[args.length - 1]);
- // override the callback
- args[args.length - 1] = function lstatCb(err, stats) {
- if (err)
- return cb(err);
- if (!stats.isSymbolicLink()) {
+ if (!useInternalLstatPatch) {
+ fs.lstat = function lstat(...args) {
+ // preserve error when calling function without required callback
+ if (typeof args[args.length - 1] !== 'function') {
+ return origLstat(...args);
+ }
+ const cb = once(args[args.length - 1]);
+ // override the callback
+ args[args.length - 1] = function lstatCb(err, stats) {
+ if (err)
+ return cb(err);
+ if (!stats.isSymbolicLink()) {
+ // the file is not a symbolic link so there is nothing more to do
+ return cb(null, stats);
+ }
+ args[0] = resolvePathLike(args[0]);
+ if (!canEscape(args[0])) {
+ // the file can not escaped the sandbox so there is nothing more to do
+ return cb(null, stats);
+ }
+ return guardedReadLink(args[0], guardedReadLinkCb);
+ function guardedReadLinkCb(str) {
+ if (str != args[0]) {
+ // there are one or more hops within the guards so there is nothing more to do
+ return cb(null, stats);
+ }
+ // there are no hops so lets report the stats of the real file;
+ // we can't use origRealPath here since that function calls lstat internally
+ // which can result in an infinite loop
+ return unguardedRealPath(args[0], unguardedRealPathCb);
+ function unguardedRealPathCb(err, str) {
+ if (err) {
+ if (err.code === 'ENOENT') {
+ // broken link so there is nothing more to do
+ return cb(null, stats);
+ }
+ return cb(err);
+ }
+ return origLstat(str, cb);
+ }
+ }
+ };
+ origLstat(...args);
+ };
+ fs.lstatSync = function lstatSync(...args) {
+ const stats = origLstatSync(...args);
+ if (!(stats === null || stats === void 0 ? void 0 : stats.isSymbolicLink())) {
// the file is not a symbolic link so there is nothing more to do
- return cb(null, stats);
+ return stats;
}
args[0] = resolvePathLike(args[0]);
if (!canEscape(args[0])) {
// the file can not escaped the sandbox so there is nothing more to do
- return cb(null, stats);
+ return stats;
}
- return guardedReadLink(args[0], guardedReadLinkCb);
- function guardedReadLinkCb(str) {
- if (str != args[0]) {
- // there are one or more hops within the guards so there is nothing more to do
- return cb(null, stats);
- }
+ const guardedReadLink = guardedReadLinkSync(args[0]);
+ if (guardedReadLink != args[0]) {
+ // there are one or more hops within the guards so there is nothing more to do
+ return stats;
+ }
+ try {
+ args[0] = unguardedRealPathSync(args[0]);
// there are no hops so lets report the stats of the real file;
- // we can't use origRealPath here since that function calls lstat internally
+ // we can't use origRealPathSync here since that function calls lstat internally
// which can result in an infinite loop
- return unguardedRealPath(args[0], unguardedRealPathCb);
- function unguardedRealPathCb(err, str) {
- if (err) {
- if (err.code === 'ENOENT') {
- // broken link so there is nothing more to do
- return cb(null, stats);
- }
- return cb(err);
- }
- return origLstat(str, cb);
+ return origLstatSync(...args);
+ }
+ catch (err) {
+ if (err.code === 'ENOENT') {
+ // broken link so there is nothing more to do
+ return stats;
}
+ throw err;
}
};
- origLstat(...args);
- };
- fs.lstatSync = function lstatSync(...args) {
- const stats = origLstatSync(...args);
- if (!(stats === null || stats === void 0 ? void 0 : stats.isSymbolicLink())) {
- // the file is not a symbolic link so there is nothing more to do
- return stats;
- }
- args[0] = resolvePathLike(args[0]);
- if (!canEscape(args[0])) {
- // the file can not escaped the sandbox so there is nothing more to do
- return stats;
- }
- const guardedReadLink = guardedReadLinkSync(args[0]);
- if (guardedReadLink != args[0]) {
- // there are one or more hops within the guards so there is nothing more to do
- return stats;
- }
- try {
- args[0] = unguardedRealPathSync(args[0]);
- // there are no hops so lets report the stats of the real file;
- // we can't use origRealPathSync here since that function calls lstat internally
- // which can result in an infinite loop
- return origLstatSync(...args);
- }
- catch (err) {
- if (err.code === 'ENOENT') {
- // broken link so there is nothing more to do
- return stats;
- }
- throw err;
- }
- };
+ }
// =========================================================================
// fs.realpath
// =========================================================================
@@ -388,7 +400,9 @@ function patcher(roots) {
let unpatchPromises;
if (promisePropertyDescriptor) {
const promises = {};
- promises.lstat = util.promisify(fs.lstat);
+ if (!useInternalLstatPatch) {
+ promises.lstat = util.promisify(fs.lstat);
+ }
// NOTE: node core uses the newer realpath function fs.promises.native instead of fs.realPath
promises.realpath = util.promisify(fs.realpath.native);
promises.readlink = util.promisify(fs.readlink);
@@ -765,6 +779,9 @@ function patcher(roots) {
if (unpatchPromises) {
unpatchPromises();
}
+ if (unpatchEsm) {
+ unpatchEsm();
+ }
// Re-sync the esm modules to revert to the unpatched module.
esmModule.syncBuiltinESMExports();
};
diff --git a/js/private/node-patches/fs_stat.cjs b/js/private/node-patches/fs_stat.cjs
new file mode 100644
index 000000000..c4ce5e11b
--- /dev/null
+++ b/js/private/node-patches/fs_stat.cjs
@@ -0,0 +1,121 @@
+"use strict";
+// Patches Node's internal FS bindings, right before they would call into C++.
+// See full context in: https://github.com/aspect-build/rules_js/issues/362.
+// This is to ensure ESM imports don't escape accidentally via `realpathSync`.
+Object.defineProperty(exports, "__esModule", { value: true });
+exports.FsInternalStatPatcher = void 0;
+///
+const binding_1 = require("internal/test/binding");
+const utils_1 = require("internal/fs/utils");
+const fs_cjs_1 = require("./fs.cjs");
+const internalFs = (0, binding_1.internalBinding)('fs');
+class FsInternalStatPatcher {
+ constructor(escapeFns, guardedReadLink, guardedReadLinkSync, unguardedRealPath, unguardedRealPathSync) {
+ this.escapeFns = escapeFns;
+ this.guardedReadLink = guardedReadLink;
+ this.guardedReadLinkSync = guardedReadLinkSync;
+ this.unguardedRealPath = unguardedRealPath;
+ this.unguardedRealPathSync = unguardedRealPathSync;
+ this._originalFsLstat = internalFs.lstat;
+ }
+ revert() {
+ internalFs.lstat = this._originalFsLstat;
+ }
+ patch() {
+ const statPatcher = this;
+ internalFs.lstat = function (path, bigint, reqCallback, throwIfNoEntry) {
+ if (reqCallback === internalFs.kUsePromises) {
+ return statPatcher._originalFsLstat.call(internalFs, path, bigint, reqCallback, throwIfNoEntry).then((stats) => {
+ return new Promise((resolve, reject) => {
+ statPatcher.eeguardStats(path, bigint, stats, throwIfNoEntry, (err, guardedStats) => {
+ err || !guardedStats ? reject(err) : resolve(guardedStats);
+ });
+ });
+ });
+ }
+ else if (reqCallback === undefined) {
+ const stats = statPatcher._originalFsLstat.call(internalFs, path, bigint, undefined, throwIfNoEntry);
+ if (!stats) {
+ return stats;
+ }
+ return statPatcher.eeguardStatsSync(path, bigint, throwIfNoEntry, stats);
+ }
+ else {
+ // Just re-use the promise path from above.
+ internalFs.lstat(path, bigint, internalFs.kUsePromises, throwIfNoEntry)
+ .then((stats) => reqCallback.oncomplete(null, stats))
+ .catch((err) => reqCallback.oncomplete(err));
+ }
+ };
+ }
+ eeguardStats(path, bigint, stats, throwIfNotFound, cb) {
+ const statsObj = (0, utils_1.getStatsFromBinding)(stats);
+ if (!statsObj.isSymbolicLink()) {
+ // the file is not a symbolic link so there is nothing more to do
+ return cb(null, stats);
+ }
+ path = (0, fs_cjs_1.resolvePathLike)(path);
+ if (!this.escapeFns.canEscape(path)) {
+ // the file can not escaped the sandbox so there is nothing more to do
+ return cb(null, stats);
+ }
+ return this.guardedReadLink(path, (str) => {
+ if (str != path) {
+ // there are one or more hops within the guards so there is nothing more to do
+ return cb(null, stats);
+ }
+ // there are no hops so lets report the stats of the real file;
+ // we can't use origRealPath here since that function calls lstat internally
+ // which can result in an infinite loop
+ return this.unguardedRealPath(path, (err, str) => {
+ if (err) {
+ if (err.code === 'ENOENT') {
+ // broken link so there is nothing more to do
+ return cb(null, stats);
+ }
+ return cb(err);
+ }
+ // Forward request to original callback.
+ const req2 = new internalFs.FSReqCallback(bigint);
+ req2.oncomplete = (err, realStats) => cb(err, realStats);
+ return this._originalFsLstat.call(internalFs, str, bigint, req2, throwIfNotFound);
+ });
+ });
+ }
+ eeguardStatsSync(path, bigint, throwIfNoEntry, stats) {
+ // No stats available.
+ if (!stats) {
+ return stats;
+ }
+ const statsObj = (0, utils_1.getStatsFromBinding)(stats);
+ if (!statsObj.isSymbolicLink()) {
+ // the file is not a symbolic link so there is nothing more to do
+ return stats;
+ }
+ path = (0, fs_cjs_1.resolvePathLike)(path);
+ if (!this.escapeFns.canEscape(path)) {
+ // the file can not escaped the sandbox so there is nothing more to do
+ return stats;
+ }
+ const guardedReadLink = this.guardedReadLinkSync(path);
+ if (guardedReadLink != path) {
+ // there are one or more hops within the guards so there is nothing more to do
+ return stats;
+ }
+ try {
+ path = this.unguardedRealPathSync(path);
+ // there are no hops so lets report the stats of the real file;
+ // we can't use origRealPathSync here since that function calls lstat internally
+ // which can result in an infinite loop
+ return this._originalFsLstat.call(internalFs, path, bigint, undefined, throwIfNoEntry);
+ }
+ catch (err) {
+ if (err.code === 'ENOENT') {
+ // broken link so there is nothing more to do
+ return stats;
+ }
+ throw err;
+ }
+ }
+}
+exports.FsInternalStatPatcher = FsInternalStatPatcher;
diff --git a/js/private/node-patches/register.cjs b/js/private/node-patches/register.cjs
index 85d67ddf9..5fd10591b 100644
--- a/js/private/node-patches/register.cjs
+++ b/js/private/node-patches/register.cjs
@@ -5,6 +5,7 @@ const {
JS_BINARY__LOG_PREFIX,
JS_BINARY__NODE_WRAPPER,
JS_BINARY__PATCH_NODE_FS,
+ JS_BINARY__PATCH_NODE_ESM_LOADER,
} = process.env
// Keep a count of how many times these patches are applied; this should reflect the depth
@@ -41,5 +42,8 @@ if (
`DEBUG: ${JS_BINARY__LOG_PREFIX}: node fs patches will be applied with roots: ${roots}`
)
}
- patchfs(roots)
+ const useLstatPatch =
+ JS_BINARY__PATCH_NODE_ESM_LOADER &&
+ JS_BINARY__PATCH_NODE_ESM_LOADER != '0'
+ patchfs(roots, useLstatPatch)
}
diff --git a/js/private/node-patches/src/BUILD.bazel b/js/private/node-patches/src/BUILD.bazel
index 5ed1769c5..ea19c014f 100644
--- a/js/private/node-patches/src/BUILD.bazel
+++ b/js/private/node-patches/src/BUILD.bazel
@@ -4,18 +4,24 @@ typescript_bin.tsc(
name = "compile",
srcs = [
"fs.cts",
+ "fs_stat.cts",
+ "fs_stat_types.d.cts",
"tsconfig.json",
"//:node_modules/@types/node",
],
outs = [
"fs.cjs",
+ "fs_stat.cjs",
],
args = [
"-p",
"tsconfig.json",
],
chdir = package_name(),
- visibility = ["//js/private/test/node-patches:__pkg__"],
+ visibility = [
+ "//js/private/node-patches:__pkg__",
+ "//js/private/test/node-patches:__pkg__",
+ ],
)
genrule(
diff --git a/js/private/node-patches/src/fs.cts b/js/private/node-patches/src/fs.cts
index f5767ba66..a5d2592ca 100644
--- a/js/private/node-patches/src/fs.cts
+++ b/js/private/node-patches/src/fs.cts
@@ -53,7 +53,10 @@ const PATCHED_FS_METHODS: ReadonlyArray = [
* Function that patches the `fs` module to not escape the given roots.
* @returns a function to undo the patches.
*/
-export function patcher(roots: string[]): () => void {
+export function patcher(
+ roots: string[],
+ useInternalLstatPatch: boolean = false
+): () => void {
if (fs._unpatched) {
throw new Error('FS is already patched.')
}
@@ -102,98 +105,119 @@ export function patcher(roots: string[]): () => void {
const { canEscape, isEscape } = escapeFunction(roots)
// =========================================================================
- // fs.lstat
+ // fsInternal.lstat (to patch ESM resolve's `realpathSync`!)
// =========================================================================
+ let unpatchEsm: Function | undefined
+ if (useInternalLstatPatch) {
+ const lstatEsmPatcher =
+ new (require('./fs_stat.cjs').FsInternalStatPatcher)(
+ { canEscape, isEscape },
+ guardedReadLink,
+ guardedReadLinkSync,
+ unguardedRealPath,
+ unguardedRealPathSync
+ )
- fs.lstat = function lstat(...args: Parameters) {
- // preserve error when calling function without required callback
- if (typeof args[args.length - 1] !== 'function') {
- return origLstat(...args)
- }
+ lstatEsmPatcher.patch()
- const cb = once(args[args.length - 1] as Function)
+ unpatchEsm = lstatEsmPatcher.revert.bind(lstatEsmPatcher)
+ }
- // override the callback
- args[args.length - 1] = function lstatCb(err: Error, stats: Stats) {
- if (err) return cb(err)
+ // =========================================================================
+ // fs.lstat
+ // =========================================================================
- if (!stats.isSymbolicLink()) {
- // the file is not a symbolic link so there is nothing more to do
- return cb(null, stats)
+ if (!useInternalLstatPatch) {
+ fs.lstat = function lstat(...args: Parameters) {
+ // preserve error when calling function without required callback
+ if (typeof args[args.length - 1] !== 'function') {
+ return origLstat(...args)
}
- args[0] = resolvePathLike(args[0])
+ const cb = once(args[args.length - 1] as any)
- if (!canEscape(args[0])) {
- // the file can not escaped the sandbox so there is nothing more to do
- return cb(null, stats)
- }
+ // override the callback
+ args[args.length - 1] = function lstatCb(err: Error, stats: Stats) {
+ if (err) return cb(err)
- return guardedReadLink(args[0], guardedReadLinkCb)
+ if (!stats.isSymbolicLink()) {
+ // the file is not a symbolic link so there is nothing more to do
+ return cb(null, stats)
+ }
- function guardedReadLinkCb(str: string) {
- if (str != args[0]) {
- // there are one or more hops within the guards so there is nothing more to do
+ args[0] = resolvePathLike(args[0])
+
+ if (!canEscape(args[0])) {
+ // the file can not escaped the sandbox so there is nothing more to do
return cb(null, stats)
}
- // there are no hops so lets report the stats of the real file;
- // we can't use origRealPath here since that function calls lstat internally
- // which can result in an infinite loop
- return unguardedRealPath(args[0], unguardedRealPathCb)
+ return guardedReadLink(args[0], guardedReadLinkCb)
- function unguardedRealPathCb(err: Error, str: string) {
- if (err) {
- if ((err as any).code === 'ENOENT') {
- // broken link so there is nothing more to do
- return cb(null, stats)
+ function guardedReadLinkCb(str: string) {
+ if (str != args[0]) {
+ // there are one or more hops within the guards so there is nothing more to do
+ return cb(null, stats)
+ }
+
+ // there are no hops so lets report the stats of the real file;
+ // we can't use origRealPath here since that function calls lstat internally
+ // which can result in an infinite loop
+ return unguardedRealPath(args[0], unguardedRealPathCb)
+
+ function unguardedRealPathCb(err: Error, str: string) {
+ if (err) {
+ if ((err as any).code === 'ENOENT') {
+ // broken link so there is nothing more to do
+ return cb(null, stats)
+ }
+ return cb(err)
}
- return cb(err)
+ return origLstat(str, cb)
}
- return origLstat(str, cb)
}
}
- }
- origLstat(...args)
- }
-
- fs.lstatSync = function lstatSync(
- ...args: Parameters
- ) {
- const stats = origLstatSync(...args)
-
- if (!stats?.isSymbolicLink()) {
- // the file is not a symbolic link so there is nothing more to do
- return stats
+ origLstat(...args)
}
- args[0] = resolvePathLike(args[0])
+ fs.lstatSync = function lstatSync(
+ ...args: Parameters
+ ) {
+ const stats = origLstatSync(...args)
- if (!canEscape(args[0])) {
- // the file can not escaped the sandbox so there is nothing more to do
- return stats
- }
+ if (!stats?.isSymbolicLink()) {
+ // the file is not a symbolic link so there is nothing more to do
+ return stats
+ }
- const guardedReadLink: string = guardedReadLinkSync(args[0])
- if (guardedReadLink != args[0]) {
- // there are one or more hops within the guards so there is nothing more to do
- return stats
- }
+ args[0] = resolvePathLike(args[0])
- try {
- args[0] = unguardedRealPathSync(args[0])
+ if (!canEscape(args[0])) {
+ // the file can not escaped the sandbox so there is nothing more to do
+ return stats
+ }
- // there are no hops so lets report the stats of the real file;
- // we can't use origRealPathSync here since that function calls lstat internally
- // which can result in an infinite loop
- return origLstatSync(...args)
- } catch (err) {
- if (err.code === 'ENOENT') {
- // broken link so there is nothing more to do
+ const guardedReadLink: string = guardedReadLinkSync(args[0])
+ if (guardedReadLink != args[0]) {
+ // there are one or more hops within the guards so there is nothing more to do
return stats
}
- throw err
+
+ try {
+ args[0] = unguardedRealPathSync(args[0])
+
+ // there are no hops so lets report the stats of the real file;
+ // we can't use origRealPathSync here since that function calls lstat internally
+ // which can result in an infinite loop
+ return origLstatSync(...args)
+ } catch (err) {
+ if (err.code === 'ENOENT') {
+ // broken link so there is nothing more to do
+ return stats
+ }
+ throw err
+ }
}
}
@@ -461,7 +485,9 @@ export function patcher(roots: string[]): () => void {
if (promisePropertyDescriptor) {
const promises: typeof fs.promises = {}
- promises.lstat = util.promisify(fs.lstat)
+ if (!useInternalLstatPatch) {
+ promises.lstat = util.promisify(fs.lstat)
+ }
// NOTE: node core uses the newer realpath function fs.promises.native instead of fs.realPath
promises.realpath = util.promisify(fs.realpath.native)
promises.readlink = util.promisify(fs.readlink)
@@ -886,6 +912,10 @@ export function patcher(roots: string[]): () => void {
unpatchPromises()
}
+ if (unpatchEsm) {
+ unpatchEsm()
+ }
+
// Re-sync the esm modules to revert to the unpatched module.
esmModule.syncBuiltinESMExports()
}
@@ -910,7 +940,7 @@ function stringifyPathLike(p: PathLike): string {
}
}
-function resolvePathLike(p: PathLike): string {
+export function resolvePathLike(p: PathLike): string {
return path.resolve(stringifyPathLike(p))
}
diff --git a/js/private/node-patches/src/fs_stat.cts b/js/private/node-patches/src/fs_stat.cts
new file mode 100644
index 000000000..f59fe97b5
--- /dev/null
+++ b/js/private/node-patches/src/fs_stat.cts
@@ -0,0 +1,185 @@
+// Patches Node's internal FS bindings, right before they would call into C++.
+// See full context in: https://github.com/aspect-build/rules_js/issues/362.
+// This is to ensure ESM imports don't escape accidentally via `realpathSync`.
+
+///
+
+import { internalBinding, FsInternalModule } from 'internal/test/binding';
+import { getStatsFromBinding } from 'internal/fs/utils';
+import { resolvePathLike, type escapeFunction } from './fs.cjs';
+
+const internalFs = internalBinding('fs');
+
+export class FsInternalStatPatcher {
+ private _originalFsLstat = internalFs.lstat;
+
+ constructor(
+ private escapeFns: ReturnType,
+ private guardedReadLink: (start: string, cb: (str: string) => void) => void,
+ private guardedReadLinkSync: (start: string) => string,
+ private unguardedRealPath: (start: string, cb: (err: Error, str?: string) => void) => void,
+ private unguardedRealPathSync: (start: string) => string,
+ ) {}
+
+ revert() {
+ internalFs.lstat = this._originalFsLstat;
+ }
+
+ patch() {
+ const statPatcher = this;
+
+ internalFs.lstat = function (path, bigint, reqCallback, throwIfNoEntry) {
+ const currentStack = new Error().stack;
+ const needsGuarding =
+ currentStack &&
+ (currentStack.includes('finalizeResolution (node:internal/modules/esm/resolve') &&
+ !currentStack.includes('eeguardStats'));
+
+ if (!needsGuarding) {
+ console.log("NO NEED: " + currentStack);
+ return statPatcher._originalFsLstat.call(
+ internalFs,
+ path,
+ bigint,
+ reqCallback,
+ throwIfNoEntry,
+ );
+ }
+
+ if (reqCallback === internalFs.kUsePromises) {
+ return (
+ statPatcher._originalFsLstat.call(
+ internalFs,
+ path,
+ bigint,
+ reqCallback,
+ throwIfNoEntry,
+ ) as Promise
+ ).then((stats) => {
+ return new Promise((resolve, reject) => {
+ statPatcher.eeguardStats(path, bigint, stats, throwIfNoEntry, (err, guardedStats) => {
+ err || !guardedStats ? reject(err) : resolve(guardedStats);
+ });
+ });
+ });
+ } else if (reqCallback === undefined) {
+ const stats = statPatcher._originalFsLstat.call(
+ internalFs,
+ path,
+ bigint,
+ undefined,
+ throwIfNoEntry,
+ ) as FsInternalModule.InternalStats;
+ if (!stats) {
+ return stats;
+ }
+ return statPatcher.eeguardStatsSync(path, bigint, throwIfNoEntry, stats);
+ } else {
+ // Just re-use the promise path from above.
+ (
+ internalFs.lstat(
+ path,
+ bigint,
+ internalFs.kUsePromises,
+ throwIfNoEntry,
+ ) as Promise
+ )
+ .then((stats) => reqCallback.oncomplete(null, stats))
+ .catch((err) => reqCallback.oncomplete(err));
+ }
+ };
+ }
+
+ eeguardStats(
+ path: string,
+ bigint: boolean,
+ stats: FsInternalModule.InternalStats,
+ throwIfNotFound: boolean,
+ cb: (err: unknown, stats?: FsInternalModule.InternalStats) => void,
+ ) {
+ const statsObj = getStatsFromBinding(stats);
+ if (!statsObj.isSymbolicLink()) {
+ // the file is not a symbolic link so there is nothing more to do
+ return cb(null, stats);
+ }
+
+ path = resolvePathLike(path);
+ if (!this.escapeFns.canEscape(path)) {
+ // the file can not escaped the sandbox so there is nothing more to do
+ return cb(null, stats);
+ }
+
+ return this.guardedReadLink(path, (str) => {
+ if (str != path) {
+ // there are one or more hops within the guards so there is nothing more to do
+ return cb(null, stats);
+ }
+ // there are no hops so lets report the stats of the real file;
+ // we can't use origRealPath here since that function calls lstat internally
+ // which can result in an infinite loop
+ return this.unguardedRealPath(path, (err, str) => {
+ if (err) {
+ if ((err as Partial<{ code: string }>).code === 'ENOENT') {
+ // broken link so there is nothing more to do
+ return cb(null, stats);
+ }
+ return cb(err);
+ }
+
+ // Forward request to original callback.
+ const req2 = new internalFs.FSReqCallback(bigint);
+ req2.oncomplete = (err, realStats) => cb(err, realStats);
+ return this._originalFsLstat.call(internalFs, str!, bigint, req2, throwIfNotFound);
+ });
+ });
+ }
+
+ eeguardStatsSync(
+ path: string,
+ bigint: boolean,
+ throwIfNoEntry: boolean,
+ stats: FsInternalModule.InternalStats,
+ ): FsInternalModule.InternalStats {
+ // No stats available.
+ if (!stats) {
+ return stats;
+ }
+
+ const statsObj = getStatsFromBinding(stats);
+ if (!statsObj.isSymbolicLink()) {
+ // the file is not a symbolic link so there is nothing more to do
+ return stats;
+ }
+
+ path = resolvePathLike(path);
+ if (!this.escapeFns.canEscape(path)) {
+ // the file can not escaped the sandbox so there is nothing more to do
+ return stats;
+ }
+
+ const guardedReadLink = this.guardedReadLinkSync(path);
+ if (guardedReadLink != path) {
+ // there are one or more hops within the guards so there is nothing more to do
+ return stats;
+ }
+ try {
+ path = this.unguardedRealPathSync(path);
+ // there are no hops so lets report the stats of the real file;
+ // we can't use origRealPathSync here since that function calls lstat internally
+ // which can result in an infinite loop
+ return this._originalFsLstat.call(
+ internalFs,
+ path,
+ bigint,
+ undefined,
+ throwIfNoEntry,
+ ) as FsInternalModule.InternalStats;
+ } catch (err) {
+ if ((err as Partial<{ code: string }>).code === 'ENOENT') {
+ // broken link so there is nothing more to do
+ return stats;
+ }
+ throw err;
+ }
+ }
+}
diff --git a/js/private/node-patches/src/fs_stat_types.d.cts b/js/private/node-patches/src/fs_stat_types.d.cts
new file mode 100644
index 000000000..ff5266add
--- /dev/null
+++ b/js/private/node-patches/src/fs_stat_types.d.cts
@@ -0,0 +1,31 @@
+// Types of internal modules exposes via `--expose-internals`.
+// See: https://github.com/nodejs/node/blob/f58613a64c8e02b42391952a6e55a330a7607fa7/typings/internalBinding/fs.d.ts#L17.
+
+declare module 'internal/test/binding' {
+ namespace FsInternalModule {
+ type InternalStats = { readonly __internalStatsBrandedType: unique symbol };
+
+ const kUsePromises: unique symbol;
+
+ class FSReqCallback {
+ constructor(bigint: boolean);
+ oncomplete: (err: unknown, stats?: InternalStats) => void;
+ }
+
+ function lstat(
+ path: string,
+ bigint: boolean,
+ cb: typeof kUsePromises | FSReqCallback | undefined,
+ throwIfNotFound: boolean,
+ ): InternalStats | Promise | undefined;
+ }
+
+ function internalBinding(module: 'fs'): typeof FsInternalModule;
+}
+
+declare module 'internal/fs/utils' {
+ import type { Stats } from 'node:fs';
+ import type { FsInternalModule } from 'internal/test/binding';
+
+ function getStatsFromBinding(stat: FsInternalModule.InternalStats): Stats;
+}
diff --git a/js/private/node-patches/src/tsconfig.json b/js/private/node-patches/src/tsconfig.json
index c8f887d64..1f494a3ef 100644
--- a/js/private/node-patches/src/tsconfig.json
+++ b/js/private/node-patches/src/tsconfig.json
@@ -1,7 +1,9 @@
{
"compilerOptions": {
"module": "commonjs",
- "target": "es2017"
+ "target": "es2017",
+ "moduleResolution": "node",
+ "types": ["node"]
},
"include": ["*.cts"]
}
diff --git a/js/private/node_wrapper.sh b/js/private/node_wrapper.sh
index fae2c2a56..511a900c5 100755
--- a/js/private/node_wrapper.sh
+++ b/js/private/node_wrapper.sh
@@ -2,4 +2,15 @@
set -o pipefail -o errexit -o nounset
-exec "$JS_BINARY__NODE_BINARY" --require "$JS_BINARY__NODE_PATCHES" "$@"
+if [[ "${JS_BINARY__PATCH_NODE_ESM_LOADER:-}" == "1" ]]; then
+ # --expose-internals is needed for FS esm patches.
+
+ #--disable-warning=internal/test/binding
+
+ exec "$JS_BINARY__NODE_BINARY" \
+ --expose-internals \
+ --inspect-brk \
+ --require "$JS_BINARY__NODE_PATCHES" "$@"
+else
+ exec "$JS_BINARY__NODE_BINARY" --require "$JS_BINARY__NODE_PATCHES" "$@"
+fi
diff --git a/js/private/test/image/checksum_test.expected b/js/private/test/image/checksum_test.expected
index eedb9795b..a77b42c51 100644
--- a/js/private/test/image/checksum_test.expected
+++ b/js/private/test/image/checksum_test.expected
@@ -1,4 +1,4 @@
-979f06636582a6d515442b13bb048db0b66ed2febfa458a02d8745b691e9b2ff js/private/test/image/cksum_node
+48120e2802ab300d1125df6c6f52351aec81fab8427a92c39eebea2d402257b6 js/private/test/image/cksum_node
52836a988c8ac815b4a3b70fa3a3acec67b851699fa989694cef4cc1fa53de96 js/private/test/image/cksum_package_store_3p
642b308a0561fb51dfd96d08d74a4ec419c9d2ca501cfa1002a49c8e25fbe4c2 js/private/test/image/cksum_package_store_1p
5d45f32dacf0b83e26c33d4e1017c694e79eaff29b8ecc336f9ea8fdee870d45 js/private/test/image/cksum_node_modules
diff --git a/js/private/test/image/custom_layers_nomatch_test_node.listing b/js/private/test/image/custom_layers_nomatch_test_node.listing
index c789e0f0c..97fed4a65 100644
--- a/js/private/test/image/custom_layers_nomatch_test_node.listing
+++ b/js/private/test/image/custom_layers_nomatch_test_node.listing
@@ -8,8 +8,9 @@ drwxr-xr-x 0 0 0 0 Jan 1 1970 ./app/js/private/test/image/bin.
drwxr-xr-x 0 0 0 0 Jan 1 1970 ./app/js/private/test/image/bin.runfiles/_main/js/
drwxr-xr-x 0 0 0 0 Jan 1 1970 ./app/js/private/test/image/bin.runfiles/_main/js/private/
drwxr-xr-x 0 0 0 0 Jan 1 1970 ./app/js/private/test/image/bin.runfiles/_main/js/private/node-patches/
--r-xr-xr-x 0 0 0 34656 Jan 1 1970 ./app/js/private/test/image/bin.runfiles/_main/js/private/node-patches/fs.cjs
--r-xr-xr-x 0 0 0 1460 Jan 1 1970 ./app/js/private/test/image/bin.runfiles/_main/js/private/node-patches/register.cjs
+-r-xr-xr-x 0 0 0 35421 Jan 1 1970 ./app/js/private/test/image/bin.runfiles/_main/js/private/node-patches/fs.cjs
+-r-xr-xr-x 0 0 0 6009 Jan 1 1970 ./app/js/private/test/image/bin.runfiles/_main/js/private/node-patches/fs_stat.cjs
+-r-xr-xr-x 0 0 0 1631 Jan 1 1970 ./app/js/private/test/image/bin.runfiles/_main/js/private/node-patches/register.cjs
drwxr-xr-x 0 0 0 0 Jan 1 1970 ./app/js/private/test/image/bin.runfiles/rules_nodejs~~node~nodejs_linux_amd64/
drwxr-xr-x 0 0 0 0 Jan 1 1970 ./app/js/private/test/image/bin.runfiles/rules_nodejs~~node~nodejs_linux_amd64/bin/
drwxr-xr-x 0 0 0 0 Jan 1 1970 ./app/js/private/test/image/bin.runfiles/rules_nodejs~~node~nodejs_linux_amd64/bin/nodejs/
diff --git a/js/private/test/image/custom_owner_test_app.listing b/js/private/test/image/custom_owner_test_app.listing
index 40e19a484..bb3843353 100644
--- a/js/private/test/image/custom_owner_test_app.listing
+++ b/js/private/test/image/custom_owner_test_app.listing
@@ -2,7 +2,7 @@ drwxr-xr-x 0 100 0 0 Jan 1 1970 ./js/
drwxr-xr-x 0 100 0 0 Jan 1 1970 ./js/private/
drwxr-xr-x 0 100 0 0 Jan 1 1970 ./js/private/test/
drwxr-xr-x 0 100 0 0 Jan 1 1970 ./js/private/test/image/
--r-xr-xr-x 0 100 0 23981 Jan 1 1970 ./js/private/test/image/bin
+-r-xr-xr-x 0 100 0 24025 Jan 1 1970 ./js/private/test/image/bin
drwxr-xr-x 0 100 0 0 Jan 1 1970 ./js/private/test/image/bin.runfiles/
drwxr-xr-x 0 100 0 0 Jan 1 1970 ./js/private/test/image/bin.runfiles/_main/
drwxr-xr-x 0 100 0 0 Jan 1 1970 ./js/private/test/image/bin.runfiles/_main/examples/
@@ -16,7 +16,7 @@ drwxr-xr-x 0 100 0 0 Jan 1 1970 ./js/private/test/image/bin.runf
drwxr-xr-x 0 100 0 0 Jan 1 1970 ./js/private/test/image/bin.runfiles/_main/js/private/test/
drwxr-xr-x 0 100 0 0 Jan 1 1970 ./js/private/test/image/bin.runfiles/_main/js/private/test/image/
drwxr-xr-x 0 100 0 0 Jan 1 1970 ./js/private/test/image/bin.runfiles/_main/js/private/test/image/bin_/
--r-xr-xr-x 0 100 0 23981 Jan 1 1970 ./js/private/test/image/bin.runfiles/_main/js/private/test/image/bin_/bin
+-r-xr-xr-x 0 100 0 24025 Jan 1 1970 ./js/private/test/image/bin.runfiles/_main/js/private/test/image/bin_/bin
drwxr-xr-x 0 100 0 0 Jan 1 1970 ./js/private/test/image/bin.runfiles/_main/js/private/test/image/bin_node_bin/
--r-xr-xr-x 0 100 0 133 Jan 1 1970 ./js/private/test/image/bin.runfiles/_main/js/private/test/image/bin_node_bin/node
+-r-xr-xr-x 0 100 0 411 Jan 1 1970 ./js/private/test/image/bin.runfiles/_main/js/private/test/image/bin_node_bin/node
-r-xr-xr-x 0 100 0 20 Jan 1 1970 ./js/private/test/image/bin.runfiles/_main/js/private/test/image/main.js
diff --git a/js/private/test/image/custom_owner_test_node.listing b/js/private/test/image/custom_owner_test_node.listing
index 4cef76254..04ba1409a 100644
--- a/js/private/test/image/custom_owner_test_node.listing
+++ b/js/private/test/image/custom_owner_test_node.listing
@@ -7,8 +7,9 @@ drwxr-xr-x 0 100 0 0 Jan 1 1970 ./js/private/test/image/bin.runf
drwxr-xr-x 0 100 0 0 Jan 1 1970 ./js/private/test/image/bin.runfiles/_main/js/
drwxr-xr-x 0 100 0 0 Jan 1 1970 ./js/private/test/image/bin.runfiles/_main/js/private/
drwxr-xr-x 0 100 0 0 Jan 1 1970 ./js/private/test/image/bin.runfiles/_main/js/private/node-patches/
--r-xr-xr-x 0 100 0 34656 Jan 1 1970 ./js/private/test/image/bin.runfiles/_main/js/private/node-patches/fs.cjs
--r-xr-xr-x 0 100 0 1460 Jan 1 1970 ./js/private/test/image/bin.runfiles/_main/js/private/node-patches/register.cjs
+-r-xr-xr-x 0 100 0 35421 Jan 1 1970 ./js/private/test/image/bin.runfiles/_main/js/private/node-patches/fs.cjs
+-r-xr-xr-x 0 100 0 6009 Jan 1 1970 ./js/private/test/image/bin.runfiles/_main/js/private/node-patches/fs_stat.cjs
+-r-xr-xr-x 0 100 0 1631 Jan 1 1970 ./js/private/test/image/bin.runfiles/_main/js/private/node-patches/register.cjs
drwxr-xr-x 0 100 0 0 Jan 1 1970 ./js/private/test/image/bin.runfiles/rules_nodejs~~node~nodejs_linux_amd64/
drwxr-xr-x 0 100 0 0 Jan 1 1970 ./js/private/test/image/bin.runfiles/rules_nodejs~~node~nodejs_linux_amd64/bin/
drwxr-xr-x 0 100 0 0 Jan 1 1970 ./js/private/test/image/bin.runfiles/rules_nodejs~~node~nodejs_linux_amd64/bin/nodejs/
diff --git a/js/private/test/image/default_test_app.listing b/js/private/test/image/default_test_app.listing
index 785009224..027bded2f 100644
--- a/js/private/test/image/default_test_app.listing
+++ b/js/private/test/image/default_test_app.listing
@@ -2,7 +2,7 @@ drwxr-xr-x 0 0 0 0 Jan 1 1970 ./js/
drwxr-xr-x 0 0 0 0 Jan 1 1970 ./js/private/
drwxr-xr-x 0 0 0 0 Jan 1 1970 ./js/private/test/
drwxr-xr-x 0 0 0 0 Jan 1 1970 ./js/private/test/image/
--r-xr-xr-x 0 0 0 23981 Jan 1 1970 ./js/private/test/image/bin
+-r-xr-xr-x 0 0 0 24025 Jan 1 1970 ./js/private/test/image/bin
drwxr-xr-x 0 0 0 0 Jan 1 1970 ./js/private/test/image/bin.runfiles/
drwxr-xr-x 0 0 0 0 Jan 1 1970 ./js/private/test/image/bin.runfiles/_main/
drwxr-xr-x 0 0 0 0 Jan 1 1970 ./js/private/test/image/bin.runfiles/_main/examples/
@@ -16,7 +16,7 @@ drwxr-xr-x 0 0 0 0 Jan 1 1970 ./js/private/test/image/bin.runf
drwxr-xr-x 0 0 0 0 Jan 1 1970 ./js/private/test/image/bin.runfiles/_main/js/private/test/
drwxr-xr-x 0 0 0 0 Jan 1 1970 ./js/private/test/image/bin.runfiles/_main/js/private/test/image/
drwxr-xr-x 0 0 0 0 Jan 1 1970 ./js/private/test/image/bin.runfiles/_main/js/private/test/image/bin_/
--r-xr-xr-x 0 0 0 23981 Jan 1 1970 ./js/private/test/image/bin.runfiles/_main/js/private/test/image/bin_/bin
+-r-xr-xr-x 0 0 0 24025 Jan 1 1970 ./js/private/test/image/bin.runfiles/_main/js/private/test/image/bin_/bin
drwxr-xr-x 0 0 0 0 Jan 1 1970 ./js/private/test/image/bin.runfiles/_main/js/private/test/image/bin_node_bin/
--r-xr-xr-x 0 0 0 133 Jan 1 1970 ./js/private/test/image/bin.runfiles/_main/js/private/test/image/bin_node_bin/node
+-r-xr-xr-x 0 0 0 411 Jan 1 1970 ./js/private/test/image/bin.runfiles/_main/js/private/test/image/bin_node_bin/node
-r-xr-xr-x 0 0 0 20 Jan 1 1970 ./js/private/test/image/bin.runfiles/_main/js/private/test/image/main.js
diff --git a/js/private/test/image/default_test_node.listing b/js/private/test/image/default_test_node.listing
index 3ebfdf82b..1bb171f9f 100644
--- a/js/private/test/image/default_test_node.listing
+++ b/js/private/test/image/default_test_node.listing
@@ -7,8 +7,9 @@ drwxr-xr-x 0 0 0 0 Jan 1 1970 ./js/private/test/image/bin.runf
drwxr-xr-x 0 0 0 0 Jan 1 1970 ./js/private/test/image/bin.runfiles/_main/js/
drwxr-xr-x 0 0 0 0 Jan 1 1970 ./js/private/test/image/bin.runfiles/_main/js/private/
drwxr-xr-x 0 0 0 0 Jan 1 1970 ./js/private/test/image/bin.runfiles/_main/js/private/node-patches/
--r-xr-xr-x 0 0 0 34656 Jan 1 1970 ./js/private/test/image/bin.runfiles/_main/js/private/node-patches/fs.cjs
--r-xr-xr-x 0 0 0 1460 Jan 1 1970 ./js/private/test/image/bin.runfiles/_main/js/private/node-patches/register.cjs
+-r-xr-xr-x 0 0 0 35421 Jan 1 1970 ./js/private/test/image/bin.runfiles/_main/js/private/node-patches/fs.cjs
+-r-xr-xr-x 0 0 0 6009 Jan 1 1970 ./js/private/test/image/bin.runfiles/_main/js/private/node-patches/fs_stat.cjs
+-r-xr-xr-x 0 0 0 1631 Jan 1 1970 ./js/private/test/image/bin.runfiles/_main/js/private/node-patches/register.cjs
drwxr-xr-x 0 0 0 0 Jan 1 1970 ./js/private/test/image/bin.runfiles/rules_nodejs~~node~nodejs_linux_amd64/
drwxr-xr-x 0 0 0 0 Jan 1 1970 ./js/private/test/image/bin.runfiles/rules_nodejs~~node~nodejs_linux_amd64/bin/
drwxr-xr-x 0 0 0 0 Jan 1 1970 ./js/private/test/image/bin.runfiles/rules_nodejs~~node~nodejs_linux_amd64/bin/nodejs/
diff --git a/js/private/test/image/non_ascii/custom_layer_groups_test_app.listing b/js/private/test/image/non_ascii/custom_layer_groups_test_app.listing
index 46846288d..2d7622b50 100644
--- a/js/private/test/image/non_ascii/custom_layer_groups_test_app.listing
+++ b/js/private/test/image/non_ascii/custom_layer_groups_test_app.listing
@@ -4,7 +4,7 @@ drwxr-xr-x 0 0 0 0 Jan 1 1970 ./app/js/private/
drwxr-xr-x 0 0 0 0 Jan 1 1970 ./app/js/private/test/
drwxr-xr-x 0 0 0 0 Jan 1 1970 ./app/js/private/test/image/
drwxr-xr-x 0 0 0 0 Jan 1 1970 ./app/js/private/test/image/non_ascii/
--r-xr-xr-x 0 0 0 24076 Jan 1 1970 ./app/js/private/test/image/non_ascii/bin2
+-r-xr-xr-x 0 0 0 24120 Jan 1 1970 ./app/js/private/test/image/non_ascii/bin2
drwxr-xr-x 0 0 0 0 Jan 1 1970 ./app/js/private/test/image/non_ascii/bin2.runfiles/
drwxr-xr-x 0 0 0 0 Jan 1 1970 ./app/js/private/test/image/non_ascii/bin2.runfiles/_main/
drwxr-xr-x 0 0 0 0 Jan 1 1970 ./app/js/private/test/image/non_ascii/bin2.runfiles/_main/js/
@@ -14,8 +14,8 @@ drwxr-xr-x 0 0 0 0 Jan 1 1970 ./app/js/private/test/image/non_
drwxr-xr-x 0 0 0 0 Jan 1 1970 ./app/js/private/test/image/non_ascii/bin2.runfiles/_main/js/private/test/image/non_ascii/
-r-xr-xr-x 0 0 0 0 Jan 1 1970 ./app/js/private/test/image/non_ascii/bin2.runfiles/_main/js/private/test/image/non_ascii/ㅑㅕㅣㅇ.ㄴㅅ
drwxr-xr-x 0 0 0 0 Jan 1 1970 ./app/js/private/test/image/non_ascii/bin2.runfiles/_main/js/private/test/image/non_ascii/bin2_/
--r-xr-xr-x 0 0 0 24076 Jan 1 1970 ./app/js/private/test/image/non_ascii/bin2.runfiles/_main/js/private/test/image/non_ascii/bin2_/bin2
+-r-xr-xr-x 0 0 0 24120 Jan 1 1970 ./app/js/private/test/image/non_ascii/bin2.runfiles/_main/js/private/test/image/non_ascii/bin2_/bin2
drwxr-xr-x 0 0 0 0 Jan 1 1970 ./app/js/private/test/image/non_ascii/bin2.runfiles/_main/js/private/test/image/non_ascii/bin2_node_bin/
--r-xr-xr-x 0 0 0 133 Jan 1 1970 ./app/js/private/test/image/non_ascii/bin2.runfiles/_main/js/private/test/image/non_ascii/bin2_node_bin/node
+-r-xr-xr-x 0 0 0 411 Jan 1 1970 ./app/js/private/test/image/non_ascii/bin2.runfiles/_main/js/private/test/image/non_ascii/bin2_node_bin/node
-r-xr-xr-x 0 0 0 0 Jan 1 1970 ./app/js/private/test/image/non_ascii/bin2.runfiles/_main/js/private/test/image/non_ascii/empty empty.ㄴㅅ
-r-xr-xr-x 0 0 0 20 Jan 1 1970 ./app/js/private/test/image/non_ascii/bin2.runfiles/_main/js/private/test/image/non_ascii/main.js
diff --git a/js/private/test/image/non_ascii/custom_layer_groups_test_just_the_fs_patch.listing b/js/private/test/image/non_ascii/custom_layer_groups_test_just_the_fs_patch.listing
index 501708ac7..99fcdb3ef 100644
--- a/js/private/test/image/non_ascii/custom_layer_groups_test_just_the_fs_patch.listing
+++ b/js/private/test/image/non_ascii/custom_layer_groups_test_just_the_fs_patch.listing
@@ -9,4 +9,4 @@ drwxr-xr-x 0 0 0 0 Jan 1 1970 ./app/js/private/test/image/non_
drwxr-xr-x 0 0 0 0 Jan 1 1970 ./app/js/private/test/image/non_ascii/bin2.runfiles/_main/js/
drwxr-xr-x 0 0 0 0 Jan 1 1970 ./app/js/private/test/image/non_ascii/bin2.runfiles/_main/js/private/
drwxr-xr-x 0 0 0 0 Jan 1 1970 ./app/js/private/test/image/non_ascii/bin2.runfiles/_main/js/private/node-patches/
--r-xr-xr-x 0 0 0 34656 Jan 1 1970 ./app/js/private/test/image/non_ascii/bin2.runfiles/_main/js/private/node-patches/fs.cjs
+-r-xr-xr-x 0 0 0 35421 Jan 1 1970 ./app/js/private/test/image/non_ascii/bin2.runfiles/_main/js/private/node-patches/fs.cjs
diff --git a/js/private/test/image/non_ascii/custom_layer_groups_test_node.listing b/js/private/test/image/non_ascii/custom_layer_groups_test_node.listing
index 480acc973..0e23f0435 100644
--- a/js/private/test/image/non_ascii/custom_layer_groups_test_node.listing
+++ b/js/private/test/image/non_ascii/custom_layer_groups_test_node.listing
@@ -9,7 +9,8 @@ drwxr-xr-x 0 0 0 0 Jan 1 1970 ./app/js/private/test/image/non_
drwxr-xr-x 0 0 0 0 Jan 1 1970 ./app/js/private/test/image/non_ascii/bin2.runfiles/_main/js/
drwxr-xr-x 0 0 0 0 Jan 1 1970 ./app/js/private/test/image/non_ascii/bin2.runfiles/_main/js/private/
drwxr-xr-x 0 0 0 0 Jan 1 1970 ./app/js/private/test/image/non_ascii/bin2.runfiles/_main/js/private/node-patches/
--r-xr-xr-x 0 0 0 1460 Jan 1 1970 ./app/js/private/test/image/non_ascii/bin2.runfiles/_main/js/private/node-patches/register.cjs
+-r-xr-xr-x 0 0 0 6009 Jan 1 1970 ./app/js/private/test/image/non_ascii/bin2.runfiles/_main/js/private/node-patches/fs_stat.cjs
+-r-xr-xr-x 0 0 0 1631 Jan 1 1970 ./app/js/private/test/image/non_ascii/bin2.runfiles/_main/js/private/node-patches/register.cjs
drwxr-xr-x 0 0 0 0 Jan 1 1970 ./app/js/private/test/image/non_ascii/bin2.runfiles/rules_nodejs~~node~nodejs_linux_amd64/
drwxr-xr-x 0 0 0 0 Jan 1 1970 ./app/js/private/test/image/non_ascii/bin2.runfiles/rules_nodejs~~node~nodejs_linux_amd64/bin/
drwxr-xr-x 0 0 0 0 Jan 1 1970 ./app/js/private/test/image/non_ascii/bin2.runfiles/rules_nodejs~~node~nodejs_linux_amd64/bin/nodejs/
diff --git a/js/private/test/image/regex_edge_cases_test_app.listing b/js/private/test/image/regex_edge_cases_test_app.listing
index 1743a02cd..c83cda02f 100644
--- a/js/private/test/image/regex_edge_cases_test_app.listing
+++ b/js/private/test/image/regex_edge_cases_test_app.listing
@@ -3,7 +3,7 @@ drwxr-xr-x 0 0 0 0 Jan 1 1970 ./app/js/
drwxr-xr-x 0 0 0 0 Jan 1 1970 ./app/js/private/
drwxr-xr-x 0 0 0 0 Jan 1 1970 ./app/js/private/test/
drwxr-xr-x 0 0 0 0 Jan 1 1970 ./app/js/private/test/image/
--r-xr-xr-x 0 0 0 23981 Jan 1 1970 ./app/js/private/test/image/bin
+-r-xr-xr-x 0 0 0 24025 Jan 1 1970 ./app/js/private/test/image/bin
drwxr-xr-x 0 0 0 0 Jan 1 1970 ./app/js/private/test/image/bin.runfiles/
drwxr-xr-x 0 0 0 0 Jan 1 1970 ./app/js/private/test/image/bin.runfiles/_main/
drwxr-xr-x 0 0 0 0 Jan 1 1970 ./app/js/private/test/image/bin.runfiles/_main/examples/
@@ -17,7 +17,7 @@ drwxr-xr-x 0 0 0 0 Jan 1 1970 ./app/js/private/test/image/bin.
drwxr-xr-x 0 0 0 0 Jan 1 1970 ./app/js/private/test/image/bin.runfiles/_main/js/private/test/
drwxr-xr-x 0 0 0 0 Jan 1 1970 ./app/js/private/test/image/bin.runfiles/_main/js/private/test/image/
drwxr-xr-x 0 0 0 0 Jan 1 1970 ./app/js/private/test/image/bin.runfiles/_main/js/private/test/image/bin_/
--r-xr-xr-x 0 0 0 23981 Jan 1 1970 ./app/js/private/test/image/bin.runfiles/_main/js/private/test/image/bin_/bin
+-r-xr-xr-x 0 0 0 24025 Jan 1 1970 ./app/js/private/test/image/bin.runfiles/_main/js/private/test/image/bin_/bin
drwxr-xr-x 0 0 0 0 Jan 1 1970 ./app/js/private/test/image/bin.runfiles/_main/js/private/test/image/bin_node_bin/
--r-xr-xr-x 0 0 0 133 Jan 1 1970 ./app/js/private/test/image/bin.runfiles/_main/js/private/test/image/bin_node_bin/node
+-r-xr-xr-x 0 0 0 411 Jan 1 1970 ./app/js/private/test/image/bin.runfiles/_main/js/private/test/image/bin_node_bin/node
-r-xr-xr-x 0 0 0 20 Jan 1 1970 ./app/js/private/test/image/bin.runfiles/_main/js/private/test/image/main.js
diff --git a/js/private/test/image/regex_edge_cases_test_node.listing b/js/private/test/image/regex_edge_cases_test_node.listing
index c789e0f0c..97fed4a65 100644
--- a/js/private/test/image/regex_edge_cases_test_node.listing
+++ b/js/private/test/image/regex_edge_cases_test_node.listing
@@ -8,8 +8,9 @@ drwxr-xr-x 0 0 0 0 Jan 1 1970 ./app/js/private/test/image/bin.
drwxr-xr-x 0 0 0 0 Jan 1 1970 ./app/js/private/test/image/bin.runfiles/_main/js/
drwxr-xr-x 0 0 0 0 Jan 1 1970 ./app/js/private/test/image/bin.runfiles/_main/js/private/
drwxr-xr-x 0 0 0 0 Jan 1 1970 ./app/js/private/test/image/bin.runfiles/_main/js/private/node-patches/
--r-xr-xr-x 0 0 0 34656 Jan 1 1970 ./app/js/private/test/image/bin.runfiles/_main/js/private/node-patches/fs.cjs
--r-xr-xr-x 0 0 0 1460 Jan 1 1970 ./app/js/private/test/image/bin.runfiles/_main/js/private/node-patches/register.cjs
+-r-xr-xr-x 0 0 0 35421 Jan 1 1970 ./app/js/private/test/image/bin.runfiles/_main/js/private/node-patches/fs.cjs
+-r-xr-xr-x 0 0 0 6009 Jan 1 1970 ./app/js/private/test/image/bin.runfiles/_main/js/private/node-patches/fs_stat.cjs
+-r-xr-xr-x 0 0 0 1631 Jan 1 1970 ./app/js/private/test/image/bin.runfiles/_main/js/private/node-patches/register.cjs
drwxr-xr-x 0 0 0 0 Jan 1 1970 ./app/js/private/test/image/bin.runfiles/rules_nodejs~~node~nodejs_linux_amd64/
drwxr-xr-x 0 0 0 0 Jan 1 1970 ./app/js/private/test/image/bin.runfiles/rules_nodejs~~node~nodejs_linux_amd64/bin/
drwxr-xr-x 0 0 0 0 Jan 1 1970 ./app/js/private/test/image/bin.runfiles/rules_nodejs~~node~nodejs_linux_amd64/bin/nodejs/
diff --git a/js/private/test/node-patches/BUILD.bazel b/js/private/test/node-patches/BUILD.bazel
index 6f44f9458..60c098be2 100644
--- a/js/private/test/node-patches/BUILD.bazel
+++ b/js/private/test/node-patches/BUILD.bazel
@@ -81,6 +81,7 @@ babel_bin.babel(
"//js/private/node-patches/src:compile",
],
entry_point = "copy_entry_{}".format(t),
+ node_options = ["--expose-internals"],
node_toolchain = toolchain,
patch_node_fs = False,
# Without node patches on for these tests, the program is going to escape the sandbox if it
@@ -101,7 +102,8 @@ babel_bin.babel(
"//:node_modules/inline-fixtures",
"//js/private/node-patches/src:compile",
],
- entry_point = t,
+ entry_point = "copy_entry_{}".format(t) if (t in TESTS) else t,
+ node_options = ["--expose-internals"],
node_toolchain = toolchain,
patch_node_fs = False,
# Without node patches on for these tests, the program is going to escape the sandbox if it
diff --git a/js/private/test/snapshots/launcher.sh b/js/private/test/snapshots/launcher.sh
index 106b436e3..a8398c6b8 100644
--- a/js/private/test/snapshots/launcher.sh
+++ b/js/private/test/snapshots/launcher.sh
@@ -12,6 +12,7 @@
set -o pipefail -o errexit -o nounset
+export JS_BINARY__PATCH_NODE_ESM_LOADER="0"
export JS_BINARY__BINDIR="bazel-out/k8-fastbuild/bin"
export JS_BINARY__COMPILATION_MODE="fastbuild"
export JS_BINARY__TARGET_CPU="k8"