'use strict';

Object.defineProperty(exports, '__esModule', { value: true });

require('dns');

// src/core/constants.ts
var INPUT = {
  /** Default maximum input size (1MB) */
  DEFAULT_MAX_SIZE: 1e6,
  /** Maximum recursion depth for nested objects */
  MAX_RECURSION_DEPTH: 10
};
var RATE_LIMIT = {
  /** Default window size (1 minute) */
  DEFAULT_WINDOW_MS: 6e4,
  /** Default max requests per window */
  DEFAULT_MAX_REQUESTS: 100,
  /** Default HTTP status code for rate limited responses */
  DEFAULT_STATUS_CODE: 429,
  /** Default error message */
  DEFAULT_MESSAGE: "Too many requests, please try again later."};
var HEADERS = {
  /** Default Content Security Policy */
  DEFAULT_CSP: [
    "default-src 'self'",
    "script-src 'self'",
    "style-src 'self' 'unsafe-inline'",
    "img-src 'self' data: https:",
    "font-src 'self'",
    "object-src 'none'",
    "frame-ancestors 'none'"
  ].join("; "),
  /** Default HSTS max age (1 year in seconds) */
  HSTS_MAX_AGE: 31536e3,
  /** Default X-Frame-Options value */
  FRAME_OPTIONS: "DENY",
  /** Default X-Content-Type-Options value */
  CONTENT_TYPE_OPTIONS: "nosniff",
  /** Default Referrer-Policy value */
  REFERRER_POLICY: "strict-origin-when-cross-origin",
  /** Default Permissions-Policy value */
  PERMISSIONS_POLICY: "geolocation=(), microphone=(), camera=()",
  /** Default Cache-Control value for security */
  CACHE_CONTROL: "no-store, no-cache, must-revalidate, proxy-revalidate"
};
var XSS_PATTERNS = [
  /** Script tags (ReDoS-safe version) */
  /<script[^>]*>[\s\S]*?<\/script>/gi,
  /** javascript: protocol (allow optional spaces before colon) */
  /javascript\s*:/gi,
  /** vbscript: protocol */
  /vbscript\s*:/gi,
  /** Event handlers (onclick, onerror, etc.) — any separator before attribute */
  /(?:[\s/])on\w+\s*=/gi,
  /** iframe tags */
  /<iframe/gi,
  /** object tags */
  /<object/gi,
  /** embed tags */
  /<embed/gi,
  /** data: URIs (only dangerous ones, avoid false positives) */
  /(?:^|[\s"'=])data:/gi,
  /** URL-encoded script tags */
  /%3Cscript/gi,
  /** SVG with onload */
  /<svg[^>]*onload/gi,
  /** form tags — phishing/credential harvesting via action= redirection */
  /<form[\s>]/gi,
  /** meta tags — http-equiv refresh redirects or CSP bypass */
  /<meta[\s>]/gi,
  /** base href hijacking — redirects all relative URLs to attacker domain */
  /<base[\s>]/gi,
  /** link tag injection — stylesheet or preload CSRF attacks */
  /<link[\s>]/gi,
  /** style tag — CSS expression() / behavior: / IE-era attacks. Mirrors
   *  Python's xss-style-tag from packages/core/patterns.json. */
  /<style[\s>]/gi
];
var XSS_REMOVE_PATTERNS = [
  /** Full script blocks (content + tags) */
  /<script[^>]*>[\s\S]*?<\/script>/gi,
  /** Standalone/unclosed script tags */
  /<script[^>]*>/gi,
  /** style — CSS expression() and behavior: attacks (IE-era but still relevant) */
  /<style[^>]*>[\s\S]*?<\/style>/gi,
  /<style[^>]*/gi,
  /** iframe — full block and partial/unclosed */
  /<iframe[^>]*>[\s\S]*?<\/iframe>/gi,
  /<iframe[^>]*/gi,
  /** object — full block and partial/unclosed */
  /<object[^>]*>[\s\S]*?<\/object>/gi,
  /<object[^>]*/gi,
  /** embed tags */
  /<embed[^>]*/gi,
  /** SVG with inline event handlers */
  /<svg[^>]*onload[^>]*>/gi,
  /** URL-encoded script tags */
  /%3Cscript/gi,
  /** Event handlers with quoted values: onclick="...", onerror='...' */
  /(?:[\s/])on\w+\s*=\s*["'][^"']*["']/gi,
  /** Event handlers with unquoted values: onload=value */
  /(?:[\s/])on\w+\s*=\s*[^\s>]*/gi,
  /** javascript: and vbscript: protocols (allow optional spaces before colon) */
  /javascript\s*:/gi,
  /vbscript\s*:/gi,
  /** data: URIs with HTML or SVG content (SVG can run JS via inline event handlers) */
  /data\s*:\s*(?:text\/html|image\/svg)[^>\s]*/gi,
  /** form tag injection — phishing via action= redirection */
  /<form[\s>][^>]*/gi,
  /** meta tag injection — http-equiv refresh or CSP bypass */
  /<meta[\s>][^>]*/gi,
  /** base href hijacking */
  /<base[\s>][^>]*/gi,
  /** link tag injection — stylesheet or preload attacks */
  /<link[\s>][^>]*/gi
];
var SQL_PATTERNS = [
  /** SQL keywords */
  /(\b(SELECT|INSERT|UPDATE|DELETE|DROP|UNION|ALTER|CREATE|TRUNCATE|EXEC|EXECUTE)\b)/gi,
  /** SQL comments: ANSI (--), C-style (slash-star ... star-slash), MySQL (#) */
  /(--|\/\*|\*\/|#)/g,
  /** SQL statement separators */
  /(;|\|\||&&)/g,
  /** Boolean injection: OR 1=1 */
  /\bOR\s+\d+\s*=\s*\d+/gi,
  /** Boolean injection: OR 'a'='a' or OR "a"="a" (including mixed quotes) */
  /\bOR\s+(['"])[^'"]*\1\s*=\s*(['"])[^'"]*\2/gi,
  /\bOR\s+('[^']*'|"[^"]*")\s*=\s*('[^']*'|"[^"]*")/gi,
  /** Boolean injection: AND 1=1 */
  /\bAND\s+\d+\s*=\s*\d+/gi,
  /** Boolean injection: AND 'a'='a' or AND "a"="a" (including mixed quotes) */
  /\bAND\s+(['"])[^'"]*\1\s*=\s*(['"])[^'"]*\2/gi,
  /\bAND\s+('[^']*'|"[^"]*")\s*=\s*('[^']*'|"[^"]*")/gi,
  /** Time-based blind: SLEEP() */
  /\bSLEEP\s*\(\s*\d+\s*\)/gi,
  /** Time-based blind: BENCHMARK() */
  /\bBENCHMARK\s*\(/gi,
  /** Time-based blind: PostgreSQL pg_sleep() */
  /\bpg_sleep\s*\(/gi,
  /** Time-based blind: MSSQL WAITFOR DELAY */
  /\bWAITFOR\s+DELAY\b/gi,
  /**
   * Oracle DBMS_* stdlib packages used for time-based blind SQLi
   * (DBMS_LOCK.SLEEP, DBMS_PIPE.RECEIVE_MESSAGE) and other Oracle
   * abuse paths. No legitimate user input contains these. Mirrors
   * `sqli-oracle-dbms-packages` in packages/core/patterns.json —
   * improvements.md §1.1.e Q3. Must stay in sync until Node
   * migrates to patterns.json-at-runtime (planned v1.7).
   */
  /\bDBMS_(?:LOCK|PIPE|UTILITY|XSLPROCESSOR|JAVA|OUTPUT|SCHEDULER)\b/gi
];
var PATH_PATTERNS = [
  /** Unix path traversal */
  /\.\.\//g,
  /** Windows path traversal */
  /\.\.\\/g,
  /** URL-encoded traversal (%2e%2e) */
  /%2e%2e/gi,
  /** Double URL-encoded traversal (%252e) */
  /%252e/gi,
  /** Mixed encoding: ..%2F */
  /\.\.%2F/gi,
  /** Mixed encoding: %2e./ and .%2e/ */
  /%2e\.[\\/]/gi,
  /\.%2e[\\/]/gi,
  /** Fully URL-encoded: %2e%2e%2f */
  /%2e%2e%2f/gi,
  /** Double URL-encoded forward slash: %252f */
  /%252f/gi,
  /** Dotdotslash bypass: ....// or ....\\ */
  /\.{2,}[/\\]{2,}/g,
  /** Null byte injection in paths */
  /\0/g
];
var COMMAND_PATTERNS = [
  /**
   * Shell metacharacters that enable command chaining/substitution.
   * Bare ( and ) are excluded — they appear in common legitimate values
   * (function calls in code fields, math expressions, etc.).
   * Command substitution is caught by the $( combined pattern below.
   * NOTE: ';', '&', '|' may appear in legitimate URL query strings
   * and Markdown; consider disabling command checking (command: false)
   * for fields that intentionally allow those characters.
   */
  /[;&|`]/g,
  /** Command substitution: $( ... ) — matched as a pair to reduce false positives */
  /\$\(/g,
  /**
   * POSIX shell IFS-substitution: ${IFS} or ${IFS%??}.
   * Attackers use this to inject spaces past metacharacter filters
   * in payloads like `;cat${IFS}/etc/passwd`. Mirrors
   * `cmdi-ifs-bypass` in packages/core/patterns.json — improvements.md
   * §1.1.e Q5. Must stay in sync until Node migrates to
   * patterns.json-at-runtime (planned v1.7).
   */
  /\$\{IFS(?:%[^}]*)?\}/g,
  /** URL-encoded control characters (%00-%0F): null, tab, vtab, formfeed, LF, CR */
  /%0[0-9a-f]/gi
];
var DANGEROUS_PROTO_KEYS = /* @__PURE__ */ new Set([
  "__proto__",
  "constructor",
  "prototype",
  "__definegetter__",
  "__definesetter__",
  "__lookupgetter__",
  "__lookupsetter__"
]);
var NOSQL_DANGEROUS_KEYS = /* @__PURE__ */ new Set([
  // Comparison
  "$gt",
  "$gte",
  "$lt",
  "$lte",
  "$ne",
  "$eq",
  "$in",
  "$nin",
  // Logical
  "$and",
  "$or",
  "$not",
  "$nor",
  // Element / evaluation
  "$exists",
  "$type",
  "$regex",
  "$where",
  "$expr",
  "$mod",
  "$text",
  "$jsonSchema",
  // Array
  "$elemMatch",
  "$all",
  "$size",
  // JavaScript execution (critical)
  "$function",
  "$accumulator",
  // Aggregation pipeline operators (injectable via $lookup etc.)
  "$lookup",
  "$match",
  "$project",
  "$group",
  "$sort",
  "$limit",
  "$skip",
  "$unwind",
  "$addFields",
  "$replaceRoot"
]);
var REDACTION = {
  /** Replacement text for redacted values */
  REPLACEMENT: "[REDACTED]",
  /** Truncation indicator */
  TRUNCATED: "[TRUNCATED]",
  /** Max depth indicator */
  MAX_DEPTH: "[MAX_DEPTH]",
  /** Default max message length */
  DEFAULT_MAX_LENGTH: 1e4,
  /** Default sensitive keys to redact */
  SENSITIVE_KEYS: /* @__PURE__ */ new Set([
    "password",
    "passwd",
    "pwd",
    "secret",
    "token",
    "apikey",
    "api_key",
    "apiKey",
    "auth",
    "authorization",
    "credit_card",
    "creditcard",
    "cc",
    "ssn",
    "social_security",
    "private_key",
    "privateKey",
    "access_token",
    "accessToken",
    "refresh_token",
    "refreshToken",
    "bearer",
    "jwt",
    "session",
    "cookie",
    "credentials",
    "x-api-key",
    "x-auth-token"
  ])
};
var VALIDATION = {
  /**
   * Email regex pattern.
   * Rejects consecutive dots in local part (e.g. test..foo@example.com),
   * leading/trailing dots, and other common invalid forms.
   */
  EMAIL: /^[^\s@.][^\s@]*(?:\.[^\s@.][^\s@]*)*@[^\s@]+\.[^\s@]+$/,
  /**
   * URL regex pattern.
   * Only allows http:// and https:// (case-insensitive scheme per
   * RFC 3986); explicitly rejects javascript:, data:, vbscript:, and
   * other dangerous URI schemes.
   */
  URL: /^https?:\/\/[^\s/$.?#][^\s]*$/i,
  /** UUID regex pattern (v4) */
  UUID: /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i
};
var ERRORS = {
  /** Generic error message (production) */
  INTERNAL_SERVER_ERROR: "Internal Server Error",
  /** Validation error messages */
  VALIDATION: {
    REQUIRED: (field) => `${field} is required`,
    INVALID_TYPE: (field, type) => `${field} must be a ${type}`,
    MIN_LENGTH: (field, min) => `${field} must be at least ${min} characters`,
    MAX_LENGTH: (field, max) => `${field} must be at most ${max} characters`,
    MIN_VALUE: (field, min) => `${field} must be at least ${min}`,
    MAX_VALUE: (field, max) => `${field} must be at most ${max}`,
    INVALID_FORMAT: (field) => `${field} format is invalid`,
    INVALID_EMAIL: (field) => `${field} must be a valid email`,
    INVALID_URL: (field) => `${field} must be a valid URL`,
    INVALID_UUID: (field) => `${field} must be a valid UUID`,
    INVALID_ENUM: (field, values) => `${field} must be one of: ${values.join(", ")}`,
    MIN_ITEMS: (field, min) => `${field} must have at least ${min} items`,
    MAX_ITEMS: (field, max) => `${field} must have at most ${max} items`
  }
};

// src/middleware/headers.ts
function createHeaders(options = {}) {
  const {
    contentSecurityPolicy = true,
    xssFilter = true,
    noSniff = true,
    frameOptions = HEADERS.FRAME_OPTIONS,
    hsts = true,
    referrerPolicy = HEADERS.REFERRER_POLICY,
    permissionsPolicy = HEADERS.PERMISSIONS_POLICY,
    cacheControl = true,
    crossOriginOpenerPolicy = "same-origin",
    crossOriginResourcePolicy = "same-origin",
    crossOriginEmbedderPolicy = "require-corp",
    originAgentCluster = true,
    dnsPrefetchControl = true
  } = options;
  return (req, res, next) => {
    if (contentSecurityPolicy) {
      const csp = typeof contentSecurityPolicy === "string" ? contentSecurityPolicy : HEADERS.DEFAULT_CSP;
      res.setHeader("Content-Security-Policy", csp);
    }
    if (xssFilter) {
      res.setHeader("X-XSS-Protection", "0");
    }
    if (noSniff) {
      res.setHeader("X-Content-Type-Options", HEADERS.CONTENT_TYPE_OPTIONS);
    }
    if (frameOptions) {
      res.setHeader("X-Frame-Options", frameOptions);
    }
    const forwardedProto = req.headers["x-forwarded-proto"]?.split(",")[0].trim().toLowerCase();
    const trustedForwardedProto = forwardedProto === "https" || forwardedProto === "http" ? forwardedProto : void 0;
    const isHttps = req.secure || trustedForwardedProto === "https";
    if (hsts && isHttps) {
      const hstsOpts = typeof hsts === "object" ? hsts : {};
      const maxAge = hstsOpts.maxAge ?? HEADERS.HSTS_MAX_AGE;
      const includeSubDomains = hstsOpts.includeSubDomains !== false;
      const preload = hstsOpts.preload === true;
      let hstsValue = `max-age=${maxAge}`;
      if (includeSubDomains) hstsValue += "; includeSubDomains";
      if (preload) hstsValue += "; preload";
      res.setHeader("Strict-Transport-Security", hstsValue);
    }
    if (referrerPolicy) {
      res.setHeader("Referrer-Policy", referrerPolicy);
    }
    if (permissionsPolicy) {
      res.setHeader("Permissions-Policy", permissionsPolicy);
    }
    if (crossOriginOpenerPolicy) {
      res.setHeader("Cross-Origin-Opener-Policy", crossOriginOpenerPolicy);
    }
    if (crossOriginResourcePolicy) {
      res.setHeader("Cross-Origin-Resource-Policy", crossOriginResourcePolicy);
    }
    if (crossOriginEmbedderPolicy) {
      res.setHeader("Cross-Origin-Embedder-Policy", crossOriginEmbedderPolicy);
    }
    if (originAgentCluster) {
      res.setHeader("Origin-Agent-Cluster", "?1");
    }
    if (dnsPrefetchControl) {
      res.setHeader("X-DNS-Prefetch-Control", "off");
    }
    res.setHeader("X-Permitted-Cross-Domain-Policies", "none");
    if (cacheControl) {
      const cacheControlValue = typeof cacheControl === "string" ? cacheControl : HEADERS.CACHE_CONTROL;
      res.setHeader("Cache-Control", cacheControlValue);
      res.setHeader("Pragma", "no-cache");
      res.setHeader("Expires", "0");
    }
    res.removeHeader("X-Powered-By");
    next();
  };
}

// src/middleware/rate-limit.ts
function createRateLimiter(options = {}) {
  const {
    max = RATE_LIMIT.DEFAULT_MAX_REQUESTS,
    windowMs = RATE_LIMIT.DEFAULT_WINDOW_MS,
    message = RATE_LIMIT.DEFAULT_MESSAGE,
    statusCode = RATE_LIMIT.DEFAULT_STATUS_CODE,
    keyGenerator = (req) => {
      const ip = req.ip ?? req.socket?.remoteAddress;
      if (ip) return ip;
      const ua = req.headers["user-agent"] ?? "";
      const lang = req.headers["accept-language"] ?? "";
      const fp = `${ua}|${lang}`;
      let hash = 0;
      for (let i = 0; i < fp.length; i++) hash = (hash << 5) - hash + fp.charCodeAt(i) | 0;
      return `unknown:${hash.toString(36)}`;
    },
    skip,
    store: externalStore
  } = options;
  const inMemoryStore = /* @__PURE__ */ Object.create(null);
  let cleanupInterval = null;
  if (!externalStore) {
    cleanupInterval = setInterval(() => {
      const now = Date.now();
      for (const key of Object.keys(inMemoryStore)) {
        if (inMemoryStore[key].resetTime < now) {
          delete inMemoryStore[key];
        }
      }
    }, windowMs);
    if (typeof cleanupInterval.unref === "function") {
      cleanupInterval.unref();
    }
  }
  const handler = async (req, res, next) => {
    try {
      if (skip?.(req)) {
        return next();
      }
      const key = keyGenerator(req);
      const now = Date.now();
      let count;
      let resetTime;
      if (externalStore) {
        const entry = await externalStore.get(key);
        if (!entry || entry.resetTime < now) {
          await externalStore.set(key, { count: 1, resetTime: now + windowMs });
          count = 1;
          resetTime = now + windowMs;
        } else {
          count = await externalStore.increment(key);
          resetTime = entry.resetTime;
        }
      } else {
        if (!inMemoryStore[key] || inMemoryStore[key].resetTime < now) {
          inMemoryStore[key] = { count: 1, resetTime: now + windowMs };
        } else {
          inMemoryStore[key].count++;
        }
        count = inMemoryStore[key].count;
        resetTime = inMemoryStore[key].resetTime;
      }
      const remaining = Math.max(0, max - count);
      const resetSeconds = Math.ceil((resetTime - now) / 1e3);
      res.setHeader("X-RateLimit-Limit", max.toString());
      res.setHeader("X-RateLimit-Remaining", remaining.toString());
      res.setHeader("X-RateLimit-Reset", resetSeconds.toString());
      if (count > max) {
        res.setHeader("Retry-After", resetSeconds.toString());
        res.status(statusCode).json({
          error: message,
          retryAfter: resetSeconds
        });
        return;
      }
      next();
    } catch (error) {
      console.error("[arcis] Rate limiter store error, using in-memory fallback:", error);
      try {
        const key = keyGenerator(req);
        const now = Date.now();
        if (!inMemoryStore[key] || inMemoryStore[key].resetTime < now) {
          inMemoryStore[key] = { count: 1, resetTime: now + windowMs };
        } else {
          inMemoryStore[key].count++;
        }
        const count = inMemoryStore[key].count;
        if (count > max) {
          const resetSeconds = Math.ceil((inMemoryStore[key].resetTime - now) / 1e3);
          res.setHeader("Retry-After", resetSeconds.toString());
          res.status(statusCode).json({ error: message, retryAfter: resetSeconds });
          return;
        }
      } catch {
      }
      next();
    }
  };
  const middleware = handler;
  middleware.close = () => {
    if (cleanupInterval) {
      clearInterval(cleanupInterval);
      cleanupInterval = null;
    }
  };
  return middleware;
}

// src/middleware/error-handler.ts
var SENSITIVE_ERROR_PATTERNS = [
  // SQL database errors
  /\b(SQLITE_ERROR|SQLSTATE|ORA-\d|PG::|mysql_|pg_query|ECONNREFUSED)/i,
  /\b(syntax error at or near|relation ".*" does not exist)/i,
  /\b(column ".*" (does not exist|of relation))/i,
  /\b(duplicate key value violates unique constraint)/i,
  /\b(table .* doesn't exist|unknown column)/i,
  // MongoDB errors
  /\b(MongoError|MongoServerError|MongoNetworkError|E11000 duplicate key)/i,
  // Redis errors
  /\b(WRONGTYPE|CROSSSLOT|CLUSTERDOWN|READONLY|ReplyError)/i,
  // Connection strings and DSNs
  /\b(mongodb(\+srv)?:\/\/|postgres(ql)?:\/\/|mysql:\/\/|redis:\/\/)/i,
  // Stack traces with file paths
  /\bat\s+.*\.(js|ts|py|go|java):\d+/i,
  // Internal IP addresses
  /\b(127\.0\.0\.\d+|10\.\d+\.\d+\.\d+|192\.168\.\d+\.\d+|172\.(1[6-9]|2\d|3[01])\.\d+\.\d+)\b/
];
function containsSensitiveInfo(message) {
  return SENSITIVE_ERROR_PATTERNS.some((pattern) => pattern.test(message));
}
function errorHandler(options = false) {
  const isDev = typeof options === "boolean" ? options : options.isDev ?? false;
  const logErrors = typeof options === "object" ? options.logErrors ?? true : true;
  const logger = typeof options === "object" ? options.logger : void 0;
  const customHandler = typeof options === "object" ? options.customHandler : void 0;
  return (err, req, res, _next) => {
    const rawStatus = err.statusCode ?? err.status ?? 500;
    const statusCode = Number.isFinite(rawStatus) && rawStatus >= 400 && rawStatus <= 599 ? Math.floor(rawStatus) : 500;
    if (customHandler) {
      return customHandler(err, req, res);
    }
    if (logErrors) {
      const logData = {
        error: err.message,
        stack: err.stack,
        statusCode,
        path: req.path,
        method: req.method
      };
      if (logger) {
        logger.error("Request error", logData);
      } else {
        console.error("[arcis] Request error:", logData);
      }
    }
    const exposeMessage = isDev || err.expose === true;
    let clientMessage;
    if (!exposeMessage) {
      clientMessage = ERRORS.INTERNAL_SERVER_ERROR;
    } else if (containsSensitiveInfo(err.message)) {
      clientMessage = isDev ? err.message : ERRORS.INTERNAL_SERVER_ERROR;
    } else {
      clientMessage = err.message;
    }
    const response = {
      error: clientMessage
    };
    if (isDev) {
      response.stack = err.stack;
      response.details = err.message;
    }
    res.status(statusCode).json(response);
  };
}
var createErrorHandler = errorHandler;

// src/core/errors.ts
var ArcisError = class extends Error {
  constructor(message, statusCode = 500, code = "ARCIS_ERROR") {
    super(message);
    this.name = "ArcisError";
    this.statusCode = statusCode;
    this.code = code;
    this.expose = statusCode < 500;
    if (Error.captureStackTrace) {
      Error.captureStackTrace(this, this.constructor);
    }
  }
};
var InputTooLargeError = class extends ArcisError {
  constructor(maxSize, actualSize) {
    super(`Input exceeds maximum size of ${maxSize} bytes`, 413, "INPUT_TOO_LARGE");
    this.name = "InputTooLargeError";
    this.maxSize = maxSize;
    this.actualSize = actualSize;
  }
};
var SecurityThreatError = class extends ArcisError {
  constructor(threatType, pattern) {
    super("Request blocked for security reasons", 400, "SECURITY_THREAT");
    this.name = "SecurityThreatError";
    this.threatType = threatType;
    this.pattern = pattern;
  }
};

// src/middleware/telemetry.ts
var THREAT_TO_VECTOR = {
  xss: "xss",
  sql_injection: "sql",
  nosql_injection: "nosql",
  path_traversal: "path",
  command_injection: "command",
  prototype_pollution: "prototype",
  header_injection: "header",
  ssti: "ssti",
  xxe: "xxe"
};
function createTelemetryEmitter(client) {
  return (req, res, next) => {
    const start = performance.now();
    res.on("finish", () => {
      try {
        const event = buildEvent(req, res.statusCode, performance.now() - start);
        client.record(event);
      } catch {
      }
    });
    next();
  };
}
function tapSanitizerThreats(handler) {
  return (req, res, next) => {
    handler(req, res, (err) => {
      if (err instanceof SecurityThreatError) {
        const vector = THREAT_TO_VECTOR[err.threatType] ?? err.threatType;
        req.__arcis = {
          vector,
          rule: `${vector}/match`,
          severity: "high",
          matchedPattern: err.pattern,
          reason: err.message,
          decision: "deny"
        };
      }
      next(err);
    });
  };
}
function buildEvent(req, status, latencyMs) {
  const marker = req.__arcis;
  const decision = marker?.decision ?? inferDecision(status);
  return {
    ts: (/* @__PURE__ */ new Date()).toISOString(),
    ip: extractIp(req),
    method: (req.method ?? "GET").toUpperCase(),
    path: req.path ?? req.url ?? "/",
    decision,
    vector: marker?.vector ?? (status === 429 ? "rate-limit" : void 0),
    rule: marker?.rule ?? (status === 429 ? "rate-limit/exceeded" : void 0),
    severity: marker?.severity ?? (status === 429 ? "medium" : void 0),
    userAgent: typeof req.headers?.["user-agent"] === "string" ? req.headers["user-agent"] : "",
    reason: marker?.reason,
    status,
    matchedPattern: marker?.matchedPattern,
    latencyMs: Math.max(0, latencyMs)
  };
}
function inferDecision(status) {
  if (status === 429) return "deny";
  if (status === 400) return "deny";
  if (status === 403) return "deny";
  return "allow";
}
function extractIp(req) {
  if (typeof req.ip === "string" && req.ip.length > 0) return req.ip;
  const remote = req.socket?.remoteAddress;
  return typeof remote === "string" ? remote : "0.0.0.0";
}

// src/sanitizers/utils.ts
function encodeHtmlEntities(str) {
  return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#x27;");
}

// src/sanitizers/xss.ts
function sanitizeXss(input, collectThreats = false, htmlEncode = false) {
  if (typeof input !== "string") {
    return collectThreats ? { value: String(input), wasSanitized: false, threats: [] } : String(input);
  }
  const threats = [];
  let value = input;
  let wasSanitized = false;
  for (const pattern of XSS_REMOVE_PATTERNS) {
    pattern.lastIndex = 0;
    if (pattern.test(value)) {
      pattern.lastIndex = 0;
      if (collectThreats) {
        const matches = value.match(pattern);
        if (matches) {
          for (const match of matches) {
            threats.push({
              type: "xss",
              pattern: pattern.source,
              original: match
            });
          }
        }
      }
      value = value.replace(pattern, "");
      wasSanitized = true;
    }
  }
  if (htmlEncode) {
    const encoded = encodeHtmlEntities(value);
    if (encoded !== value) {
      wasSanitized = true;
    }
    value = encoded;
  }
  if (collectThreats) {
    return { value, wasSanitized, threats };
  }
  return value;
}
function detectXss(input) {
  if (typeof input !== "string") return false;
  if (/\s+on\w+\s*=/i.test(input)) return true;
  if (/javascript\s*:/i.test(input)) return true;
  if (/vbscript\s*:/i.test(input)) return true;
  if (/data\s*:\s*text\/html/i.test(input)) return true;
  for (const pattern of XSS_PATTERNS) {
    pattern.lastIndex = 0;
    if (pattern.test(input)) {
      return true;
    }
  }
  return false;
}

// src/sanitizers/sql.ts
function sanitizeSql(input, collectThreats = false) {
  if (typeof input !== "string") {
    return collectThreats ? { value: String(input), wasSanitized: false, threats: [] } : String(input);
  }
  const threats = [];
  let value = input;
  let wasSanitized = false;
  for (const pattern of SQL_PATTERNS) {
    pattern.lastIndex = 0;
    if (pattern.test(value)) {
      pattern.lastIndex = 0;
      if (collectThreats) {
        const matches = value.match(pattern);
        if (matches) {
          for (const match of matches) {
            threats.push({
              type: "sql_injection",
              pattern: pattern.source,
              original: match
            });
          }
        }
      }
      value = value.replace(pattern, " ");
      wasSanitized = true;
    }
  }
  if (collectThreats) {
    return { value, wasSanitized, threats };
  }
  return value;
}
function detectSql(input) {
  if (typeof input !== "string") return false;
  for (const pattern of SQL_PATTERNS) {
    pattern.lastIndex = 0;
    if (pattern.test(input)) {
      return true;
    }
  }
  return false;
}

// src/sanitizers/path.ts
function sanitizePath(input, collectThreats = false) {
  if (typeof input !== "string") {
    return collectThreats ? { value: String(input), wasSanitized: false, threats: [] } : String(input);
  }
  const threats = [];
  let value = input;
  let wasSanitized = false;
  value = value.normalize("NFKC");
  let prev;
  do {
    prev = value;
    for (const pattern of PATH_PATTERNS) {
      pattern.lastIndex = 0;
      if (pattern.test(value)) {
        pattern.lastIndex = 0;
        if (collectThreats) {
          const matches = value.match(pattern);
          if (matches) {
            for (const match of matches) {
              threats.push({
                type: "path_traversal",
                pattern: pattern.source,
                original: match
              });
            }
          }
        }
        value = value.replace(pattern, "");
        wasSanitized = true;
      }
    }
  } while (value !== prev);
  if (collectThreats) {
    return { value, wasSanitized, threats };
  }
  return value;
}
function detectPathTraversal(input) {
  if (typeof input !== "string") return false;
  const normalized = input.normalize("NFKC");
  for (const pattern of PATH_PATTERNS) {
    pattern.lastIndex = 0;
    if (pattern.test(normalized)) {
      return true;
    }
  }
  return false;
}

// src/sanitizers/command.ts
function sanitizeCommand(input, collectThreats = false) {
  if (typeof input !== "string") {
    return collectThreats ? { value: String(input), wasSanitized: false, threats: [] } : String(input);
  }
  const threats = [];
  let value = input;
  let wasSanitized = false;
  for (const pattern of COMMAND_PATTERNS) {
    pattern.lastIndex = 0;
    if (pattern.test(value)) {
      pattern.lastIndex = 0;
      if (collectThreats) {
        const matches = value.match(pattern);
        if (matches) {
          for (const match of matches) {
            threats.push({
              type: "command_injection",
              pattern: pattern.source,
              original: match
            });
          }
        }
      }
      value = value.replace(pattern, " ");
      wasSanitized = true;
    }
  }
  if (collectThreats) {
    return { value, wasSanitized, threats };
  }
  return value;
}
function detectCommandInjection(input) {
  if (typeof input !== "string") return false;
  for (const pattern of COMMAND_PATTERNS) {
    pattern.lastIndex = 0;
    if (pattern.test(input)) {
      return true;
    }
  }
  return false;
}

// src/sanitizers/ssti.ts
var SSTI_DETECT_PATTERNS = [
  /** Jinja2 / Twig / Nunjucks: {{ ... }} */
  /\{\{.*?\}\}/g,
  /** Freemarker / Thymeleaf / Spring EL: ${ ... } */
  /\$\{.*?\}/g,
  /** ERB / EJS: <%= ... %> or <% ... %> */
  /<%[=\-]?.*?%>/gs,
  /** Pug / Jade / Slim: #{ ... } */
  /#\{.*?\}/g,
  /** Python dunder sandbox escape */
  /__(?:class|mro|subclasses|globals|builtins|import)__/gi,
  /** Jinja2 config leak: {{config.X}} or {{config['X']}} */
  /\{\{\s*config[.\[]/gi,
  /** Jinja2 built-in objects */
  /\{\{\s*(?:self|request|lipsum|cycler|joiner|namespace|range)\b/gi
];
function detectSsti(input) {
  if (typeof input !== "string") return false;
  for (const pattern of SSTI_DETECT_PATTERNS) {
    pattern.lastIndex = 0;
    if (pattern.test(input)) {
      return true;
    }
  }
  return false;
}

// src/sanitizers/xxe.ts
var XXE_DETECT_PATTERNS = [
  /** DOCTYPE declaration */
  /<!DOCTYPE\b/gi,
  /** ENTITY declaration */
  /<!ENTITY\b/gi,
  /** SYSTEM keyword with URI */
  /\bSYSTEM\s+["']/gi,
  /** PUBLIC keyword with URI */
  /\bPUBLIC\s+["']/gi,
  /** Parameter entity reference (%entity;) */
  /%\s*\w+\s*;/g,
  /** CDATA section (often used to smuggle payloads) */
  /<!\[CDATA\[/gi
];
function detectXxe(input) {
  if (typeof input !== "string") return false;
  for (const pattern of XXE_DETECT_PATTERNS) {
    pattern.lastIndex = 0;
    if (pattern.test(input)) {
      return true;
    }
  }
  return false;
}

// src/sanitizers/ldap.ts
var LDAP_DETECT_PATTERN = /[*()\\\x00]/;
var LDAP_INJECTION_PATTERN = /\)\s*\(|\*\s*\)\s*\(/;
var LDAP_NOT_BYPASS_PATTERN = /\)\s*\(\s*!|&\s*\(\s*!|\|\s*\(\s*!/;
function detectLdapInjection(input) {
  if (typeof input !== "string") return false;
  return LDAP_DETECT_PATTERN.test(input) || LDAP_INJECTION_PATTERN.test(input) || LDAP_NOT_BYPASS_PATTERN.test(input);
}

// src/sanitizers/xpath.ts
var XPATH_INJECTION_CHARS = /['"|,()]/;
var XPATH_INJECTION_PATTERN = /('\s*(or|and)\s*'|"\s*(or|and)\s*"|\)\s*(or|and)\s*\(|\|\s*\/)/i;
function detectXpathInjection(input) {
  if (typeof input !== "string" || input.length === 0) return false;
  if (!XPATH_INJECTION_CHARS.test(input)) return false;
  return XPATH_INJECTION_PATTERN.test(input);
}

// src/sanitizers/headers.ts
var HEADER_INJECTION_PATTERN = /\r\n|\r|\n|\0/g;
function detectHeaderInjection(input) {
  if (typeof input !== "string") return false;
  HEADER_INJECTION_PATTERN.lastIndex = 0;
  return HEADER_INJECTION_PATTERN.test(input);
}

// src/sanitizers/sanitize.ts
function multiDecode(value, maxPasses = 4) {
  for (let i = 0; i < maxPasses; i++) {
    const prev = value;
    try {
      value = decodeURIComponent(value);
    } catch {
    }
    value = htmlEntityDecode(value);
    if (value === prev) break;
  }
  return value;
}
function htmlEntityDecode(s) {
  s = s.replace(/&#(\d+);/g, (_m, n) => {
    const code = parseInt(n, 10);
    return Number.isFinite(code) && code >= 0 && code <= 1114111 ? String.fromCodePoint(code) : _m;
  });
  s = s.replace(/&#x([0-9a-fA-F]+);/g, (_m, h) => {
    const code = parseInt(h, 16);
    return Number.isFinite(code) && code >= 0 && code <= 1114111 ? String.fromCodePoint(code) : _m;
  });
  const named = {
    "&lt;": "<",
    "&gt;": ">",
    "&amp;": "&",
    "&quot;": '"',
    "&apos;": "'",
    "&nbsp;": " "
  };
  for (const [entity, ch] of Object.entries(named)) {
    s = s.split(entity).join(ch);
  }
  return s;
}
function sanitizeString(value, options = {}) {
  if (typeof value !== "string") return value;
  const maxSize = options.maxSize ?? INPUT.DEFAULT_MAX_SIZE;
  if (value.length > maxSize) {
    throw new InputTooLargeError(maxSize, value.length);
  }
  const reject = options.mode === "reject";
  let result = value.normalize("NFKC");
  result = multiDecode(result);
  if (options.sql !== false) {
    if (reject) {
      if (detectSql(result)) {
        throw new SecurityThreatError("sql_injection", "SQL pattern detected in input");
      }
    } else {
      result = sanitizeSql(result);
    }
  }
  if (options.path !== false) {
    result = sanitizePath(result);
  }
  if (options.command !== false) {
    if (reject) {
      if (detectCommandInjection(result)) {
        throw new SecurityThreatError("command_injection", "Shell metacharacter detected in input");
      }
    } else {
      result = sanitizeCommand(result);
    }
  }
  if (options.xss !== false) {
    result = sanitizeXss(result, false, options.htmlEncode ?? false);
  }
  return result;
}
function sanitizeObject(obj, options = {}) {
  if (obj === null || obj === void 0) return obj;
  if (typeof obj === "string") return sanitizeString(obj, options);
  if (typeof obj !== "object") return obj;
  if (Array.isArray(obj)) return obj.map((item) => sanitizeObject(item, options));
  const result = sanitizeObjectDepth(obj, options, 0);
  return options.freeze ? Object.freeze(result) : result;
}
function sanitizeObjectDepth(obj, options, depth) {
  if (depth >= INPUT.MAX_RECURSION_DEPTH) return obj;
  const result = {};
  for (const key of Object.keys(obj)) {
    if (options.proto !== false && DANGEROUS_PROTO_KEYS.has(key.toLowerCase())) {
      continue;
    }
    if (options.nosql !== false && NOSQL_DANGEROUS_KEYS.has(key)) {
      continue;
    }
    const sanitizedKey = sanitizeString(key, options);
    const value = obj[key];
    if (value === null || value === void 0) {
      result[sanitizedKey] = value;
    } else if (typeof value === "string") {
      result[sanitizedKey] = sanitizeString(value, options);
    } else if (Array.isArray(value)) {
      result[sanitizedKey] = value.map((item) => sanitizeObject(item, options));
    } else if (typeof value === "object") {
      result[sanitizedKey] = sanitizeObjectDepth(value, options, depth + 1);
    } else {
      result[sanitizedKey] = value;
    }
  }
  return result;
}
function scanThreats(data, depth = 0) {
  if (depth > INPUT.MAX_RECURSION_DEPTH) return null;
  if (data && typeof data === "object" && !Array.isArray(data)) {
    for (const key of Object.keys(data)) {
      const lower = key.toLowerCase();
      if (DANGEROUS_PROTO_KEYS.has(lower)) {
        return { vector: "prototype", rule: "prototype/match", matchedPattern: key };
      }
      if (NOSQL_DANGEROUS_KEYS.has(key)) {
        return { vector: "nosql", rule: "nosql/match", matchedPattern: key };
      }
      const inner = scanThreats(data[key], depth + 1);
      if (inner) return inner;
    }
    return null;
  }
  if (Array.isArray(data)) {
    for (const item of data) {
      const inner = scanThreats(item, depth + 1);
      if (inner) return inner;
    }
    return null;
  }
  if (typeof data !== "string") return null;
  const sample = data.slice(0, 80);
  if (detectXss(data)) {
    return { vector: "xss", rule: "xss/match", matchedPattern: sample };
  }
  if (detectSsti(data)) {
    return { vector: "ssti", rule: "ssti/match", matchedPattern: sample };
  }
  if (detectXxe(data)) {
    return { vector: "xxe", rule: "xxe/match", matchedPattern: sample };
  }
  if (detectSql(data)) {
    return { vector: "sql", rule: "sql/match", matchedPattern: sample };
  }
  if (detectPathTraversal(data)) {
    return { vector: "path", rule: "path/match", matchedPattern: sample };
  }
  if (detectCommandInjection(data)) {
    return { vector: "command", rule: "command/match", matchedPattern: sample };
  }
  if (detectLdapInjection(data)) {
    return { vector: "ldap", rule: "ldap/match", matchedPattern: sample };
  }
  if (detectXpathInjection(data)) {
    return { vector: "xpath", rule: "xpath/match", matchedPattern: sample };
  }
  if (detectHeaderInjection(data)) {
    return { vector: "header", rule: "header/match", matchedPattern: sample };
  }
  return null;
}
function createSanitizer(options = {}) {
  return (req, res, next) => {
    try {
      if (options.block) {
        const hit = scanThreats(req.body) || scanThreats(req.query) || scanThreats(req.params) || scanThreats(req.path);
        if (hit) {
          req.__arcis = {
            vector: hit.vector,
            rule: hit.rule,
            severity: "high",
            matchedPattern: hit.matchedPattern,
            reason: `${hit.vector} pattern detected in request`,
            decision: "deny"
          };
          res.status(403).json({
            error: "Request blocked for security reasons",
            code: "SECURITY_THREAT",
            vector: hit.vector
          });
          return;
        }
      }
      if (req.body && typeof req.body === "object") {
        req.body = sanitizeObject(req.body, options);
      }
      if (req.query && typeof req.query === "object") {
        const sanitizedQuery = sanitizeObject(req.query, options);
        Object.defineProperty(req, "query", { value: sanitizedQuery, writable: true, configurable: true });
      }
      if (req.params && typeof req.params === "object") {
        const sanitizedParams = sanitizeObject(req.params, options);
        Object.defineProperty(req, "params", { value: sanitizedParams, writable: true, configurable: true });
      }
      next();
    } catch (err) {
      next(err);
    }
  };
}

// src/validation/schema.ts
function validate(schema, source = "body") {
  return (req, res, next) => {
    const data = req[source] || {};
    const errors = [];
    const validated = {};
    for (const [field, rules] of Object.entries(schema)) {
      const value = data[field];
      const result = validateField(field, value, rules);
      if (result.errors.length > 0) {
        errors.push(...result.errors);
      } else if (result.value !== void 0) {
        validated[field] = result.value;
      }
    }
    if (errors.length > 0) {
      res.status(400).json({ errors });
      return;
    }
    Object.defineProperty(req, source, {
      value: validated,
      writable: true,
      configurable: true,
      enumerable: true
    });
    next();
  };
}
function validateField(field, value, rules) {
  const errors = [];
  if (rules.required && (value === void 0 || value === null || value === "")) {
    errors.push(ERRORS.VALIDATION.REQUIRED(field));
    return { errors };
  }
  if (value === void 0 || value === null) {
    return { errors: [] };
  }
  let typedValue = value;
  let isValid = true;
  switch (rules.type) {
    case "string":
      if (typeof value !== "string") {
        errors.push(ERRORS.VALIDATION.INVALID_TYPE(field, "string"));
        isValid = false;
        break;
      }
      if (rules.min !== void 0 && value.length < rules.min) {
        errors.push(ERRORS.VALIDATION.MIN_LENGTH(field, rules.min));
        isValid = false;
      }
      if (rules.max !== void 0 && value.length > rules.max) {
        errors.push(ERRORS.VALIDATION.MAX_LENGTH(field, rules.max));
        isValid = false;
      }
      if (rules.pattern && !rules.pattern.test(value)) {
        errors.push(ERRORS.VALIDATION.INVALID_FORMAT(field));
        isValid = false;
      }
      if (isValid && rules.enum && !rules.enum.includes(value)) {
        errors.push(ERRORS.VALIDATION.INVALID_ENUM(field, rules.enum));
        isValid = false;
      }
      if (isValid && rules.sanitize !== false) {
        typedValue = sanitizeString(value);
      }
      break;
    case "number":
      typedValue = Number(value);
      if (isNaN(typedValue)) {
        errors.push(ERRORS.VALIDATION.INVALID_TYPE(field, "number"));
        isValid = false;
        break;
      }
      if (rules.min !== void 0 && typedValue < rules.min) {
        errors.push(ERRORS.VALIDATION.MIN_VALUE(field, rules.min));
        isValid = false;
      }
      if (rules.max !== void 0 && typedValue > rules.max) {
        errors.push(ERRORS.VALIDATION.MAX_VALUE(field, rules.max));
        isValid = false;
      }
      break;
    case "boolean":
      if (value === "true" || value === true || value === 1 || value === "1") {
        typedValue = true;
      } else if (value === "false" || value === false || value === 0 || value === "0") {
        typedValue = false;
      } else {
        errors.push(ERRORS.VALIDATION.INVALID_TYPE(field, "boolean"));
        isValid = false;
      }
      break;
    case "email":
      if (!VALIDATION.EMAIL.test(String(value))) {
        errors.push(ERRORS.VALIDATION.INVALID_EMAIL(field));
        isValid = false;
      }
      if (isValid) {
        typedValue = sanitizeString(String(value).toLowerCase().trim());
      }
      break;
    case "url":
      if (!VALIDATION.URL.test(String(value))) {
        errors.push(ERRORS.VALIDATION.INVALID_URL(field));
        isValid = false;
      }
      if (isValid) {
        typedValue = sanitizeString(String(value));
      }
      break;
    case "uuid":
      if (!VALIDATION.UUID.test(String(value))) {
        errors.push(ERRORS.VALIDATION.INVALID_UUID(field));
        isValid = false;
      }
      break;
    case "array":
      if (!Array.isArray(value)) {
        errors.push(ERRORS.VALIDATION.INVALID_TYPE(field, "array"));
        isValid = false;
        break;
      }
      if (rules.min !== void 0 && value.length < rules.min) {
        errors.push(ERRORS.VALIDATION.MIN_ITEMS(field, rules.min));
        isValid = false;
      }
      if (rules.max !== void 0 && value.length > rules.max) {
        errors.push(ERRORS.VALIDATION.MAX_ITEMS(field, rules.max));
        isValid = false;
      }
      break;
    case "object":
      if (typeof value !== "object" || Array.isArray(value) || value === null) {
        errors.push(ERRORS.VALIDATION.INVALID_TYPE(field, "object"));
        isValid = false;
      }
      break;
  }
  if (isValid && rules.enum && rules.type !== "string" && !rules.enum.includes(typedValue)) {
    errors.push(ERRORS.VALIDATION.INVALID_ENUM(field, rules.enum));
    isValid = false;
  }
  if (isValid && rules.custom) {
    const customResult = rules.custom(typedValue);
    if (customResult === void 0) {
      throw new TypeError(
        `Custom validator for field "${field}" returned undefined. Return true to pass, false to fail, or a string error message.`
      );
    }
    if (customResult !== true) {
      errors.push(typeof customResult === "string" && customResult.length > 0 ? customResult : `${field} is invalid`);
      isValid = false;
    }
  }
  return {
    value: isValid ? typedValue : void 0,
    errors
  };
}

// src/validation/file.ts
({
  // Images
  "image/jpeg": [Buffer.from([255, 216, 255])],
  "image/png": [Buffer.from([137, 80, 78, 71])],
  "image/gif": [Buffer.from("GIF87a"), Buffer.from("GIF89a")],
  "image/webp": [Buffer.from("RIFF")],
  // RIFF....WEBP
  "image/bmp": [Buffer.from([66, 77])],
  // text-based, check separately
  // Documents
  "application/pdf": [Buffer.from("%PDF")],
  "application/zip": [Buffer.from([80, 75, 3, 4])],
  // Audio/Video
  "audio/mpeg": [Buffer.from([255, 251]), Buffer.from([255, 243]), Buffer.from([73, 68, 51])]});

// src/logging/redactor.ts
var LOG_LEVELS = {
  debug: 0,
  info: 1,
  warn: 2,
  error: 3,
  silent: 4
};
function createSafeLogger(options = {}) {
  const {
    redactKeys = [],
    maxLength = REDACTION.DEFAULT_MAX_LENGTH,
    redactPatterns = [],
    level: minLevel = "debug"
  } = options;
  const minLevelNum = LOG_LEVELS[minLevel] ?? 0;
  const allRedactKeys = /* @__PURE__ */ new Set([
    ...Array.from(REDACTION.SENSITIVE_KEYS),
    ...redactKeys.map((k) => k.toLowerCase())
  ]);
  function redact(obj, depth = 0) {
    if (depth > INPUT.MAX_RECURSION_DEPTH) return REDACTION.MAX_DEPTH;
    if (obj === null || obj === void 0) return obj;
    if (typeof obj === "string") {
      return redactString(obj, maxLength, redactPatterns);
    }
    if (typeof obj !== "object") return obj;
    if (Array.isArray(obj)) {
      return obj.map((item) => redact(item, depth + 1));
    }
    const result = {};
    for (const [key, value] of Object.entries(obj)) {
      if (allRedactKeys.has(key.toLowerCase())) {
        result[key] = REDACTION.REPLACEMENT;
      } else {
        result[key] = redact(value, depth + 1);
      }
    }
    return result;
  }
  function log(level, message, data) {
    const levelNum = LOG_LEVELS[level] ?? 0;
    if (levelNum < minLevelNum) return;
    const entry = {
      timestamp: (/* @__PURE__ */ new Date()).toISOString(),
      level,
      message: redactString(message, maxLength, redactPatterns)
    };
    if (data !== void 0) {
      entry.data = redact(data);
    }
    console.log(JSON.stringify(entry));
  }
  return {
    log,
    info: (msg, data) => log("info", msg, data),
    warn: (msg, data) => log("warn", msg, data),
    error: (msg, data) => log("error", msg, data),
    debug: (msg, data) => log("debug", msg, data)
  };
}
function redactString(str, maxLength, patterns) {
  let safe = str.replace(/[\r\n\t]/g, " ").replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F\x80-\x9F]/g, "");
  for (const pattern of patterns) {
    safe = safe.replace(pattern, REDACTION.REPLACEMENT);
  }
  if (safe.length > maxLength) {
    safe = safe.substring(0, maxLength) + `...${REDACTION.TRUNCATED}`;
  }
  return safe;
}

// src/telemetry/client.ts
var DEFAULT_BATCH_SIZE = 50;
var MAX_BATCH_SIZE = 500;
var DEFAULT_FLUSH_INTERVAL_MS = 5e3;
var MIN_FLUSH_INTERVAL_MS = 500;
var FLUSH_TIMEOUT_MS = 1e4;
var DEFAULT_MAX_QUEUE_SIZE = 1e4;
var TelemetryClient = class {
  constructor(options) {
    this.queue = [];
    this.flushing = false;
    this.closed = false;
    // Counts events dropped since the last successful flush. Resets to 0
    // each flush so onQueueOverflow callbacks see "drops in this window"
    // rather than a monotonic lifetime counter.
    this.droppedSinceLastFlush = 0;
    if (!options.endpoint || typeof options.endpoint !== "string") {
      throw new TypeError("TelemetryClient: `endpoint` is required");
    }
    this.endpoint = options.endpoint;
    this.apiKey = options.apiKey;
    this.workspaceId = options.workspaceId;
    this.batchSize = clamp(
      options.batchSize ?? DEFAULT_BATCH_SIZE,
      1,
      MAX_BATCH_SIZE
    );
    this.flushIntervalMs = Math.max(
      options.flushIntervalMs ?? DEFAULT_FLUSH_INTERVAL_MS,
      MIN_FLUSH_INTERVAL_MS
    );
    this.maxQueueSize = Math.max(
      options.maxQueueSize ?? DEFAULT_MAX_QUEUE_SIZE,
      this.batchSize
    );
    this.onError = options.onError ?? (() => {
    });
    this.onQueueOverflow = options.onQueueOverflow ?? (() => {
    });
    this.startTimer();
  }
  /**
   * Enqueue an event. Fast, synchronous, cannot throw.
   * Triggers a flush if the queue has reached `batchSize`.
   */
  record(event) {
    if (this.closed) return;
    this.queue.push(event);
    if (this.queue.length > this.maxQueueSize) {
      const drop = this.queue.length - this.maxQueueSize;
      this.queue.splice(0, drop);
      this.droppedSinceLastFlush += drop;
      try {
        this.onQueueOverflow(this.droppedSinceLastFlush);
      } catch {
      }
    }
    if (this.queue.length >= this.batchSize) {
      void this.flush();
    }
  }
  /**
   * Manually flush the queue. Pulls up to `batchSize` events into a batch and
   * POSTs them. Returns a resolved promise on success OR on handled failure.
   * Never throws.
   */
  async flush() {
    if (this.flushing) return;
    if (this.queue.length === 0) return;
    this.flushing = true;
    try {
      const batch = this.queue.splice(0, this.batchSize);
      await this.send(batch);
      this.droppedSinceLastFlush = 0;
    } catch (err) {
      this.safeNotify(err);
    } finally {
      this.flushing = false;
    }
    if (!this.closed && this.queue.length > 0) {
      void this.flush();
    }
  }
  /**
   * Shut down: stop the interval timer and attempt one final flush.
   * Safe to call multiple times.
   */
  async close() {
    if (this.closed) return;
    this.closed = true;
    if (this.timer !== void 0) {
      clearInterval(this.timer);
      this.timer = void 0;
    }
    if (this.signalHandler !== void 0) {
      process.off("SIGTERM", this.signalHandler);
      process.off("SIGINT", this.signalHandler);
      this.signalHandler = void 0;
    }
    try {
      await this.flush();
    } catch {
    }
  }
  /**
   * Register `SIGTERM` / `SIGINT` handlers that call `close()` to drain
   * the queue on graceful shutdown. Opt-in — libraries should not silently
   * attach global signal handlers. Safe to call multiple times.
   */
  installShutdownHooks() {
    if (this.signalHandler !== void 0 || this.closed) return;
    const handler = () => {
      void this.close();
    };
    this.signalHandler = handler;
    process.once("SIGTERM", handler);
    process.once("SIGINT", handler);
  }
  /** Count of events currently waiting to be sent. Useful for tests. */
  get pendingCount() {
    return this.queue.length;
  }
  // ── internals ─────────────────────────────────────────────────────────
  async send(batch) {
    const headers = {
      "content-type": "application/json"
    };
    if (this.apiKey) headers["authorization"] = `Bearer ${this.apiKey}`;
    if (this.workspaceId) headers["x-workspace-id"] = this.workspaceId;
    const controller = new AbortController();
    const abortTimer = setTimeout(() => controller.abort(), FLUSH_TIMEOUT_MS);
    try {
      const res = await fetch(this.endpoint, {
        method: "POST",
        headers,
        body: JSON.stringify({ events: batch }),
        signal: controller.signal
      });
      if (!res.ok) {
        const text = await safeReadBody(res);
        throw new TelemetryHttpError(res.status, text);
      }
    } finally {
      clearTimeout(abortTimer);
    }
  }
  startTimer() {
    this.timer = setInterval(() => {
      void this.flush();
    }, this.flushIntervalMs);
    this.timer.unref?.();
  }
  safeNotify(err) {
    try {
      this.onError(err instanceof Error ? err : new Error(String(err)));
    } catch {
    }
  }
};
var TelemetryHttpError = class extends Error {
  constructor(status, responseBody) {
    super(`Telemetry ingest returned HTTP ${status}`);
    this.status = status;
    this.responseBody = responseBody;
    this.name = "TelemetryHttpError";
  }
};
function clamp(value, min, max) {
  if (!Number.isFinite(value)) return min;
  return Math.max(min, Math.min(max, Math.trunc(value)));
}
async function safeReadBody(res) {
  try {
    const text = await res.text();
    return text.slice(0, 500);
  } catch {
    return "";
  }
}

// src/middleware/main.ts
function buildTelemetryFromEnv() {
  const env = typeof process !== "undefined" ? process.env : void 0;
  const endpoint = env?.ARCIS_ENDPOINT;
  if (!endpoint) return void 0;
  const opts = { endpoint };
  if (env?.ARCIS_WORKSPACE_ID) opts.workspaceId = env.ARCIS_WORKSPACE_ID;
  if (env?.ARCIS_KEY) opts.apiKey = env.ARCIS_KEY;
  const batch = env?.ARCIS_BATCH_SIZE ? parseInt(env.ARCIS_BATCH_SIZE, 10) : NaN;
  if (!Number.isNaN(batch)) opts.batchSize = batch;
  const flush = env?.ARCIS_FLUSH_INTERVAL_MS ? parseInt(env.ARCIS_FLUSH_INTERVAL_MS, 10) : NaN;
  if (!Number.isNaN(flush)) opts.flushIntervalMs = flush;
  return opts;
}
function createSanitizeObserver(onSanitize) {
  return (req, _res, next) => {
    const fields = [
      ["body", req.body],
      ["query", req.query],
      ["params", req.params],
      ["path", req.path]
    ];
    for (const [name, value] of fields) {
      const hit = scanThreats(value);
      if (!hit) continue;
      try {
        onSanitize({
          type: hit.vector,
          field: name,
          original: hit.matchedPattern,
          pattern: hit.matchedPattern
        });
      } catch {
      }
    }
    next();
  };
}
function suppressRateLimit429(handler) {
  return (req, res, next) => {
    const originalStatus = res.status.bind(res);
    const originalJson = res.json.bind(res);
    let suppressed = false;
    let nextCalled = false;
    const restore = () => {
      res.status = originalStatus;
      res.json = originalJson;
    };
    res.status = ((code) => {
      if (code === 429) {
        suppressed = true;
        return res;
      }
      return originalStatus(code);
    });
    res.json = ((body) => {
      if (suppressed) {
        restore();
        if (!nextCalled) {
          nextCalled = true;
          next();
        }
        return res;
      }
      return originalJson(body);
    });
    handler(req, res, (err) => {
      restore();
      if (!nextCalled) {
        nextCalled = true;
        next(err);
      }
    });
  };
}
function arcis(options = {}) {
  const middlewares = [];
  const cleanupFns = [];
  const dryRun = options.dryRun === true;
  let telemetryClient;
  const telemetryOpts = options.telemetry?.endpoint ? options.telemetry : buildTelemetryFromEnv();
  if (telemetryOpts) {
    const client = new TelemetryClient(telemetryOpts);
    telemetryClient = client;
    middlewares.push(createTelemetryEmitter(client));
    cleanupFns.push(() => {
      void client.close();
    });
  }
  if (options.headers !== false) {
    const headerOpts = typeof options.headers === "object" ? options.headers : {};
    middlewares.push(createHeaders(headerOpts));
  }
  if (options.onSanitize) {
    middlewares.push(createSanitizeObserver(options.onSanitize));
  }
  if (options.rateLimit !== false) {
    const rateLimitOpts = typeof options.rateLimit === "object" ? options.rateLimit : {};
    const rateLimiter = createRateLimiter(rateLimitOpts);
    middlewares.push(dryRun ? suppressRateLimit429(rateLimiter) : rateLimiter);
    cleanupFns.push(() => rateLimiter.close());
  }
  if (options.sanitize !== false) {
    const sanitizeOpts = typeof options.sanitize === "object" ? { ...options.sanitize } : {};
    if (options.block && sanitizeOpts.block === void 0) {
      sanitizeOpts.block = true;
    }
    if (dryRun) {
      sanitizeOpts.block = false;
    }
    const sanitizer = createSanitizer(sanitizeOpts);
    middlewares.push(telemetryClient ? tapSanitizerThreats(sanitizer) : sanitizer);
  }
  const result = middlewares;
  result.close = () => {
    for (const fn of cleanupFns) {
      fn();
    }
  };
  return result;
}
var arcisWithMethods = arcis;
arcisWithMethods.sanitize = createSanitizer;
arcisWithMethods.rateLimit = createRateLimiter;
arcisWithMethods.headers = createHeaders;
arcisWithMethods.validate = validate;
arcisWithMethods.logger = createSafeLogger;
arcisWithMethods.errorHandler = createErrorHandler;

// src/middleware/nestjs.ts
var ARCIS_OPTIONS = /* @__PURE__ */ Symbol("ARCIS_OPTIONS");
var ArcisMiddleware = class {
  constructor(options = {}) {
    this.handlers = arcis(options);
  }
  use(req, res, next) {
    const handlers = this.handlers;
    let i = 0;
    const run = (err) => {
      if (err !== void 0) {
        next(err);
        return;
      }
      const handler = handlers[i++];
      if (!handler) {
        next();
        return;
      }
      try {
        handler(req, res, run);
      } catch (caught) {
        next(caught);
      }
    };
    run();
  }
  /** Release rate-limiter intervals etc. Call from `OnApplicationShutdown`. */
  close() {
    this.handlers.close();
  }
};
var ArcisModule = class _ArcisModule {
  static forRoot(options = {}) {
    return {
      module: _ArcisModule,
      providers: [
        { provide: ARCIS_OPTIONS, useValue: options },
        {
          provide: ArcisMiddleware,
          useFactory: (opts) => new ArcisMiddleware(opts),
          inject: [ARCIS_OPTIONS]
        }
      ],
      exports: [ArcisMiddleware]
    };
  }
};
var nestjs_default = ArcisModule;

exports.ARCIS_OPTIONS = ARCIS_OPTIONS;
exports.ArcisMiddleware = ArcisMiddleware;
exports.ArcisModule = ArcisModule;
exports.default = nestjs_default;
//# sourceMappingURL=index.js.map
//# sourceMappingURL=index.js.map