Problem
Running Node.js built-in fetch() inside nsjail fails with a Wasm OOM during undici initialization:
RangeError: WebAssembly.instantiate(): Out of memory: Cannot allocate Wasm memory for new instance
at lazyllhttp (node:internal/deps/undici/undici:5829:32)
The failure path is:
- Node.js built-in
fetch
undici
lazyllhttp
WebAssembly.instantiate()
The same script works outside nsjail.
I reproduced this with a minimal Docker environment containing only:
- Debian bookworm-slim
- Node.js 22.19.0
- nsjail 3.6
The sandbox configuration is roughly:
clone_newnet: false
clone_newuser/newns/newpid/newipc/newuts: true
- bind mounts for
/usr, /lib, /lib64, /proc, and basic DNS/SSL files
--keep_env
--disable_clone_newcgroup
--rlimit_as <value>
With lower RLIMIT_AS, Node may fail even earlier during V8 startup with:
Fatal process out of memory: Failed to reserve virtual memory for CodeRange
With a higher RLIMIT_AS value, Node starts, but fetch() still fails in WebAssembly.instantiate().
This looks like an interaction between nsjail memory/address-space limits and Node/undici Wasm initialization, rather than an application-level memory issue.
I would like to know whether this is:
- expected behavior under
nsjail
- a known limitation with
RLIMIT_AS
- or a bug / unsupported case for this workload
This is a min reproducible environment version.
you just need run:
docker build -f "Dockerfile.nsjail" -t callit-nsjail-repro "." && docker run --rm --privileged -e RLIMIT_AS_MB=4096 callit-nsjail-repro bash -lc '/repro/run-fetch-in-nsjail.sh'
FROM debian:bookworm-slim
ARG NODE_VERSION=22.19.0
ARG NODE_ARCH=linux-x64
ARG NSJAIL_VERSION=3.6
WORKDIR /repro
ENV PATH=/usr/local/nodejs/bin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
autoconf \
bison \
ca-certificates \
curl \
flex \
g++ \
gcc \
git \
libnl-route-3-dev \
libprotobuf-dev \
libtool \
make \
pkg-config \
protobuf-compiler \
xz-utils \
&& rm -rf /var/lib/apt/lists/*
RUN mkdir -p /usr/local/nodejs \
&& curl -fsSL "https://nodejs.org/dist/v${NODE_VERSION}/node-v${NODE_VERSION}-${NODE_ARCH}.tar.xz" -o /tmp/node.tar.xz \
&& tar -xJf /tmp/node.tar.xz -C /usr/local/nodejs --strip-components=1 \
&& rm -f /tmp/node.tar.xz \
&& ln -sf /usr/local/nodejs/bin/node /usr/local/bin/node \
&& ln -sf /usr/local/nodejs/bin/npm /usr/local/bin/npm \
&& ln -sf /usr/local/nodejs/bin/npx /usr/local/bin/npx
RUN git clone --branch "${NSJAIL_VERSION}" --depth 1 https://github.com/google/nsjail.git /tmp/nsjail-src \
&& cd /tmp/nsjail-src \
&& make \
&& install -m 0755 /tmp/nsjail-src/nsjail /usr/local/bin/nsjail \
&& rm -rf /tmp/nsjail-src
RUN cat <<'EOF' > /repro/fetch-test.mjs
const targetUrl = process.argv[2] ?? 'https://example.com';
console.log(`[fetch-test] url=${targetUrl}`);
const response = await fetch(targetUrl);
const responseText = await response.text();
console.log(`[fetch-test] status=${response.status}`);
console.log(responseText.slice(0, 200));
EOF
RUN cat <<'EOF' > /repro/nsjail-node-fetch.cfg
name: "node-fetch-repro"
mode: ONCE
hostname: "nsjail-repro"
cwd: "/repro"
clone_newnet: false
clone_newuser: true
clone_newns: true
clone_newpid: true
clone_newipc: true
clone_newuts: true
uidmap {
inside_id: "0"
outside_id: ""
count: 1
}
gidmap {
inside_id: "0"
outside_id: ""
count: 1
}
mount {
src: "/repro"
dst: "/repro"
is_bind: true
rw: false
}
mount {
src: "/usr"
dst: "/usr"
is_bind: true
rw: false
}
mount {
src: "/lib"
dst: "/lib"
is_bind: true
rw: false
}
mount {
src: "/lib64"
dst: "/lib64"
is_bind: true
rw: false
}
mount {
src: "/etc/resolv.conf"
dst: "/etc/resolv.conf"
is_bind: true
rw: false
}
mount {
src: "/etc/hosts"
dst: "/etc/hosts"
is_bind: true
rw: false
}
mount {
src: "/etc/nsswitch.conf"
dst: "/etc/nsswitch.conf"
is_bind: true
rw: false
}
mount {
src: "/etc/gai.conf"
dst: "/etc/gai.conf"
is_bind: true
rw: false
}
mount {
src: "/etc/ssl/certs"
dst: "/etc/ssl/certs"
is_bind: true
rw: false
}
mount {
src: "/etc/ca-certificates.conf"
dst: "/etc/ca-certificates.conf"
is_bind: true
rw: false
}
mount {
src: "/etc/ssl/openssl.cnf"
dst: "/etc/ssl/openssl.cnf"
is_bind: true
rw: false
}
mount {
dst: "/proc"
fstype: "proc"
}
EOF
RUN cat <<'EOF' > /repro/run-fetch-in-nsjail.sh
#!/usr/bin/env bash
set -euo pipefail
TARGET_URL="${1:-https://example.com}"
RLIMIT_AS_MB="${RLIMIT_AS_MB:-4096}"
echo "[nsjail-repro] node=$(node --version)"
echo "[nsjail-repro] nsjail=$(nsjail --version 2>/dev/null || true)"
echo "[nsjail-repro] url=${TARGET_URL}"
echo "[nsjail-repro] rlimit_as_mb=${RLIMIT_AS_MB}"
exec nsjail \
--config /repro/nsjail-node-fetch.cfg \
--log_fd 1 \
--keep_env \
--disable_clone_newcgroup \
--rlimit_as "${RLIMIT_AS_MB}" \
-- /usr/local/bin/node /repro/fetch-test.mjs "${TARGET_URL}"
EOF
RUN chmod +x /repro/run-fetch-in-nsjail.sh
CMD ["bash"]
Problem
Running Node.js built-in
fetch()insidensjailfails with a Wasm OOM duringundiciinitialization:RangeError: WebAssembly.instantiate(): Out of memory: Cannot allocate Wasm memory for new instance at lazyllhttp (node:internal/deps/undici/undici:5829:32)The failure path is:
fetchundicilazyllhttpWebAssembly.instantiate()The same script works outside
nsjail.I reproduced this with a minimal Docker environment containing only:
The sandbox configuration is roughly:
clone_newnet: falseclone_newuser/newns/newpid/newipc/newuts: true/usr,/lib,/lib64,/proc, and basic DNS/SSL files--keep_env--disable_clone_newcgroup--rlimit_as <value>With lower
RLIMIT_AS, Node may fail even earlier during V8 startup with:With a higher
RLIMIT_ASvalue, Node starts, butfetch()still fails inWebAssembly.instantiate().This looks like an interaction between
nsjailmemory/address-space limits and Node/undici Wasm initialization, rather than an application-level memory issue.I would like to know whether this is:
nsjailRLIMIT_ASThis is a min reproducible environment version.
you just need run: