/**
* SYMLINK ATTACK TESTS - Most dangerous attack vector
*/
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
import { PathValidator } from '../../src/security/pathValidator.js';
import fs from 'fs';
import path from 'path';
describe('🔴 SYMLINK ATTACKS', () => {
const workspace = '/Users/guybary/path_validator_symlink_test';
let validator: PathValidator;
beforeAll(() => {
// Create test workspace
fs.mkdirSync(workspace, { recursive: true });
validator = new PathValidator(workspace);
});
afterAll(() => {
// Cleanup
try {
// Remove symlinks first
const links = ['evil_link_etc', 'evil_link_root', 'evil_link_parent'];
links.forEach(link => {
try {
fs.unlinkSync(path.join(workspace, link));
} catch {
// Ignore cleanup errors
}
});
fs.rmdirSync(workspace);
} catch {
// Ignore cleanup errors
}
});
it('ATTACK: Symlink pointing to /etc', () => {
const linkPath = path.join(workspace, 'evil_link_etc');
try {
// Create symlink to /etc
fs.symlinkSync('/etc', linkPath);
console.log('\n🔴 ATTACK: Symlink to /etc');
console.log(` Symlink created: ${linkPath} -> /etc`);
// Try to validate the symlink
const result = validator.validate(linkPath);
console.log(` Validation result: ${result.isValid ? '❌ BYPASSED!' : '✅ BLOCKED'}`);
console.log(` Error: ${result.error || 'none'}`);
console.log(` Sanitized: ${result.sanitizedPath || 'none'}`);
// Should be blocked!
expect(result.isValid).toBe(false);
expect(result.error).toContain('outside allowed directories');
// Cleanup
fs.unlinkSync(linkPath);
} catch {
// If we can't create symlink (permissions), test passes
expect(true).toBe(true);
}
});
it('ATTACK: Symlink pointing to root directory', () => {
const linkPath = path.join(workspace, 'evil_link_root');
try {
fs.symlinkSync('/', linkPath);
console.log('\n🔴 ATTACK: Symlink to /');
const result = validator.validate(linkPath);
console.log(` Result: ${result.isValid ? '❌ BYPASSED!' : '✅ BLOCKED'}`);
expect(result.isValid).toBe(false);
fs.unlinkSync(linkPath);
} catch {
expect(true).toBe(true);
}
});
it('ATTACK: Symlink pointing to parent directory', () => {
const linkPath = path.join(workspace, 'evil_link_parent');
const parentDir = path.dirname(workspace);
try {
fs.symlinkSync(parentDir, linkPath);
console.log('\n🔴 ATTACK: Symlink to parent directory');
console.log(` Symlink: ${linkPath} -> ${parentDir}`);
const result = validator.validate(linkPath);
console.log(` Result: ${result.isValid ? '❌ BYPASSED!' : '✅ BLOCKED'}`);
expect(result.isValid).toBe(false);
expect(result.error).toContain('outside allowed directories');
fs.unlinkSync(linkPath);
} catch {
expect(true).toBe(true);
}
});
it('ATTACK: Symlink chain (link -> link -> /etc)', () => {
const link1 = path.join(workspace, 'link1');
const link2 = path.join(workspace, 'link2');
try {
fs.symlinkSync(link2, link1);
fs.symlinkSync('/etc', link2);
console.log('\n🔴 ATTACK: Symlink chain');
const result = validator.validate(link1);
console.log(` Result: ${result.isValid ? '❌ BYPASSED!' : '✅ BLOCKED'}`);
expect(result.isValid).toBe(false);
fs.unlinkSync(link1);
fs.unlinkSync(link2);
} catch {
expect(true).toBe(true);
}
});
it('SAFE: Symlink pointing within workspace', () => {
const safeDir = path.join(workspace, 'safe_target');
const linkPath = path.join(workspace, 'safe_link');
try {
fs.mkdirSync(safeDir);
fs.symlinkSync(safeDir, linkPath);
console.log('\n✅ SAFE: Symlink within workspace');
const result = validator.validate(linkPath);
console.log(` Result: ${result.isValid ? '✅ ALLOWED (correct)' : '❌ BLOCKED (wrong)'}`);
// This should be allowed - symlink within workspace
expect(result.isValid).toBe(true);
fs.unlinkSync(linkPath);
fs.rmdirSync(safeDir);
} catch (e: unknown) {
const error = e as Error;
console.log(` Error: ${error.message}`);
}
});
});