feat(core): populate sandbox forbidden paths with project ignore file contents (#24038)

This commit is contained in:
Emily Hedlund
2026-04-01 12:27:55 -04:00
committed by GitHub
parent 066da2a1d1
commit 6a8a0d4faa
13 changed files with 309 additions and 79 deletions
@@ -420,7 +420,7 @@ describe('LinuxSandboxManager', () => {
const customManager = new LinuxSandboxManager({
workspace,
forbiddenPaths: ['/tmp/cache', '/opt/secret.txt'],
forbiddenPaths: async () => ['/tmp/cache', '/opt/secret.txt'],
});
const bwrapArgs = await getBwrapArgs(
@@ -452,7 +452,7 @@ describe('LinuxSandboxManager', () => {
const customManager = new LinuxSandboxManager({
workspace,
forbiddenPaths: ['/tmp/forbidden-symlink'],
forbiddenPaths: async () => ['/tmp/forbidden-symlink'],
});
const bwrapArgs = await getBwrapArgs(
@@ -480,7 +480,7 @@ describe('LinuxSandboxManager', () => {
const customManager = new LinuxSandboxManager({
workspace,
forbiddenPaths: ['/tmp/not-here.txt'],
forbiddenPaths: async () => ['/tmp/not-here.txt'],
});
const bwrapArgs = await getBwrapArgs(
@@ -509,7 +509,7 @@ describe('LinuxSandboxManager', () => {
const customManager = new LinuxSandboxManager({
workspace,
forbiddenPaths: ['/tmp/dir-link'],
forbiddenPaths: async () => ['/tmp/dir-link'],
});
const bwrapArgs = await getBwrapArgs(
@@ -534,7 +534,7 @@ describe('LinuxSandboxManager', () => {
const customManager = new LinuxSandboxManager({
workspace,
forbiddenPaths: ['/tmp/conflict'],
forbiddenPaths: async () => ['/tmp/conflict'],
});
const bwrapArgs = await getBwrapArgs(
@@ -550,12 +550,14 @@ describe('LinuxSandboxManager', () => {
customManager,
);
const bindTryIdx = bwrapArgs.indexOf('--bind-try');
const tmpfsIdx = bwrapArgs.lastIndexOf('--tmpfs');
// Conflict should have been filtered out of allow list (--bind-try)
expect(bwrapArgs).not.toContain('--bind-try');
expect(bwrapArgs).not.toContain('--bind-try-ro');
expect(bwrapArgs[bindTryIdx + 1]).toBe('/tmp/conflict');
expect(bwrapArgs[tmpfsIdx + 1]).toBe('/tmp/conflict');
expect(tmpfsIdx).toBeGreaterThan(bindTryIdx);
// It should only appear as a forbidden path (via --tmpfs)
const conflictIdx = bwrapArgs.indexOf('/tmp/conflict');
expect(conflictIdx).toBeGreaterThan(0);
expect(bwrapArgs[conflictIdx - 1]).toBe('--tmpfs');
});
});
});
@@ -17,6 +17,7 @@ import {
getSecretFileFindArgs,
sanitizePaths,
type ParsedSandboxDenial,
resolveSandboxPaths,
} from '../../services/sandboxManager.js';
import type { ShellExecutionResult } from '../../services/shellExecutionService.js';
import {
@@ -294,7 +295,7 @@ export class LinuxSandboxManager implements SandboxManager {
bwrapArgs.push(bindFlag, mainGitDir, mainGitDir);
}
const includeDirs = sanitizePaths(this.options.includeDirectories) || [];
const includeDirs = sanitizePaths(this.options.includeDirectories);
for (const includeDir of includeDirs) {
try {
const resolved = tryRealpath(includeDir);
@@ -304,7 +305,8 @@ export class LinuxSandboxManager implements SandboxManager {
}
}
const allowedPaths = sanitizePaths(req.policy?.allowedPaths) || [];
const { allowed: allowedPaths, forbidden: forbiddenPaths } =
await resolveSandboxPaths(this.options, req);
const normalizedWorkspace = normalize(workspacePath).replace(/\/$/, '');
for (const allowedPath of allowedPaths) {
@@ -330,8 +332,7 @@ export class LinuxSandboxManager implements SandboxManager {
}
}
const additionalReads =
sanitizePaths(mergedAdditional.fileSystem?.read) || [];
const additionalReads = sanitizePaths(mergedAdditional.fileSystem?.read);
for (const p of additionalReads) {
try {
const safeResolvedPath = tryRealpath(p);
@@ -341,8 +342,7 @@ export class LinuxSandboxManager implements SandboxManager {
}
}
const additionalWrites =
sanitizePaths(mergedAdditional.fileSystem?.write) || [];
const additionalWrites = sanitizePaths(mergedAdditional.fileSystem?.write);
for (const p of additionalWrites) {
try {
const safeResolvedPath = tryRealpath(p);
@@ -362,7 +362,6 @@ export class LinuxSandboxManager implements SandboxManager {
}
}
const forbiddenPaths = sanitizePaths(this.options.forbiddenPaths) || [];
for (const p of forbiddenPaths) {
let resolved: string;
try {