fix: close sandbox metadata read race (#1235)
Some checks are pending
CI / Lint & Type Check (push) Waiting to run
CI / Changed Tests (push) Blocked by required conditions
CI / Select Test Scope (push) Waiting to run
CI / Workspace Unit Tests (push) Blocked by required conditions
CI / Critical Path Coverage (push) Blocked by required conditions
CI / Build (push) Waiting to run
CI / Security Audit (push) Waiting to run
Security Gates / CodeQL (push) Waiting to run
Security Gates / Gitleaks (push) Waiting to run

This commit is contained in:
Brad Groux 2026-08-24 02:11:09 -05:00 committed by GitHub
parent 6ab3feb35f
commit 1cdcd6ec60
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 50 additions and 7 deletions

View file

@ -14,6 +14,7 @@ import {
type FilesystemSandboxCommandRunner,
} from '../services/filesystem-sandbox-service.js';
import {
inspectGitMetadataRoots,
removeRunSandboxDirectory,
runSandboxDirectories,
} from '../utils/filesystem-sandbox-runtime.js';
@ -139,6 +140,23 @@ function conformantRunner(): FilesystemSandboxCommandRunner {
}
describe('FilesystemSandboxService', () => {
it('rejects a symbolic linked-worktree common-directory pointer', async () => {
const root = await fs.mkdtemp(path.join(os.tmpdir(), 'veritas-sandbox-git-metadata-'));
roots.push(root);
const workspace = path.join(root, 'workspace');
const gitDirectory = path.join(root, 'git-directory');
const commonDirectory = path.join(root, 'common-directory');
await fs.mkdir(workspace);
await fs.mkdir(gitDirectory);
await fs.mkdir(commonDirectory);
await fs.writeFile(path.join(workspace, '.git'), `gitdir: ${gitDirectory}\n`, 'utf8');
await fs.symlink(commonDirectory, path.join(gitDirectory, 'commondir'));
await expect(inspectGitMetadataRoots(workspace)).rejects.toThrow(
'Filesystem metadata pointer must be a regular file.'
);
});
it.each([
['darwin', 'seatbelt'],
['linux', 'landlock-bubblewrap'],

View file

@ -552,14 +552,39 @@ async function digestFileContents(filePath: string): Promise<string> {
}
async function readBoundedPathFile(filePath: string, maxBytes: number): Promise<string> {
const stat = await fs.lstat(filePath);
if (!stat.isFile() || stat.isSymbolicLink()) {
throw new Error('Filesystem metadata pointer must be a regular file.');
let handle: Awaited<ReturnType<typeof fs.open>> | undefined;
try {
const noFollow = typeof constants.O_NOFOLLOW === 'number' ? constants.O_NOFOLLOW : 0;
handle = await fs.open(filePath, constants.O_RDONLY | noFollow);
const before = await handle.stat();
if (!before.isFile()) {
throw new Error('Filesystem metadata pointer must be a regular file.');
}
if (before.size > maxBytes) {
throw new Error(`Filesystem metadata file exceeds ${maxBytes} bytes.`);
}
const buffer = Buffer.alloc(maxBytes + 1);
const { bytesRead } = await handle.read(buffer, 0, buffer.length, 0);
const after = await handle.stat();
if (
bytesRead > maxBytes ||
before.dev !== after.dev ||
before.ino !== after.ino ||
before.size !== after.size ||
before.mtimeMs !== after.mtimeMs
) {
throw new Error('Filesystem metadata file changed while it was being inspected.');
}
return buffer.subarray(0, bytesRead).toString('utf8');
} catch (error) {
if ((error as NodeJS.ErrnoException).code === 'ELOOP') {
throw new Error('Filesystem metadata pointer must be a regular file.', { cause: error });
}
throw error;
} finally {
await handle?.close();
}
if (stat.size > maxBytes) {
throw new Error(`Filesystem metadata file exceeds ${maxBytes} bytes.`);
}
return fs.readFile(filePath, 'utf8');
}
async function readGitIdentityValue(cwd: string, key: 'user.name' | 'user.email') {