Files
TORCH/test/governance-security-impl.mjs
T
google-labs-jules[bot]andPR0M3TH3AN bdd3ed8c8f fix(security): prevent command injection in governance service and fix build errors
- Mitigate command injection in `src/services/governance/index.js` by using explicit array arguments with `execFileSync` and adding a security comment.
- Add regression test `test/governance-security.test.mjs` verifying the fix against simulated exploit attempts.
- Fix CI build failures by resolving circular dependencies and duplicate declarations in `src/lock-utils.mjs`, `src/lock-ops.mjs`, `src/lock-publisher.mjs`, and `src/relay-health.mjs`.
- Resolve linting errors in tests and implementation files.

Co-authored-by: PR0M3TH3AN <[email protected]>
2026-02-21 19:35:31 +00:00

112 lines
4.2 KiB
JavaScript

import fs from 'node:fs/promises';
import path from 'node:path';
import { execSync } from 'node:child_process';
import os from 'node:os';
import { fileURLToPath } from 'node:url';
// Calculate paths relative to this file
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const repoRoot = path.resolve(__dirname, '..');
const governanceServicePath = path.resolve(repoRoot, 'src/services/governance/index.js');
async function runTest() {
// Create temp dir
const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'torch-gov-test-'));
console.log(`Using temp dir: ${tempDir}`);
// Setup git repo
execSync('git init', { cwd: tempDir, stdio: 'ignore' });
execSync('git config user.name "Test User"', { cwd: tempDir, stdio: 'ignore' });
execSync('git config user.email "[email protected]"', { cwd: tempDir, stdio: 'ignore' });
// Create required directory structure
await fs.mkdir(path.join(tempDir, 'src/prompts/daily'), { recursive: true });
await fs.mkdir(path.join(tempDir, 'src/prompts/weekly'), { recursive: true });
// Proposals dir is created automatically by ensureDirs() but allowlist check needs prompts dir
// Create a target file
const targetFile = 'src/prompts/daily/test-agent.md';
const absoluteTarget = path.join(tempDir, targetFile);
await fs.writeFile(absoluteTarget, 'Original Content');
// Commit initial state so we have a HEAD
execSync(`git add ${targetFile}`, { cwd: tempDir });
execSync('git commit -m "Initial commit"', { cwd: tempDir });
// Switch cwd to tempDir
const originalCwd = process.cwd();
try {
process.chdir(tempDir);
console.log(`Changed cwd to: ${process.cwd()}`);
// Import governance service dynamically so it picks up the new cwd
const governance = await import(governanceServicePath);
// Malicious payload
const maliciousPayload = 'attacker"; touch /tmp/pwned_check; echo "';
// const proposalId = `test-proposal-${Date.now()}`; // unused
// Manually create proposal metadata and files because createProposal also uses git diff
// We can use createProposal from the service, but we need to pass the malicious agent name there.
// Let's try using createProposal first.
const proposalContent = `Shared contract (required):\nRequired startup + artifacts + memory + issue capture\n\nNew Content`;
console.log('Creating proposal...');
const { id } = await governance.createProposal({
agent: maliciousPayload,
target: targetFile,
newContent: proposalContent,
reason: 'Testing security'
});
console.log(`Proposal created with ID: ${id}`);
// Verify metadata has the malicious author
const metaPath = path.join(tempDir, 'src/proposals', id, 'meta.json');
const meta = JSON.parse(await fs.readFile(metaPath, 'utf8'));
if (meta.author !== maliciousPayload) {
throw new Error('Meta author does not match malicious payload');
}
// Apply proposal
console.log('Applying proposal...');
await governance.applyProposal(id);
console.log('Proposal applied.');
// Verification 1: Check if /tmp/pwned_check exists
try {
await fs.access('/tmp/pwned_check');
console.error('VULNERABILITY DETECTED: /tmp/pwned_check exists!');
process.exit(1);
} catch {
console.log('Security check passed: /tmp/pwned_check does not exist.');
}
// Verification 2: Check git log for commit message
const log = execSync('git log -1 --pretty=%B', { cwd: tempDir }).toString();
console.log(`Commit message: ${log}`);
if (!log.includes(maliciousPayload)) {
console.error('Verification failed: Commit message does not contain the payload literally.');
process.exit(1);
}
// Check if the commit message looks "broken" (e.g. if newlines were injected)
// Here we just want to ensure the whole thing is in the message.
console.log('Verification passed: Commit message contains payload.');
} finally {
// Cleanup
process.chdir(originalCwd);
await fs.rm(tempDir, { recursive: true, force: true });
// Also remove /tmp/pwned_check if it exists (it shouldn't)
try { await fs.unlink('/tmp/pwned_check'); } catch { /* ignore */ }
}
}
runTest().catch(err => {
console.error('Test failed:', err);
process.exit(1);
});