// 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 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"
]);

// 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/sanitizers/utils.ts
function encodeHtmlEntities(str) {
  return str.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#x27;");
}
function isPlainObject(value) {
  if (typeof value !== "object" || value === null || Array.isArray(value)) {
    return false;
  }
  const proto = Object.getPrototypeOf(value);
  return proto === Object.prototype || proto === null;
}

// 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
];
var SSTI_REMOVE_PATTERNS = [
  /** Jinja2 / Twig: {{ ... }} — always strip (not valid in any JS context) */
  /\{\{.*?\}\}/g,
  /**
   * Freemarker / Spring EL: ${...} — strip when expression contains operators,
   * method calls, or Python dunder patterns (sandbox escape).
   * Bare ${name} and ${user.name} are left intact (JS template literal syntax).
   */
  /\$\{[^}]*__\w+__[^}]*\}/g,
  /\$\{[^}]*[?!()*+\-/][^}]*\}/g,
  /** ERB / EJS: <%= ... %> */
  /<%[=\-]?.*?%>/gs,
  /**
   * Pug / Jade: #{...} — same narrowing as ${ above, plus dunder detection.
   * #{name} output expressions are left intact.
   */
  /#\{[^}]*__\w+__[^}]*\}/g,
  /#\{[^}]*[?!()*+\-/][^}]*\}/g,
  /** Python dunder sandbox escape — always strip */
  /__(?:class|mro|subclasses|globals|builtins|import)__/gi
];
function sanitizeSsti(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 SSTI_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: "ssti",
              pattern: pattern.source,
              original: match
            });
          }
        }
      }
      value = value.replace(pattern, "");
      wasSanitized = true;
    }
  }
  if (collectThreats) {
    return { value, wasSanitized, threats };
  }
  return value;
}
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 MAX_XXE_INPUT_BYTES = 1e6;
var MAX_ENTITY_REFERENCES = 64;
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
];
var XXE_REMOVE_PATTERNS = [
  /** Full DOCTYPE block with optional internal subset: <!DOCTYPE ... [...]> */
  /<!DOCTYPE\s[^[>]*(?:\[[^\]]*\]\s*)?>|<!DOCTYPE\s[^>]*>/gi,
  /** Full ENTITY declaration: <!ENTITY ... > */
  /<!ENTITY[^>]*>/gi,
  /** CDATA sections: <![CDATA[ ... ]]> */
  /<!\[CDATA\[[\s\S]*?\]\]>/gi
];
function sanitizeXxe(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;
  if (value.length > MAX_XXE_INPUT_BYTES) {
    if (collectThreats) {
      threats.push({ type: "xxe", pattern: "oversize_input", original: `length=${value.length}` });
    }
    return collectThreats ? { value: "", wasSanitized: true, threats } : "";
  }
  const entityRefs = value.match(/&\w+;/g);
  if (entityRefs && entityRefs.length > MAX_ENTITY_REFERENCES) {
    if (collectThreats) {
      threats.push({ type: "xxe", pattern: "entity_expansion", original: `count=${entityRefs.length}` });
    }
    return collectThreats ? { value: "", wasSanitized: true, threats } : "";
  }
  for (const pattern of XXE_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: "xxe",
              pattern: pattern.source,
              original: match
            });
          }
        }
      }
      value = value.replace(pattern, "");
      wasSanitized = true;
    }
  }
  if (collectThreats) {
    return { value, wasSanitized, threats };
  }
  return value;
}
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_FILTER_CHARS = /[*()\\\x00]/g;
var LDAP_DN_CHARS = /[,+<>;"=\/\\\x00*()\x00]/g;
var LDAP_DETECT_PATTERN = /[*()\\\x00]/;
var LDAP_INJECTION_PATTERN = /\)\s*\(|\*\s*\)\s*\(/;
var LDAP_NOT_BYPASS_PATTERN = /\)\s*\(\s*!|&\s*\(\s*!|\|\s*\(\s*!/;
var escapeChar = (char) => "\\" + char.charCodeAt(0).toString(16).padStart(2, "0");
function sanitizeLdapFilter(input) {
  if (typeof input !== "string") return String(input);
  return input.replace(LDAP_FILTER_CHARS, escapeChar);
}
function sanitizeLdapDn(input) {
  if (typeof input !== "string") return String(input);
  return input.replace(LDAP_DN_CHARS, escapeChar);
}
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);
}
function sanitizeXpath(input) {
  if (typeof input !== "string") return String(input);
  return input.replace(/['"|,]/g, "");
}

// src/sanitizers/headers.ts
var HEADER_INJECTION_PATTERN = /\r\n|\r|\n|\0/g;
function sanitizeHeaderValue(input, collectThreats = false) {
  if (typeof input !== "string") {
    return collectThreats ? { value: String(input), wasSanitized: false, threats: [] } : String(input);
  }
  const threats = [];
  let wasSanitized = false;
  if (HEADER_INJECTION_PATTERN.test(input)) {
    HEADER_INJECTION_PATTERN.lastIndex = 0;
    wasSanitized = true;
    if (collectThreats) {
      const matches = input.match(HEADER_INJECTION_PATTERN);
      if (matches) {
        for (const match of matches) {
          threats.push({
            type: "header_injection",
            pattern: HEADER_INJECTION_PATTERN.source,
            original: match
          });
        }
      }
    }
  }
  HEADER_INJECTION_PATTERN.lastIndex = 0;
  const value = input.replace(HEADER_INJECTION_PATTERN, "");
  if (collectThreats) {
    return { value, wasSanitized, threats };
  }
  return value;
}
function sanitizeHeaders(headers) {
  if (!headers || typeof headers !== "object") {
    return {};
  }
  const result = {};
  for (const [key, value] of Object.entries(headers)) {
    const sanitizedKey = sanitizeHeaderValue(String(key));
    const sanitizedValue = sanitizeHeaderValue(String(value));
    result[sanitizedKey] = sanitizedValue;
  }
  return result;
}
function detectHeaderInjection(input) {
  if (typeof input !== "string") return false;
  HEADER_INJECTION_PATTERN.lastIndex = 0;
  return HEADER_INJECTION_PATTERN.test(input);
}
var sanitizeEmailHeader = sanitizeHeaderValue;
var detectEmailHeaderInjection = detectHeaderInjection;

// 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/sanitizers/nosql.ts
function isDangerousNoSqlKey(key) {
  return NOSQL_DANGEROUS_KEYS.has(key);
}
function detectNoSqlInjection(obj, maxDepth = 10) {
  if (maxDepth <= 0) return false;
  if (obj === null || typeof obj !== "object") return false;
  if (Array.isArray(obj)) {
    return obj.some((item) => detectNoSqlInjection(item, maxDepth - 1));
  }
  for (const key of Object.keys(obj)) {
    if (isDangerousNoSqlKey(key)) {
      return true;
    }
    const value = obj[key];
    if (typeof value === "object" && value !== null) {
      if (detectNoSqlInjection(value, maxDepth - 1)) {
        return true;
      }
    }
  }
  return false;
}
function getDangerousOperators() {
  return Array.from(NOSQL_DANGEROUS_KEYS);
}

// src/sanitizers/prototype.ts
function isDangerousProtoKey(key) {
  return DANGEROUS_PROTO_KEYS.has(key.toLowerCase());
}
function detectPrototypePollution(obj, maxDepth = 10) {
  if (maxDepth <= 0) return false;
  if (obj === null || typeof obj !== "object") return false;
  if (Array.isArray(obj)) {
    return obj.some((item) => detectPrototypePollution(item, maxDepth - 1));
  }
  for (const key of Object.keys(obj)) {
    if (DANGEROUS_PROTO_KEYS.has(key.toLowerCase())) {
      return true;
    }
    const value = obj[key];
    if (typeof value === "object" && value !== null) {
      if (detectPrototypePollution(value, maxDepth - 1)) {
        return true;
      }
    }
  }
  return false;
}
function getDangerousProtoKeys() {
  return Array.from(DANGEROUS_PROTO_KEYS);
}

// src/sanitizers/jsonp.ts
var SAFE_CALLBACK_PATTERN = /^[a-zA-Z_$][a-zA-Z0-9_$.]*$/;
var DANGEROUS_CALLBACK_PATTERNS = [
  /\.\./
  // prototype chain traversal
];
function sanitizeJsonpCallback(callback, maxLength = 128) {
  if (typeof callback !== "string" || callback.length === 0) {
    return null;
  }
  if (callback.length > maxLength) {
    return null;
  }
  if (!SAFE_CALLBACK_PATTERN.test(callback)) {
    return null;
  }
  for (const pattern of DANGEROUS_CALLBACK_PATTERNS) {
    if (pattern.test(callback)) {
      return null;
    }
  }
  return callback;
}
function detectJsonpInjection(callback) {
  if (typeof callback !== "string" || callback.length === 0) {
    return false;
  }
  if (!SAFE_CALLBACK_PATTERN.test(callback)) {
    return true;
  }
  for (const pattern of DANGEROUS_CALLBACK_PATTERNS) {
    if (pattern.test(callback)) {
      return true;
    }
  }
  return false;
}

// src/sanitizers/pii.ts
var EMAIL_RE = /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z]{2,})+/g;
var PHONE_RE = /(?<!\d)(?:\+?1[-.\s]?)?\(?[2-9]\d{2}\)?[-.\s]?\d{3}[-.\s]?\d{4}(?!\d)/g;
var CREDIT_CARD_RE = /\b(?:\d[ -]*?){13,19}\b/g;
var SSN_RE = /\b\d{3}[-\s]\d{2}[-\s]\d{4}\b/g;
var IPV4_RE = /\b(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\b/g;
var IPV6_RE = /\b(?:[0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}\b|\b(?:[0-9a-fA-F]{1,4}:){1,7}:|::(?:[0-9a-fA-F]{1,4}:){0,5}[0-9a-fA-F]{1,4}\b/g;
var PATTERN_MAP = {
  email: [EMAIL_RE],
  phone: [PHONE_RE],
  credit_card: [CREDIT_CARD_RE],
  ssn: [SSN_RE],
  ip_address: [IPV4_RE, IPV6_RE]
};
var ALL_TYPES = ["email", "phone", "credit_card", "ssn", "ip_address"];
var TYPE_LABELS = {
  email: "[EMAIL]",
  phone: "[PHONE]",
  credit_card: "[CREDIT_CARD]",
  ssn: "[SSN]",
  ip_address: "[IP_ADDRESS]"
};
function luhnCheck(value) {
  const digits = value.replace(/[\s-]/g, "");
  if (!/^\d{13,19}$/.test(digits)) return false;
  let sum = 0;
  let alternate = false;
  for (let i = digits.length - 1; i >= 0; i--) {
    let n = parseInt(digits[i], 10);
    if (alternate) {
      n *= 2;
      if (n > 9) n -= 9;
    }
    sum += n;
    alternate = !alternate;
  }
  return sum % 10 === 0;
}
function scanPii(input, options = {}) {
  if (!input || typeof input !== "string") return [];
  const types = options.types ?? ALL_TYPES;
  const matches = [];
  for (const type of types) {
    const patterns = PATTERN_MAP[type];
    if (!patterns) continue;
    for (const pattern of patterns) {
      const re = new RegExp(pattern.source, pattern.flags);
      let match;
      while ((match = re.exec(input)) !== null) {
        const value = match[0];
        if (type === "credit_card" && !luhnCheck(value)) continue;
        if (type === "ssn") {
          const area = parseInt(value.substring(0, 3), 10);
          if (area === 0 || area === 666 || area >= 900) continue;
        }
        matches.push({
          type,
          value,
          start: match.index,
          end: match.index + value.length
        });
      }
    }
  }
  matches.sort((a, b) => a.start - b.start);
  return matches;
}
function detectPii(input, options = {}) {
  return scanPii(input, options).length > 0;
}
function redactPii(input, options = {}) {
  if (!input || typeof input !== "string") return input;
  const matches = scanPii(input, options);
  if (matches.length === 0) return input;
  const replacement = options.replacement ?? "[REDACTED]";
  let result = input;
  for (let i = matches.length - 1; i >= 0; i--) {
    const m = matches[i];
    const label = options.typeLabels ? TYPE_LABELS[m.type] : replacement;
    result = result.substring(0, m.start) + label + result.substring(m.end);
  }
  return result;
}
function scanObjectPii(obj, options = {}, path = "") {
  const results = [];
  if (!obj || typeof obj !== "object") return results;
  for (const [key, value] of Object.entries(obj)) {
    const fieldPath = path ? `${path}.${key}` : key;
    if (typeof value === "string") {
      const matches = scanPii(value, options);
      for (const m of matches) {
        results.push({ ...m, field: fieldPath });
      }
    } else if (value && typeof value === "object" && !Array.isArray(value)) {
      results.push(...scanObjectPii(value, options, fieldPath));
    } else if (Array.isArray(value)) {
      for (let i = 0; i < value.length; i++) {
        const item = value[i];
        if (typeof item === "string") {
          const matches = scanPii(item, options);
          for (const m of matches) {
            results.push({ ...m, field: `${fieldPath}[${i}]` });
          }
        } else if (item && typeof item === "object") {
          results.push(...scanObjectPii(item, options, `${fieldPath}[${i}]`));
        }
      }
    }
  }
  return results;
}
function redactObjectPii(obj, options = {}) {
  if (!obj || typeof obj !== "object") return obj;
  const result = {};
  for (const [key, value] of Object.entries(obj)) {
    if (typeof value === "string") {
      result[key] = redactPii(value, options);
    } else if (Array.isArray(value)) {
      result[key] = value.map((item) => {
        if (typeof item === "string") return redactPii(item, options);
        if (item && typeof item === "object") return redactObjectPii(item, options);
        return item;
      });
    } else if (value && typeof value === "object") {
      result[key] = redactObjectPii(value, options);
    } else {
      result[key] = value;
    }
  }
  return result;
}

// src/sanitizers/encode.ts
var HTML_ENTITIES = {
  "&": "&amp;",
  "<": "&lt;",
  ">": "&gt;",
  '"': "&quot;",
  "'": "&#x27;"
};
var HTML_ENCODE_RE = /[&<>"']/g;
function encodeForHtml(value) {
  if (!value) return "";
  return value.replace(HTML_ENCODE_RE, (ch) => HTML_ENTITIES[ch]);
}
function encodeForAttribute(value) {
  if (!value) return "";
  let result = "";
  for (let i = 0; i < value.length; i++) {
    const ch = value.charCodeAt(i);
    if (ch >= 48 && ch <= 57 || // 0-9
    ch >= 65 && ch <= 90 || // A-Z
    ch >= 97 && ch <= 122) {
      result += value[i];
    } else {
      result += `&#x${ch.toString(16).toUpperCase()};`;
    }
  }
  return result;
}
function encodeForJs(value) {
  if (!value) return "";
  let result = "";
  for (const char of value) {
    const cp = char.codePointAt(0);
    if (cp === void 0) continue;
    if (cp >= 48 && cp <= 57 || // 0-9
    cp >= 65 && cp <= 90 || // A-Z
    cp >= 97 && cp <= 122) {
      result += char;
    } else if (cp < 256) {
      result += `\\x${cp.toString(16).toUpperCase().padStart(2, "0")}`;
    } else if (cp <= 65535) {
      result += `\\u${cp.toString(16).toUpperCase().padStart(4, "0")}`;
    } else {
      result += `\\u{${cp.toString(16).toUpperCase()}}`;
    }
  }
  return result;
}
function encodeForUrl(value) {
  if (!value) return "";
  return encodeURIComponent(value).replace(/[!'()*]/g, (ch) => {
    return `%${ch.charCodeAt(0).toString(16).toUpperCase()}`;
  });
}
function encodeForCss(value) {
  if (!value) return "";
  let result = "";
  for (let i = 0; i < value.length; i++) {
    const ch = value.charCodeAt(i);
    if (ch >= 48 && ch <= 57 || // 0-9
    ch >= 65 && ch <= 90 || // A-Z
    ch >= 97 && ch <= 122) {
      result += value[i];
    } else {
      result += `\\${ch.toString(16).toUpperCase()} `;
    }
  }
  return result;
}

// src/sanitizers/graphql.ts
var DEFAULTS = {
  maxDepth: 10,
  maxLength: 1e4,
  blockIntrospection: true,
  maxAliases: 50,
  blockFragmentCycles: true
};
var INTROSPECTION_PATTERN = /\b__(schema|type|typeKind|directive)\b/;
function computeDepth(query) {
  let depth = 0;
  let max = 0;
  for (let i = 0; i < query.length; i++) {
    const c = query.charCodeAt(i);
    if (c === 123) {
      depth++;
      if (depth > max) max = depth;
    } else if (c === 125) {
      if (depth > 0) depth--;
    }
  }
  return max;
}
var ALIAS_PATTERN = /\b([a-zA-Z_][a-zA-Z0-9_]*)\s*:\s*([a-zA-Z_][a-zA-Z0-9_]*)\b/g;
var FRAGMENT_DEF_PATTERN = /\bfragment\s+([a-zA-Z_][a-zA-Z0-9_]*)\s+on\s+[a-zA-Z_][a-zA-Z0-9_]*\s*\{/g;
var FRAGMENT_SPREAD_PATTERN = /\.\.\.\s*([a-zA-Z_][a-zA-Z0-9_]*)\b/g;
function countAliases(query) {
  let n = 0;
  ALIAS_PATTERN.lastIndex = 0;
  while (ALIAS_PATTERN.exec(query) !== null) n++;
  return n;
}
function hasFragmentCycle(query) {
  const deps = /* @__PURE__ */ new Map();
  FRAGMENT_DEF_PATTERN.lastIndex = 0;
  let match;
  while ((match = FRAGMENT_DEF_PATTERN.exec(query)) !== null) {
    const name = match[1];
    const bodyStart = match.index + match[0].length;
    let depth = 1;
    let i = bodyStart;
    while (i < query.length && depth > 0) {
      const ch = query[i];
      if (ch === "{") depth++;
      else if (ch === "}") depth--;
      i++;
    }
    const bodyEnd = depth === 0 ? i - 1 : i;
    const body = query.slice(bodyStart, bodyEnd);
    const spreads = /* @__PURE__ */ new Set();
    FRAGMENT_SPREAD_PATTERN.lastIndex = 0;
    let sm;
    while ((sm = FRAGMENT_SPREAD_PATTERN.exec(body)) !== null) {
      spreads.add(sm[1]);
    }
    deps.set(name, spreads);
  }
  if (deps.size === 0) return false;
  const WHITE = 0;
  const GRAY = 1;
  const BLACK = 2;
  const color = /* @__PURE__ */ new Map();
  for (const name of deps.keys()) color.set(name, WHITE);
  function visit(name) {
    if (color.get(name) === GRAY) return true;
    if (color.get(name) === BLACK) return false;
    if (!deps.has(name)) return false;
    color.set(name, GRAY);
    for (const child of deps.get(name)) {
      if (visit(child)) return true;
    }
    color.set(name, BLACK);
    return false;
  }
  for (const name of deps.keys()) {
    if (visit(name)) return true;
  }
  return false;
}
function inspectGraphqlQuery(query, options = {}) {
  const maxDepth = options.maxDepth ?? DEFAULTS.maxDepth;
  const maxLength = options.maxLength ?? DEFAULTS.maxLength;
  const blockIntrospection = options.blockIntrospection ?? DEFAULTS.blockIntrospection;
  const maxAliases = options.maxAliases ?? DEFAULTS.maxAliases;
  const blockFragmentCycles = options.blockFragmentCycles ?? DEFAULTS.blockFragmentCycles;
  const length = query.length;
  const depth = computeDepth(query);
  const aliases = countAliases(query);
  if (depth > maxDepth) {
    return { blocked: true, reason: "depth", depth, length, aliases };
  }
  if (blockIntrospection && INTROSPECTION_PATTERN.test(query)) {
    return { blocked: true, reason: "introspection", depth, length, aliases };
  }
  if (aliases > maxAliases) {
    return { blocked: true, reason: "aliases", depth, length, aliases };
  }
  if (blockFragmentCycles && hasFragmentCycle(query)) {
    return { blocked: true, reason: "fragment_cycle", depth, length, aliases };
  }
  if (length > maxLength) {
    return { blocked: true, reason: "length", depth, length, aliases };
  }
  return { blocked: false, depth, length, aliases };
}
function detectGraphqlAbuse(query, options) {
  if (typeof query !== "string" || query.length === 0) return false;
  return inspectGraphqlQuery(query, options).blocked;
}

// src/sanitizers/deserialization.ts
var PICKLE_HEAD = /^\x80[\x02-\x05]/;
var RUBY_MARSHAL_HEAD = /^\x04\x08/;
var DOTNET_BINFMT_HEAD = /^\x00\x01\x00\x00\x00/;
var FASTJSON_AUTOTYPE = /"@type"\s*:\s*"[a-zA-Z_$][\w$.]*"/;
var PHP_UNSERIALIZE = /O:\d+:"[a-zA-Z_\\][\w\\]*":\d+:\{/;
function detectDeserialization(payload) {
  if (typeof payload !== "string" || payload.length === 0) {
    return null;
  }
  if (PICKLE_HEAD.test(payload)) return "python_pickle";
  if (RUBY_MARSHAL_HEAD.test(payload)) return "ruby_marshal";
  if (DOTNET_BINFMT_HEAD.test(payload)) return "dotnet_binary_formatter";
  if (FASTJSON_AUTOTYPE.test(payload)) return "java_fastjson";
  if (PHP_UNSERIALIZE.test(payload)) return "php_unserialize";
  return null;
}
function isSerializedPayload(payload) {
  return detectDeserialization(payload) !== null;
}

export { createSanitizer, detectCommandInjection, detectDeserialization, detectEmailHeaderInjection, detectGraphqlAbuse, detectHeaderInjection, detectJsonpInjection, detectLdapInjection, detectNoSqlInjection, detectPathTraversal, detectPii, detectPrototypePollution, detectSql, detectSsti, detectXpathInjection, detectXss, detectXxe, encodeForAttribute, encodeForCss, encodeForHtml, encodeForJs, encodeForUrl, encodeHtmlEntities, getDangerousOperators, getDangerousProtoKeys, inspectGraphqlQuery, isDangerousNoSqlKey, isDangerousProtoKey, isPlainObject, isSerializedPayload, redactObjectPii, redactPii, sanitizeCommand, sanitizeEmailHeader, sanitizeHeaderValue, sanitizeHeaders, sanitizeJsonpCallback, sanitizeLdapDn, sanitizeLdapFilter, sanitizeObject, sanitizePath, sanitizeSql, sanitizeSsti, sanitizeString, sanitizeXpath, sanitizeXss, sanitizeXxe, scanObjectPii, scanPii, scanThreats };
//# sourceMappingURL=index.mjs.map
//# sourceMappingURL=index.mjs.map