Fix enableWeakerNestedSandbox after apply-seccomp namespace changes

apply-seccomp now creates a nested userns and writes /proc/self/setgroups
and uid_map before applying the seccomp filter. That broke
enableWeakerNestedSandbox in two ways:

  1. Without --proc, bwrap's --ro-bind / / leaves /proc read-only.
     apply-seccomp's setgroups write dies with EROFS.

  2. In unprivileged Docker (the flag's target), apply-seccomp's proc
     remount fails the kernel domination check — Docker's /proc masks
     are MNT_LOCKED in the less-privileged nested userns.

And the reason bwrap never got that far in Docker: bwrap only auto-adds
--unshare-user when EUID != 0. Docker's default is EUID=0 without
CAP_SYS_ADMIN; bwrap assumes it has caps, tries direct clone(NEWPID),
and EPERMs before apply-seccomp runs.

Changes:

  - bwrap args for weak mode: --unshare-user (force userns even as
    EUID=0) and --bind /proc /proc (restore rw /proc for setgroups)
  - apply-seccomp: tolerate mount(/proc) EPERM. The nested userns is
    the isolation boundary; the proc remount only hides outer PIDs
    from `ls /proc`.

Fixes the two failing mandatory-deny-paths tests that exercise
enableWeakerNestedSandbox. No test changes required.

Bump version to 0.0.46.
This commit is contained in:
Dylan Conway
2026-03-31 12:21:57 -07:00
parent bc1ab82928
commit 72e477b62d
6 changed files with 22 additions and 13 deletions

15
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "@anthropic-ai/sandbox-runtime",
"version": "0.0.45",
"version": "0.0.46",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@anthropic-ai/sandbox-runtime",
"version": "0.0.45",
"version": "0.0.46",
"license": "Apache-2.0",
"dependencies": {
"@pondwader/socks5-server": "^1.0.10",
@@ -502,7 +502,6 @@
"integrity": "sha512-BnOroVl1SgrPLywqxyqdJ4l3S2MsKVLDVxZvjI1Eoe8ev2r3kGDo+PcMihNmDE+6/KjkTubSJnmqGZZjQSBq/g==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@typescript-eslint/scope-manager": "8.46.2",
"@typescript-eslint/types": "8.46.2",
@@ -1003,7 +1002,6 @@
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
"dev": true,
"license": "MIT",
"peer": true,
"bin": {
"acorn": "bin/acorn"
},
@@ -1455,7 +1453,8 @@
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.2.tgz",
"integrity": "sha512-D80T+tiqkd/8B0xNlbstWDG4x6aqVfO52+OlSUNIdkTvmNw0uQpJLeos2J/2XvpyidAFuTPmpad+tUxLndwj6g==",
"dev": true,
"license": "MIT"
"license": "MIT",
"peer": true
},
"node_modules/data-view-buffer": {
"version": "1.0.2",
@@ -1802,7 +1801,6 @@
"integrity": "sha512-t5aPOpmtJcZcz5UJyY2GbvpDlsK5E8JqRqoKtfiKE3cNh437KIqfJr3A3AKf5k64NPx6d0G3dno6XDY05PqPtw==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.8.0",
"@eslint-community/regexpp": "^4.12.1",
@@ -1892,7 +1890,6 @@
"integrity": "sha512-/IGJ6+Dka158JnP5n5YFMOszjDWrXggGz1LaK/guZq9vZTmniaKlHcsscvkAhn9y4U+BU3JuUdYvtAMcv30y4A==",
"dev": true,
"license": "MIT",
"peer": true,
"bin": {
"eslint-config-prettier": "bin/cli.js"
},
@@ -2013,7 +2010,6 @@
"integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"@rtsao/scc": "^1.1.0",
"array-includes": "^3.1.9",
@@ -3734,7 +3730,6 @@
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"dev": true,
"license": "MIT",
"peer": true,
"engines": {
"node": ">=12"
},
@@ -3781,7 +3776,6 @@
"integrity": "sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==",
"dev": true,
"license": "MIT",
"peer": true,
"bin": {
"prettier": "bin/prettier.cjs"
},
@@ -4634,7 +4628,6 @@
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
"dev": true,
"license": "Apache-2.0",
"peer": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"

View File

@@ -1,6 +1,6 @@
{
"name": "@anthropic-ai/sandbox-runtime",
"version": "0.0.45",
"version": "0.0.46",
"description": "Anthropic Sandbox Runtime (ASRT) - A general-purpose tool for wrapping security boundaries around arbitrary processes",
"type": "module",
"main": "./dist/index.js",

View File

@@ -1212,6 +1212,16 @@ export async function wrapCommandWithSandboxLinux(
if (!enableWeakerNestedSandbox) {
// Mount fresh /proc if PID namespace is isolated (secure mode)
bwrapArgs.push('--proc', '/proc')
} else {
// --unshare-user: bwrap only auto-adds this when EUID != 0. In an
// unprivileged container (Docker's default: EUID=0 without
// CAP_SYS_ADMIN), bwrap assumes it has caps, tries direct clone,
// and EPERMs. Force the userns path so bwrap starts at all.
//
// --bind /proc /proc: apply-seccomp's nested-userns path writes
// /proc/self/setgroups and uid_map. Without --proc above, the
// --ro-bind / / leaves /proc read-only and those writes EROFS.
bwrapArgs.push('--unshare-user', '--bind', '/proc', '/proc')
}
// apply-seccomp obtains CAP_SYS_ADMIN for its nested PID+mount unshare

View File

@@ -246,7 +246,13 @@ int main(int argc, char *argv[]) {
if (mount(NULL, "/", NULL, MS_REC | MS_PRIVATE, NULL) < 0) {
die("apply-seccomp: mount(MS_PRIVATE)");
}
if (mount("proc", "/proc", "proc", MS_NOSUID | MS_NODEV | MS_NOEXEC, NULL) < 0) {
/* EPERM here means a masked /proc is underneath (unprivileged Docker)
* and the kernel domination check refused the overmount. The nested
* userns above is the isolation boundary; this remount only hides
* outer PIDs from `ls /proc`. enableWeakerNestedSandbox targets
* exactly this environment. */
if (mount("proc", "/proc", "proc", MS_NOSUID | MS_NODEV | MS_NOEXEC, NULL) < 0
&& errno != EPERM) {
die("apply-seccomp: mount(/proc)");
}

Binary file not shown.

Binary file not shown.