feat(core): refactor SandboxManager to a stateless architecture and introduce explicit Deny interface (#23141)

This commit is contained in:
Emily Hedlund
2026-03-23 11:43:58 -04:00
committed by GitHub
parent 99e5164c82
commit cdf077da56
13 changed files with 444 additions and 388 deletions
@@ -4,18 +4,19 @@
* SPDX-License-Identifier: Apache-2.0
*/
import { join } from 'node:path';
import { join, normalize } from 'node:path';
import { writeFileSync } from 'node:fs';
import os from 'node:os';
import {
type SandboxManager,
type GlobalSandboxOptions,
type SandboxRequest,
type SandboxedCommand,
sanitizePaths,
} from '../../services/sandboxManager.js';
import {
sanitizeEnvironment,
getSecureSanitizationConfig,
type EnvironmentSanitizationConfig,
} from '../../services/environmentSanitization.js';
let cachedBpfPath: string | undefined;
@@ -76,28 +77,15 @@ function getSeccompBpfPath(): string {
return bpfPath;
}
/**
* Options for configuring the LinuxSandboxManager.
*/
export interface LinuxSandboxOptions {
/** The primary workspace path to bind into the sandbox. */
workspace: string;
/** Additional paths to bind into the sandbox. */
allowedPaths?: string[];
/** Optional base sanitization config. */
sanitizationConfig?: EnvironmentSanitizationConfig;
}
/**
* A SandboxManager implementation for Linux that uses Bubblewrap (bwrap).
*/
export class LinuxSandboxManager implements SandboxManager {
constructor(private readonly options: LinuxSandboxOptions) {}
constructor(private readonly options: GlobalSandboxOptions) {}
async prepareCommand(req: SandboxRequest): Promise<SandboxedCommand> {
const sanitizationConfig = getSecureSanitizationConfig(
req.config?.sanitizationConfig,
this.options.sanitizationConfig,
req.policy?.sanitizationConfig,
);
const sanitizedEnv = sanitizeEnvironment(req.env, sanitizationConfig);
@@ -121,13 +109,20 @@ export class LinuxSandboxManager implements SandboxManager {
this.options.workspace,
];
const allowedPaths = this.options.allowedPaths ?? [];
for (const path of allowedPaths) {
if (path !== this.options.workspace) {
bwrapArgs.push('--bind', path, path);
const allowedPaths = sanitizePaths(req.policy?.allowedPaths) || [];
const normalizedWorkspace = normalize(this.options.workspace).replace(
/\/$/,
'',
);
for (const allowedPath of allowedPaths) {
const normalizedAllowedPath = normalize(allowedPath).replace(/\/$/, '');
if (normalizedAllowedPath !== normalizedWorkspace) {
bwrapArgs.push('--bind-try', allowedPath, allowedPath);
}
}
// TODO: handle forbidden paths
const bpfPath = getSeccompBpfPath();
bwrapArgs.push('--seccomp', '9');