nikto-mcp
Version:
A secure MCP (Model Context Protocol) server that enables AI agents to interact with Nikto web server scanner
239 lines (206 loc) • 6.66 kB
text/typescript
import { validateScanOptions, sanitizeInput } from '../src/validators/scan.validator';
describe('Validators', () => {
describe('validateScanOptions', () => {
it('should validate valid scan options', () => {
const options = {
target: 'https://example.com',
port: 443,
ssl: true,
timeout: 1800,
outputFormat: 'json' as const,
};
const result = validateScanOptions(options);
expect(result).toEqual({
...options,
nossl: false,
nolookup: false,
dryRun: false,
});
});
it('should apply default values', () => {
const options = {
target: 'example.com',
};
const result = validateScanOptions(options);
expect(result.port).toBe(80);
expect(result.ssl).toBe(false);
expect(result.timeout).toBe(3600);
expect(result.outputFormat).toBe('json');
});
it('should validate IP addresses', () => {
const options = {
target: '192.168.1.1',
};
const result = validateScanOptions(options);
expect(result.target).toBe('192.168.1.1');
});
it('should reject invalid targets', () => {
expect(() => validateScanOptions({ target: '' })).toThrow('Validation error');
expect(() => validateScanOptions({ target: 'invalid..target' })).toThrow('Validation error');
});
it('should reject invalid ports', () => {
expect(() =>
validateScanOptions({
target: 'example.com',
port: 0,
}),
).toThrow('Validation error');
expect(() =>
validateScanOptions({
target: 'example.com',
port: 70000,
}),
).toThrow('Validation error');
});
it('should reject conflicting ssl and nossl options', () => {
expect(() =>
validateScanOptions({
target: 'example.com',
ssl: true,
nossl: true,
}),
).toThrow('Cannot specify both ssl and nossl options');
});
it('should validate new options with defaults', () => {
const options = {
target: 'example.com',
nolookup: true,
vhost: 'test.example.com',
};
const result = validateScanOptions(options);
expect(result.nolookup).toBe(true);
expect(result.vhost).toBe('test.example.com');
expect(result.nossl).toBe(false);
expect(result.dryRun).toBe(false);
});
it('should reject invalid vhost', () => {
expect(() =>
validateScanOptions({
target: 'example.com',
vhost: 'invalid..hostname',
}),
).toThrow('Invalid vhost: must be a valid hostname');
});
it('should validate dry run mode', () => {
const options = {
target: 'example.com',
dryRun: true,
};
const result = validateScanOptions(options);
expect(result.dryRun).toBe(true);
});
it('should coerce string numbers to numbers', () => {
const options = {
target: 'example.com',
port: '443',
timeout: '1800',
};
const result = validateScanOptions(options);
expect(result.port).toBe(443);
expect(result.timeout).toBe(1800);
expect(typeof result.port).toBe('number');
expect(typeof result.timeout).toBe('number');
});
it('should coerce string booleans to booleans', () => {
const options = {
target: 'example.com',
ssl: 'true',
nossl: 'false',
nolookup: '1',
dryRun: '0',
};
const result = validateScanOptions(options);
expect(result.ssl).toBe(true);
expect(result.nossl).toBe(false);
expect(result.nolookup).toBe(true);
expect(result.dryRun).toBe(false);
expect(typeof result.ssl).toBe('boolean');
expect(typeof result.nossl).toBe('boolean');
expect(typeof result.nolookup).toBe('boolean');
expect(typeof result.dryRun).toBe('boolean');
});
it('should handle case-insensitive boolean strings', () => {
const options = {
target: 'example.com',
ssl: 'TRUE',
nossl: 'False',
nolookup: 'true',
dryRun: 'FALSE',
};
const result = validateScanOptions(options);
expect(result.ssl).toBe(true);
expect(result.nossl).toBe(false);
expect(result.nolookup).toBe(true);
expect(result.dryRun).toBe(false);
});
it('should handle numeric boolean values', () => {
const options = {
target: 'example.com',
ssl: 1,
nossl: 0,
nolookup: 1,
dryRun: 0,
};
const result = validateScanOptions(options);
expect(result.ssl).toBe(true);
expect(result.nossl).toBe(false);
expect(result.nolookup).toBe(true);
expect(result.dryRun).toBe(false);
});
it('should reject invalid string numbers', () => {
expect(() =>
validateScanOptions({
target: 'example.com',
port: 'invalid',
}),
).toThrow('Validation error');
expect(() =>
validateScanOptions({
target: 'example.com',
timeout: 'not-a-number',
}),
).toThrow('Validation error');
});
it('should reject invalid boolean strings', () => {
expect(() =>
validateScanOptions({
target: 'example.com',
ssl: 'maybe',
}),
).toThrow('Validation error');
expect(() =>
validateScanOptions({
target: 'example.com',
dryRun: 'yes',
}),
).toThrow('Validation error');
});
it('should handle empty strings appropriately', () => {
const options = {
target: 'example.com',
port: '',
ssl: '',
timeout: '',
};
const result = validateScanOptions(options);
// Empty strings should result in default values
expect(result.port).toBe(80); // default
expect(result.ssl).toBe(false); // default
expect(result.timeout).toBe(3600); // default from config
});
});
describe('sanitizeInput', () => {
it('should remove dangerous characters', () => {
expect(sanitizeInput('test; rm -rf /')).toBe('test rm -rf /');
expect(sanitizeInput('test | cat /etc/passwd')).toBe('test cat /etc/passwd');
expect(sanitizeInput('test && echo hello')).toBe('test echo hello');
});
it('should remove newlines', () => {
expect(sanitizeInput('test\nline')).toBe('testline');
expect(sanitizeInput('test\r\nline')).toBe('testline');
});
it('should trim whitespace', () => {
expect(sanitizeInput(' test ')).toBe('test');
});
});
});