From c3ef2f94d7e170630c0b6bab526862826a4d3d1e Mon Sep 17 00:00:00 2001 From: Teffen Ellis <592134+GirlBossRush@users.noreply.github.com> Date: Tue, 19 May 2026 13:43:50 +0200 Subject: [PATCH] ci: run publish build under Node's permission model (experiment) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Wraps the build phase of the npm publish workflow (`tsc -p .`, plus `typedoc` for `packages/esbuild-plugin-live-reload`) in Node 24's permission model. Configuration: node --permission \ --allow-fs-read="$PWD" \ --allow-fs-write="$PWD" \ ./node_modules/typescript/bin/tsc -p . `--permission` with no other `--allow` flags denies by default: - network (--allow-net) - child_process (--allow-child-process) - worker_threads (--allow-worker) - native addons - WASI fs reads and writes are scoped to the package working tree. The threat model addressed: a malicious devDep (direct or transitive) that wakes up during `tsc` and tries to exfil credentials. It can still read process.env, but with no network, no subprocess, and no fs write outside `$PWD`, it has no channel to send anything out. `npm ci` and `npm publish` keep their full capability set — both legitimately need network and subprocess access, and `npm publish`'s defense lives at the runner egress layer (step-security/harden-runner, added in #22463). This is the smallest tractable surface for the permission model in this repo: all six publishable packages build via `tsc` (one also runs `typedoc`), with no native addons and no in-build child processes. If a future package needs broader permissions, the allow-list can be widened per-step rather than discarded. The Node permission model is still in active development (stability 1.1 in v24); the API surface is stable enough to commit to but the "experiment" framing is intentional — if a downstream tsc/typedoc revision starts touching paths outside `$PWD`, revert is one-line. Co-authored-by: Agent <279763771+playpen-agent@users.noreply.github.com> --- .github/workflows/packages-npm-publish.yml | 28 +++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/.github/workflows/packages-npm-publish.yml b/.github/workflows/packages-npm-publish.yml index 5f97c5b465..45ccbd1360 100644 --- a/.github/workflows/packages-npm-publish.yml +++ b/.github/workflows/packages-npm-publish.yml @@ -47,7 +47,33 @@ jobs: - name: Publish package if: steps.changed-files.outputs.any_changed == 'true' working-directory: ${{ matrix.package }} + # Experimental: the build (tsc, plus typedoc for one package) runs + # under Node's permission model. fs reads/writes are scoped to the + # package working tree; --permission with no other --allow flags + # blocks network, child_process, worker_threads, native addons, + # and WASI by default. + # + # Trade-off: env vars are still readable (the permission model + # does not gate them), but with no exfil channel — no network, + # no subprocess, no write outside $PWD — they cannot leave the + # build sandbox. The npm ci and npm publish phases keep their + # full capability set since they legitimately need network and + # subprocess access. run: | corepack npm ci - corepack npm run build + + WORK="$PWD" + node \ + --permission \ + --allow-fs-read="$WORK" \ + --allow-fs-write="$WORK" \ + ./node_modules/typescript/bin/tsc -p . + if [ -x ./node_modules/typedoc/bin/typedoc ]; then + node \ + --permission \ + --allow-fs-read="$WORK" \ + --allow-fs-write="$WORK" \ + ./node_modules/typedoc/bin/typedoc + fi + corepack npm publish