nikto-mcp
Version:
A secure MCP (Model Context Protocol) server that enables AI agents to interact with Nikto web server scanner
119 lines • 4.36 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.validateScanOptions = validateScanOptions;
exports.sanitizeInput = sanitizeInput;
const zod_1 = require("zod");
const index_1 = require("../config/index");
// Helper functions for type coercion
const coerceToNumber = (value) => {
if (typeof value === 'number') {
return value;
}
if (typeof value === 'string') {
const trimmed = value.trim();
if (trimmed === '') {
return undefined;
}
const num = Number(trimmed);
if (!isNaN(num)) {
return num;
}
}
return value; // Let Zod handle the validation error
};
const coerceToBoolean = (value) => {
if (typeof value === 'boolean') {
return value;
}
if (typeof value === 'string') {
const trimmed = value.trim().toLowerCase();
if (trimmed === 'true' || trimmed === '1') {
return true;
}
if (trimmed === 'false' || trimmed === '0') {
return false;
}
if (trimmed === '') {
return undefined;
}
}
if (typeof value === 'number') {
if (value === 1) {
return true;
}
if (value === 0) {
return false;
}
}
return value; // Let Zod handle the validation error
};
const scanOptionsSchema = zod_1.z
.object({
target: zod_1.z
.string()
.min(1, 'Target is required')
.refine((value) => {
// Basic validation for URL, IP, hostname, or hostname:port
const urlPattern = /^https?:\/\/.+/;
const ipPattern = /^(\d{1,3}\.){3}\d{1,3}$/;
const hostnamePattern = /^[a-zA-Z0-9][a-zA-Z0-9-]{0,61}[a-zA-Z0-9]?(\.[a-zA-Z0-9][a-zA-Z0-9-]{0,61}[a-zA-Z0-9]?)*$/;
// Check for hostname:port format
const parts = value.split(':');
if (parts.length === 2 && parts[0] && parts[1]) {
const hostname = parts[0];
const portStr = parts[1];
const port = parseInt(portStr, 10);
if ((hostnamePattern.test(hostname) || ipPattern.test(hostname)) &&
!isNaN(port) &&
port > 0 &&
port <= 65535) {
return true;
}
}
return urlPattern.test(value) || ipPattern.test(value) || hostnamePattern.test(value);
}, 'Invalid target: must be a valid URL, IP address, or hostname'),
port: zod_1.z.preprocess(coerceToNumber, zod_1.z.number().int().min(1).max(65535).optional().default(80)),
ssl: zod_1.z.preprocess(coerceToBoolean, zod_1.z.boolean().optional().default(false)),
nossl: zod_1.z.preprocess(coerceToBoolean, zod_1.z.boolean().optional().default(false)),
nolookup: zod_1.z.preprocess(coerceToBoolean, zod_1.z.boolean().optional().default(false)),
timeout: zod_1.z.preprocess(coerceToNumber, zod_1.z.number().positive().optional().default(index_1.config.defaultTimeout)),
vhost: zod_1.z
.string()
.optional()
.refine((value) => {
if (!value) {
return true;
}
// Validate hostname format for vhost
const hostnamePattern = /^[a-zA-Z0-9][a-zA-Z0-9-]{0,61}[a-zA-Z0-9]?(\.[a-zA-Z0-9][a-zA-Z0-9-]{0,61}[a-zA-Z0-9]?)*$/;
return hostnamePattern.test(value);
}, 'Invalid vhost: must be a valid hostname'),
outputFormat: zod_1.z.enum(['json', 'text']).optional().default('json'),
dryRun: zod_1.z.preprocess(coerceToBoolean, zod_1.z.boolean().optional().default(false)),
})
.refine((data) => !(data.ssl && data.nossl), {
message: 'Cannot specify both ssl and nossl options',
path: ['ssl', 'nossl'],
});
function validateScanOptions(options) {
try {
return scanOptionsSchema.parse(options);
}
catch (error) {
if (error instanceof zod_1.z.ZodError) {
const message = error.issues
.map((e) => `${e.path.join('.')}: ${e.message}`)
.join(', ');
throw new Error(`Validation error: ${message}`);
}
throw error;
}
}
function sanitizeInput(input) {
// Remove potentially dangerous characters for shell commands
return input
.replace(/[;&|`$<>\\]/g, '')
.replace(/\n|\r/g, '')
.trim();
}
//# sourceMappingURL=scan.validator.js.map