A landlock publication failed with `E409 Failed to save packument` on the second of three packages. The registry answers a write it could not commit that way, and publishing several packages back to back is what provokes it. Neither publish path could recover. The native sequence published from a shell loop of bare `npm publish` calls: no retry, and no way to resume, because the registry rejects a repeat of an existing version permanently — so a failure partway through left the release stuck. publish.ts skipped versions already present, which made a re-run safe, but had no retry either. Both paths now attempt a tarball up to four times, space writes at least two seconds apart, and back off 2s/4s/8s between attempts. Every retry re-reads the registry first, because a reported failure can answer a write that landed anyway: a version that now exists with this tarball's integrity counts as published rather than as one to place again. That same re-read is what turns a mid-run `E403 cannot publish over the previously published versions` into a skip when the bytes match, and leaves it a hard failure when they do not. The native sequence gets the registry comparison publish.ts already had, through its own script rather than shared code — the two sequences keep separate publication paths. Its publish job now checks out the repository, which the shell loop did not need. Verified against a scripted registry: a clean publish, one E409 then success, an E409 whose write landed anyway, E409 on every attempt (fails after four), and a version already present with matching integrity (publishes nothing).
@deepseek-ai/node-addon-landlock-run
English | 中文
A Landlock self-restrict-then-exec launcher for confining subprocesses on Linux, distributed as prebuilt per-platform npm packages plus a thin JS entry package that resolves the binary and speaks its CLI contract. Built for agent harnesses and other hosts that need to run untrusted commands under a filesystem allow-list without confining themselves.
The tool is landlock-run — a self-restrict-then-exec Landlock launcher (~300 lines of C11 over the raw kernel UAPI, statically linked against musl). It installs a Landlock ruleset on itself and execs the wrapped command; the ruleset is inherited across execve, so the command and every process it spawns run confined while the invoking process stays unrestricted. Fail-closed: if the kernel cannot enforce, it exits without running the command.
Install
npm install @deepseek-ai/node-addon-landlock-run
Published packages use an entry package plus platform optional packages:
@deepseek-ai/node-addon-landlock-run
@deepseek-ai/node-addon-landlock-run-linux-x64
@deepseek-ai/node-addon-landlock-run-linux-arm64
npm's os/cpu fields make installers fetch only the matching platform package. There is no install-time build fallback on purpose: on a host without a platform package the resolved path never exists, the probe reports unusable, and the consumer falls closed.
Usage
import { grantArgs, launcherPath, probe } from '@deepseek-ai/node-addon-landlock-run';
const launcher = launcherPath();
if (probe(launcher) !== 'unusable') {
const argv = [launcher, ...grantArgs({ readOnly: ['/'], readWrite: ['/tmp/work'] }), '--', 'bash', '-c', command];
// spawn argv with your process runner of choice
}
The public API is intentionally small:
launcherPath(): absolute path of this host's launcher (existence deliberately unchecked — the probe is the availability signal).probe(launcher?, { timeoutMs? }): functional enforcement probe —'full' | 'partial' | 'unusable'.grantArgs({ readOnly?, readWrite? }): the launcher's grant argv; everything not granted is denied.LAUNCHER_BINandLAUNCHER_FAILURE_EXIT(125): contract constants. A successfully exec'd child may also return 125, so consumers need the fatal diagnostic as well as the status to attribute launcher failure.
The full binary contract (argv grammar, exit codes, report lines) is pinned in docs/cli-contract.md.
Support
linux-x64 and linux-arm64, kernel with Landlock enabled (5.13+; ABI level determines full vs partial enforcement — see docs/support-matrix.md). Other platforms deliberately have no package: consumers run different confinement backends there.
Development
corepack enable
pnpm install
pnpm build:ts # entry packages → lib/
pnpm build:native # this Linux architecture's binaries (apt-get install musl-tools)
pnpm test
Binaries are git-ignored and built natively per architecture — locally for your own machine, by CI's per-arch runners as the builders of record. Release flow: docs/release.md.