mirror of
https://github.com/anthropic-experimental/sandbox-runtime.git
synced 2026-05-06 21:52:30 +08:00
Merge pull request #153 from patrick-premont/patrick/fix-allowwrite-glob-stripping
fix: strip glob suffixes from allowWrite/denyWrite on Linux
This commit is contained in:
@@ -519,12 +519,30 @@ async function wrapWithSandbox(
|
||||
// Get configs - use custom if provided, otherwise fall back to main config
|
||||
// If neither exists, defaults to empty arrays (most restrictive)
|
||||
// Always include default system write paths (like /dev/null, /tmp/claude)
|
||||
const userAllowWrite =
|
||||
customConfig?.filesystem?.allowWrite ?? config?.filesystem.allowWrite ?? []
|
||||
//
|
||||
// Strip trailing /** and filter remaining globs on Linux (bwrap needs
|
||||
// real paths, not globs; macOS subpath matching is also recursive so
|
||||
// stripping is harmless there).
|
||||
const stripWriteGlobs = (paths: string[]): string[] =>
|
||||
paths
|
||||
.map(p => removeTrailingGlobSuffix(p))
|
||||
.filter(p => {
|
||||
if (getPlatform() === 'linux' && containsGlobChars(p)) {
|
||||
logForDebugging(
|
||||
`[Sandbox] Skipping glob write pattern on Linux: ${p}`,
|
||||
)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
const userAllowWrite = stripWriteGlobs(
|
||||
customConfig?.filesystem?.allowWrite ?? config?.filesystem.allowWrite ?? [],
|
||||
)
|
||||
const writeConfig = {
|
||||
allowOnly: [...getDefaultWritePaths(), ...userAllowWrite],
|
||||
denyWithinAllow:
|
||||
denyWithinAllow: stripWriteGlobs(
|
||||
customConfig?.filesystem?.denyWrite ?? config?.filesystem.denyWrite ?? [],
|
||||
),
|
||||
}
|
||||
const rawDenyRead =
|
||||
customConfig?.filesystem?.denyRead ?? config?.filesystem.denyRead ?? []
|
||||
|
||||
@@ -69,7 +69,8 @@ export function containsGlobChars(pathPattern: string): boolean {
|
||||
* Used to normalize path patterns since /** just means "directory and everything under it"
|
||||
*/
|
||||
export function removeTrailingGlobSuffix(pathPattern: string): string {
|
||||
return pathPattern.replace(/\/\*\*$/, '')
|
||||
const stripped = pathPattern.replace(/\/\*\*$/, '')
|
||||
return stripped || '/'
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -4,6 +4,9 @@ import type { SandboxRuntimeConfig } from '../../src/sandbox/sandbox-config.js'
|
||||
import { getPlatform } from '../../src/utils/platform.js'
|
||||
import { wrapCommandWithSandboxLinux } from '../../src/sandbox/linux-sandbox-utils.js'
|
||||
import { wrapCommandWithSandboxMacOS } from '../../src/sandbox/macos-sandbox-utils.js'
|
||||
import { mkdirSync, rmSync } from 'node:fs'
|
||||
import { tmpdir } from 'node:os'
|
||||
import { join } from 'node:path'
|
||||
|
||||
/**
|
||||
* Create a test configuration with network access
|
||||
@@ -651,3 +654,67 @@ describe('empty allowedDomains network blocking (CVE fix)', () => {
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('allowWrite glob suffix handling', () => {
|
||||
const command = 'echo hello'
|
||||
|
||||
it('allowWrite with /** suffix includes path in sandbox command', async () => {
|
||||
if (skipIfUnsupportedPlatform()) {
|
||||
return
|
||||
}
|
||||
|
||||
const testDir = join(tmpdir(), `srt-test-glob-allow-${Date.now()}`)
|
||||
mkdirSync(testDir, { recursive: true })
|
||||
|
||||
try {
|
||||
await SandboxManager.reset()
|
||||
await SandboxManager.initialize({
|
||||
network: { allowedDomains: [], deniedDomains: [] },
|
||||
filesystem: {
|
||||
denyRead: [],
|
||||
allowWrite: [`${testDir}/**`],
|
||||
denyWrite: [],
|
||||
},
|
||||
})
|
||||
|
||||
const result = await SandboxManager.wrapWithSandbox(command)
|
||||
|
||||
expect(result).not.toBe(command)
|
||||
expect(result).toContain(testDir)
|
||||
} finally {
|
||||
await SandboxManager.reset()
|
||||
rmSync(testDir, { recursive: true, force: true })
|
||||
}
|
||||
})
|
||||
|
||||
it('denyWrite with /** suffix within allowed parent includes both paths', async () => {
|
||||
if (skipIfUnsupportedPlatform()) {
|
||||
return
|
||||
}
|
||||
|
||||
const parentDir = join(tmpdir(), `srt-test-glob-deny-${Date.now()}`)
|
||||
const childDir = join(parentDir, 'denied')
|
||||
mkdirSync(childDir, { recursive: true })
|
||||
|
||||
try {
|
||||
await SandboxManager.reset()
|
||||
await SandboxManager.initialize({
|
||||
network: { allowedDomains: [], deniedDomains: [] },
|
||||
filesystem: {
|
||||
denyRead: [],
|
||||
allowWrite: [parentDir],
|
||||
denyWrite: [`${childDir}/**`],
|
||||
},
|
||||
})
|
||||
|
||||
const result = await SandboxManager.wrapWithSandbox(command)
|
||||
|
||||
expect(result).not.toBe(command)
|
||||
expect(result).toContain(parentDir)
|
||||
expect(result).toContain(childDir)
|
||||
} finally {
|
||||
await SandboxManager.reset()
|
||||
rmSync(parentDir, { recursive: true, force: true })
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user