"use strict";
// Lightweight in-memory rate limiter + input validation for the daemon.
// No external deps. Sufficient for a localhost-only MCP server.
Object.defineProperty(exports, "__esModule", { value: true });
exports.checkRateLimit = checkRateLimit;
exports.resetRateLimit = resetRateLimit;
exports.validateToolArgs = validateToolArgs;
exports.isSafePath = isSafePath;
exports.looksLikeSqlInjection = looksLikeSqlInjection;
exports.getClientId = getClientId;
exports.securityHeaders = securityHeaders;
const WINDOW_MS = 60_000; // 1 minute
const MAX_REQUESTS_PER_WINDOW = 600; // 10 req/sec sustained
const buckets = new Map();
function checkRateLimit(clientId) {
    const now = Date.now();
    const bucket = buckets.get(clientId);
    if (!bucket || bucket.resetAt < now) {
        buckets.set(clientId, { count: 1, resetAt: now + WINDOW_MS });
        return { allowed: true, remaining: MAX_REQUESTS_PER_WINDOW - 1, resetIn: WINDOW_MS };
    }
    bucket.count++;
    if (bucket.count > MAX_REQUESTS_PER_WINDOW) {
        return { allowed: false, remaining: 0, resetIn: bucket.resetAt - now };
    }
    return { allowed: true, remaining: MAX_REQUESTS_PER_WINDOW - bucket.count, resetIn: bucket.resetAt - now };
}
function resetRateLimit(clientId) {
    if (clientId)
        buckets.delete(clientId);
    else
        buckets.clear();
}
const MAX_STRING_LENGTH = 100_000; // 100KB per string field
const MAX_NESTED_DEPTH = 10;
function validateToolArgs(name, args) {
    if (args === undefined)
        return { valid: true, sanitized: {} };
    if (args === null)
        return { valid: false, reason: 'args cannot be null' };
    if (typeof args !== 'object' || Array.isArray(args)) {
        return { valid: false, reason: 'args must be a plain object' };
    }
    return validateValue(name, args, 0);
}
function validateValue(field, value, depth) {
    if (depth > MAX_NESTED_DEPTH)
        return { valid: false, reason: `${field}: nested too deep (>${MAX_NESTED_DEPTH})` };
    if (value === null || value === undefined)
        return { valid: true, sanitized: value };
    if (typeof value === 'string') {
        if (value.length > MAX_STRING_LENGTH) {
            return { valid: false, reason: `${field}: string too long (${value.length} > ${MAX_STRING_LENGTH})` };
        }
        return { valid: true, sanitized: value };
    }
    if (typeof value === 'number' || typeof value === 'boolean')
        return { valid: true, sanitized: value };
    if (Array.isArray(value)) {
        if (value.length > 1000)
            return { valid: false, reason: `${field}: array too large (${value.length} > 1000)` };
        const sanitized = [];
        for (let i = 0; i < value.length; i++) {
            const r = validateValue(`${field}[${i}]`, value[i], depth + 1);
            if (!r.valid)
                return r;
            sanitized.push(r.sanitized);
        }
        return { valid: true, sanitized };
    }
    if (typeof value === 'object') {
        const keys = Object.keys(value);
        if (keys.length > 100)
            return { valid: false, reason: `${field}: object has too many keys (${keys.length} > 100)` };
        const sanitized = {};
        for (const k of keys) {
            const r = validateValue(`${field}.${k}`, value[k], depth + 1);
            if (!r.valid)
                return r;
            sanitized[k] = r.sanitized;
        }
        return { valid: true, sanitized };
    }
    return { valid: false, reason: `${field}: unsupported type ${typeof value}` };
}
const PATH_TRAVERSAL = /(\.\.[\\/])/g;
const SQL_INJECTION = /(\bunion\s+select\b)|(\bselect\s+\*?\s+\w+\s+from\b)|(;\s*(drop|truncate|delete|update|insert)\b)|(\bxp_\w+\b)|('\s*or\s+'?1'?\s*=\s*'?1)/i;
function isSafePath(p) {
    if (typeof p !== 'string')
        return false;
    if (p.length > 4096)
        return false;
    if (PATH_TRAVERSAL.test(p))
        return false;
    // Allow only safe chars
    return /^[a-zA-Z0-9 _./\-\\:]*/i.test(p);
}
function looksLikeSqlInjection(s) {
    return SQL_INJECTION.test(s);
}
function getClientId(req) {
    const xff = req.headers['x-forwarded-for'];
    if (typeof xff === 'string')
        return xff.split(',')[0].trim();
    if (Array.isArray(xff) && xff.length > 0)
        return String(xff[0]).split(',')[0].trim();
    return req.ip || req.socket?.remoteAddress || 'unknown';
}
function securityHeaders() {
    return {
        'X-Content-Type-Options': 'nosniff',
        'X-Frame-Options': 'DENY',
        'X-XSS-Protection': '1; mode=block',
        'Referrer-Policy': 'no-referrer',
    };
}
//# sourceMappingURL=security.js.map