harden proc isolation and filter cross-fork check suite PRs
shell sandbox: double-umount + remount /proc to prevent exfiltration when agent peels off --mount-proc overlay. webhooks: filter check_suite.pull_requests to same-repo PRs only (excludes cross-fork sync PRs) and skip merged/closed PRs. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
committed by
pullfrog[bot]
parent
da72d0d6ee
commit
73836d9c8f
@@ -143955,13 +143955,14 @@ function detectSandboxMethod() {
|
|||||||
log.info("PID namespace isolation not available - falling back to env filtering only");
|
log.info("PID namespace isolation not available - falling back to env filtering only");
|
||||||
return "none";
|
return "none";
|
||||||
}
|
}
|
||||||
|
var PROC_CLEANUP = "umount /proc 2>/dev/null; umount /proc 2>/dev/null; mount -t proc proc /proc 2>/dev/null;";
|
||||||
function spawnShell(params) {
|
function spawnShell(params) {
|
||||||
const spawnOpts = { env: params.env, cwd: params.cwd, stdio: params.stdio, detached: true };
|
const spawnOpts = { env: params.env, cwd: params.cwd, stdio: params.stdio, detached: true };
|
||||||
const sandboxMethod = detectSandboxMethod();
|
const sandboxMethod = detectSandboxMethod();
|
||||||
if (sandboxMethod === "unshare") {
|
if (sandboxMethod === "unshare") {
|
||||||
return spawn2(
|
return spawn2(
|
||||||
"unshare",
|
"unshare",
|
||||||
["--pid", "--fork", "--mount-proc", "bash", "-c", params.command],
|
["--pid", "--fork", "--mount-proc", "bash", "-c", `${PROC_CLEANUP} ${params.command}`],
|
||||||
spawnOpts
|
spawnOpts
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -143983,10 +143984,9 @@ function spawnShell(params) {
|
|||||||
"--mount-proc",
|
"--mount-proc",
|
||||||
"bash",
|
"bash",
|
||||||
"-c",
|
"-c",
|
||||||
params.command
|
`${PROC_CLEANUP} ${params.command}`
|
||||||
],
|
],
|
||||||
{ ...spawnOpts, env: {} }
|
{ ...spawnOpts, env: {} }
|
||||||
// empty env since we pass via sudo env
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return spawn2("bash", ["-c", params.command], spawnOpts);
|
return spawn2("bash", ["-c", params.command], spawnOpts);
|
||||||
|
|||||||
+11
-11
@@ -82,27 +82,27 @@ function detectSandboxMethod(): SandboxMethod {
|
|||||||
return "none";
|
return "none";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// strip inherited proc mount that sits underneath --mount-proc's overlay.
|
||||||
|
// --mount-proc mounts fresh proc on top, but `umount /proc` peels it off and exposes the
|
||||||
|
// host's proc with all host PIDs — allowing /proc/<pid>/environ exfiltration.
|
||||||
|
// double-umount removes both layers, then a clean mount gives only sandbox PIDs.
|
||||||
|
// on unprivileged systems where umount fails, --mount-proc still provides isolation
|
||||||
|
// (the agent also can't umount in that case).
|
||||||
|
const PROC_CLEANUP = "umount /proc 2>/dev/null; umount /proc 2>/dev/null; mount -t proc proc /proc 2>/dev/null;";
|
||||||
|
|
||||||
function spawnShell(params: SpawnParams): ChildProcess {
|
function spawnShell(params: SpawnParams): ChildProcess {
|
||||||
const spawnOpts = { env: params.env, cwd: params.cwd, stdio: params.stdio, detached: true };
|
const spawnOpts = { env: params.env, cwd: params.cwd, stdio: params.stdio, detached: true };
|
||||||
const sandboxMethod = detectSandboxMethod();
|
const sandboxMethod = detectSandboxMethod();
|
||||||
|
|
||||||
if (sandboxMethod === "unshare") {
|
if (sandboxMethod === "unshare") {
|
||||||
// use PID namespace isolation to prevent reading /proc/$PPID/environ
|
|
||||||
// this creates a new PID namespace where:
|
|
||||||
// 1. the subprocess becomes PID 1 in its namespace
|
|
||||||
// 2. parent PIDs are not visible (PPID = 0)
|
|
||||||
// 3. fresh /proc is mounted showing only sandbox PIDs
|
|
||||||
// combined with resolveEnv("restricted"), this prevents all /proc-based secret theft
|
|
||||||
return spawn(
|
return spawn(
|
||||||
"unshare",
|
"unshare",
|
||||||
["--pid", "--fork", "--mount-proc", "bash", "-c", params.command],
|
["--pid", "--fork", "--mount-proc", "bash", "-c", `${PROC_CLEANUP} ${params.command}`],
|
||||||
spawnOpts
|
spawnOpts
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sandboxMethod === "sudo-unshare") {
|
if (sandboxMethod === "sudo-unshare") {
|
||||||
// on GHA runners, unprivileged namespaces are blocked but sudo works
|
|
||||||
// pass filtered env via sudo env command since sudo clears environment
|
|
||||||
const envArgs: string[] = [];
|
const envArgs: string[] = [];
|
||||||
for (const [k, v] of Object.entries(params.env)) {
|
for (const [k, v] of Object.entries(params.env)) {
|
||||||
if (v !== undefined) {
|
if (v !== undefined) {
|
||||||
@@ -120,9 +120,9 @@ function spawnShell(params: SpawnParams): ChildProcess {
|
|||||||
"--mount-proc",
|
"--mount-proc",
|
||||||
"bash",
|
"bash",
|
||||||
"-c",
|
"-c",
|
||||||
params.command,
|
`${PROC_CLEANUP} ${params.command}`,
|
||||||
],
|
],
|
||||||
{ ...spawnOpts, env: {} } // empty env since we pass via sudo env
|
{ ...spawnOpts, env: {} }
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user