Files
2026-07-13 12:30:05 +08:00

127 lines
3.2 KiB
TypeScript

/**
* @license
* Copyright 2026 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import assert from 'node:assert';
import {spawn} from 'node:child_process';
import crypto from 'node:crypto';
import fs from 'node:fs';
import path from 'node:path';
import process from 'node:process';
import {describe, it, afterEach, beforeEach} from 'node:test';
import {
DAEMON_SCRIPT_PATH,
getPidFilePath,
IS_WINDOWS,
} from '../../src/daemon/utils.js';
describe('daemon security checks', () => {
let sessionId: string;
beforeEach(() => {
sessionId = crypto.randomUUID();
});
afterEach(() => {
const pidFilePath = getPidFilePath(sessionId);
const pidDir = path.dirname(pidFilePath);
try {
fs.unlinkSync(pidFilePath);
} catch {
// ignore
}
try {
fs.rmdirSync(pidDir);
} catch {
// ignore
}
});
it('should not follow symlinks and fail to write PID file', async () => {
if (IS_WINDOWS) {
return;
}
const pidFilePath = getPidFilePath(sessionId);
const pidDir = path.dirname(pidFilePath);
// Ensure directory exists with safe permissions
fs.mkdirSync(pidDir, {recursive: true});
fs.chmodSync(pidDir, 0o700);
// Create a target file that we do NOT want to be overwritten
const targetPath = path.join(pidDir, 'target_file.txt');
fs.writeFileSync(targetPath, 'original content', 'utf-8');
// Create a symlink at pidFilePath pointing to targetPath
fs.symlinkSync(targetPath, pidFilePath);
// Try to spawn the daemon
const child = spawn(process.execPath, [DAEMON_SCRIPT_PATH], {
env: {...process.env, CHROME_DEVTOOLS_MCP_SESSION_ID: sessionId},
});
const exitCode = await new Promise<number | null>(resolve => {
child.on('exit', code => {
resolve(code);
});
});
// Daemon should have exited with error code 1
assert.strictEqual(exitCode, 1);
// Target file content should remain unchanged ("original content")
const content = fs.readFileSync(targetPath, 'utf-8');
assert.strictEqual(content, 'original content');
// Clean up target file and symlink
try {
fs.unlinkSync(pidFilePath);
} catch {
// ignore
}
try {
fs.unlinkSync(targetPath);
} catch {
// ignore
}
});
it('should fail if directory has insecure permissions (group/world writable)', async () => {
if (IS_WINDOWS) {
return;
}
const pidFilePath = getPidFilePath(sessionId);
const pidDir = path.dirname(pidFilePath);
// Ensure directory exists
fs.mkdirSync(pidDir, {recursive: true});
// Change permissions to 0o777 (group and world writable)
fs.chmodSync(pidDir, 0o777);
// Try to spawn the daemon
const child = spawn(process.execPath, [DAEMON_SCRIPT_PATH], {
env: {...process.env, CHROME_DEVTOOLS_MCP_SESSION_ID: sessionId},
});
const exitCode = await new Promise<number | null>(resolve => {
child.on('exit', code => {
resolve(code);
});
});
// Daemon should have exited with error code 1
assert.strictEqual(exitCode, 1);
// Restore permissions so cleanup can run successfully
try {
fs.chmodSync(pidDir, 0o700);
} catch {
// ignore
}
});
});