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:
Colin McDonnell
2026-02-24 14:39:39 +00:00
committed by pullfrog[bot]
parent da72d0d6ee
commit 73836d9c8f
2 changed files with 14 additions and 14 deletions
+3 -3
View File
@@ -143955,13 +143955,14 @@ function detectSandboxMethod() {
log.info("PID namespace isolation not available - falling back to env filtering only");
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) {
const spawnOpts = { env: params.env, cwd: params.cwd, stdio: params.stdio, detached: true };
const sandboxMethod = detectSandboxMethod();
if (sandboxMethod === "unshare") {
return spawn2(
"unshare",
["--pid", "--fork", "--mount-proc", "bash", "-c", params.command],
["--pid", "--fork", "--mount-proc", "bash", "-c", `${PROC_CLEANUP} ${params.command}`],
spawnOpts
);
}
@@ -143983,10 +143984,9 @@ function spawnShell(params) {
"--mount-proc",
"bash",
"-c",
params.command
`${PROC_CLEANUP} ${params.command}`
],
{ ...spawnOpts, env: {} }
// empty env since we pass via sudo env
);
}
return spawn2("bash", ["-c", params.command], spawnOpts);
+11 -11
View File
@@ -82,27 +82,27 @@ function detectSandboxMethod(): SandboxMethod {
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 {
const spawnOpts = { env: params.env, cwd: params.cwd, stdio: params.stdio, detached: true };
const sandboxMethod = detectSandboxMethod();
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(
"unshare",
["--pid", "--fork", "--mount-proc", "bash", "-c", params.command],
["--pid", "--fork", "--mount-proc", "bash", "-c", `${PROC_CLEANUP} ${params.command}`],
spawnOpts
);
}
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[] = [];
for (const [k, v] of Object.entries(params.env)) {
if (v !== undefined) {
@@ -120,9 +120,9 @@ function spawnShell(params: SpawnParams): ChildProcess {
"--mount-proc",
"bash",
"-c",
params.command,
`${PROC_CLEANUP} ${params.command}`,
],
{ ...spawnOpts, env: {} } // empty env since we pass via sudo env
{ ...spawnOpts, env: {} }
);
}