nextjs-secure 0.3.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -869,8 +869,8 @@ function withRateLimit(handler, config) {
869
869
  };
870
870
  try {
871
871
  if (finalConfig.skip) {
872
- const shouldSkip = await finalConfig.skip(request);
873
- if (shouldSkip) {
872
+ const shouldSkip2 = await finalConfig.skip(request);
873
+ if (shouldSkip2) {
874
874
  debug("Skipping rate limit check");
875
875
  return handler(request, ctx);
876
876
  }
@@ -946,8 +946,8 @@ async function checkRateLimit(request, config) {
946
946
  const windowMs = parseDuration(finalConfig.window);
947
947
  const algorithm = getAlgorithm(finalConfig.algorithm);
948
948
  if (finalConfig.skip) {
949
- const shouldSkip = await finalConfig.skip(request);
950
- if (shouldSkip) {
949
+ const shouldSkip2 = await finalConfig.skip(request);
950
+ if (shouldSkip2) {
951
951
  const info2 = {
952
952
  limit: finalConfig.limit,
953
953
  remaining: finalConfig.limit,
@@ -1123,8 +1123,8 @@ function withCSRF(handler, config = {}) {
1123
1123
  return handler(req);
1124
1124
  }
1125
1125
  if (config.skip) {
1126
- const shouldSkip = await config.skip(req);
1127
- if (shouldSkip) return handler(req);
1126
+ const shouldSkip2 = await config.skip(req);
1127
+ if (shouldSkip2) return handler(req);
1128
1128
  }
1129
1129
  const cookieName = cookieOpts.name || "__csrf";
1130
1130
  const cookieToken = req.cookies.get(cookieName)?.value;
@@ -1446,20 +1446,3692 @@ function createSecurityHeadersObject(options = {}) {
1446
1446
  });
1447
1447
  return obj;
1448
1448
  }
1449
+ var encoder2 = new TextEncoder();
1450
+ var decoder = new TextDecoder();
1451
+ function base64UrlDecode(str) {
1452
+ const pad = str.length % 4;
1453
+ if (pad) {
1454
+ str += "=".repeat(4 - pad);
1455
+ }
1456
+ const base64 = str.replace(/-/g, "+").replace(/_/g, "/");
1457
+ const binary = atob(base64);
1458
+ const bytes = new Uint8Array(binary.length);
1459
+ for (let i = 0; i < binary.length; i++) {
1460
+ bytes[i] = binary.charCodeAt(i);
1461
+ }
1462
+ return bytes;
1463
+ }
1464
+ function decodeJWT(token) {
1465
+ try {
1466
+ const parts = token.split(".");
1467
+ if (parts.length !== 3) return null;
1468
+ const header = JSON.parse(decoder.decode(base64UrlDecode(parts[0])));
1469
+ const payload = JSON.parse(decoder.decode(base64UrlDecode(parts[1])));
1470
+ const signature = base64UrlDecode(parts[2]);
1471
+ return { header, payload, signature };
1472
+ } catch {
1473
+ return null;
1474
+ }
1475
+ }
1476
+ function getAlgorithmParams(alg) {
1477
+ switch (alg) {
1478
+ case "HS256":
1479
+ return { name: "HMAC", hash: "SHA-256" };
1480
+ case "HS384":
1481
+ return { name: "HMAC", hash: "SHA-384" };
1482
+ case "HS512":
1483
+ return { name: "HMAC", hash: "SHA-512" };
1484
+ case "RS256":
1485
+ return { name: "RSASSA-PKCS1-v1_5", hash: "SHA-256" };
1486
+ case "RS384":
1487
+ return { name: "RSASSA-PKCS1-v1_5", hash: "SHA-384" };
1488
+ case "RS512":
1489
+ return { name: "RSASSA-PKCS1-v1_5", hash: "SHA-512" };
1490
+ case "ES256":
1491
+ return { name: "ECDSA", hash: "SHA-256", namedCurve: "P-256" };
1492
+ case "ES384":
1493
+ return { name: "ECDSA", hash: "SHA-384", namedCurve: "P-384" };
1494
+ case "ES512":
1495
+ return { name: "ECDSA", hash: "SHA-512", namedCurve: "P-521" };
1496
+ default:
1497
+ return null;
1498
+ }
1499
+ }
1500
+ async function verifyHMAC(data, signature, secret, hash2) {
1501
+ const key = await crypto$1.webcrypto.subtle.importKey(
1502
+ "raw",
1503
+ encoder2.encode(secret),
1504
+ { name: "HMAC", hash: hash2 },
1505
+ false,
1506
+ ["verify"]
1507
+ );
1508
+ return crypto$1.webcrypto.subtle.verify("HMAC", key, signature, encoder2.encode(data));
1509
+ }
1510
+ async function importPublicKey(pem, algorithm) {
1511
+ const pemContents = pem.replace(/-----BEGIN.*-----/, "").replace(/-----END.*-----/, "").replace(/\s/g, "");
1512
+ const binaryDer = base64UrlDecode(pemContents.replace(/\+/g, "-").replace(/\//g, "_"));
1513
+ const keyUsages = ["verify"];
1514
+ if (algorithm.name === "RSASSA-PKCS1-v1_5") {
1515
+ return crypto$1.webcrypto.subtle.importKey(
1516
+ "spki",
1517
+ binaryDer,
1518
+ { name: algorithm.name, hash: algorithm.hash },
1519
+ false,
1520
+ keyUsages
1521
+ );
1522
+ }
1523
+ if (algorithm.name === "ECDSA") {
1524
+ return crypto$1.webcrypto.subtle.importKey(
1525
+ "spki",
1526
+ binaryDer,
1527
+ { name: algorithm.name, namedCurve: algorithm.namedCurve },
1528
+ false,
1529
+ keyUsages
1530
+ );
1531
+ }
1532
+ throw new Error(`Unsupported algorithm: ${algorithm.name}`);
1533
+ }
1534
+ async function verifyAsymmetric(data, signature, publicKey, algorithm) {
1535
+ const key = await importPublicKey(publicKey, algorithm);
1536
+ const params = algorithm.name === "ECDSA" ? { name: "ECDSA", hash: algorithm.hash } : algorithm.name;
1537
+ return crypto$1.webcrypto.subtle.verify(params, key, signature, encoder2.encode(data));
1538
+ }
1539
+ function validateClaims(payload, config) {
1540
+ const now = Math.floor(Date.now() / 1e3);
1541
+ const tolerance = config.clockTolerance || 0;
1542
+ if (payload.exp !== void 0 && payload.exp < now - tolerance) {
1543
+ return {
1544
+ code: "expired_token",
1545
+ message: "Token has expired",
1546
+ status: 401
1547
+ };
1548
+ }
1549
+ if (payload.nbf !== void 0 && payload.nbf > now + tolerance) {
1550
+ return {
1551
+ code: "invalid_token",
1552
+ message: "Token not yet valid",
1553
+ status: 401
1554
+ };
1555
+ }
1556
+ if (config.issuer) {
1557
+ const issuers = Array.isArray(config.issuer) ? config.issuer : [config.issuer];
1558
+ if (!payload.iss || !issuers.includes(payload.iss)) {
1559
+ return {
1560
+ code: "invalid_token",
1561
+ message: "Invalid token issuer",
1562
+ status: 401
1563
+ };
1564
+ }
1565
+ }
1566
+ if (config.audience) {
1567
+ const audiences = Array.isArray(config.audience) ? config.audience : [config.audience];
1568
+ const tokenAudiences = Array.isArray(payload.aud) ? payload.aud : payload.aud ? [payload.aud] : [];
1569
+ const hasValidAudience = audiences.some((aud) => tokenAudiences.includes(aud));
1570
+ if (!hasValidAudience) {
1571
+ return {
1572
+ code: "invalid_token",
1573
+ message: "Invalid token audience",
1574
+ status: 401
1575
+ };
1576
+ }
1577
+ }
1578
+ return null;
1579
+ }
1580
+ async function verifyJWT(token, config) {
1581
+ const decoded = decodeJWT(token);
1582
+ if (!decoded) {
1583
+ return {
1584
+ payload: null,
1585
+ error: {
1586
+ code: "invalid_token",
1587
+ message: "Malformed token",
1588
+ status: 401
1589
+ }
1590
+ };
1591
+ }
1592
+ const { header, payload, signature } = decoded;
1593
+ const alg = header.alg;
1594
+ const allowedAlgorithms = config.algorithms || ["HS256"];
1595
+ if (!allowedAlgorithms.includes(alg)) {
1596
+ return {
1597
+ payload: null,
1598
+ error: {
1599
+ code: "invalid_token",
1600
+ message: `Algorithm ${alg} not allowed`,
1601
+ status: 401
1602
+ }
1603
+ };
1604
+ }
1605
+ const algorithmParams = getAlgorithmParams(alg);
1606
+ if (!algorithmParams) {
1607
+ return {
1608
+ payload: null,
1609
+ error: {
1610
+ code: "invalid_token",
1611
+ message: `Unsupported algorithm: ${alg}`,
1612
+ status: 401
1613
+ }
1614
+ };
1615
+ }
1616
+ const parts = token.split(".");
1617
+ const signedData = `${parts[0]}.${parts[1]}`;
1618
+ let isValid = false;
1619
+ try {
1620
+ if (algorithmParams.name === "HMAC") {
1621
+ if (!config.secret) {
1622
+ return {
1623
+ payload: null,
1624
+ error: {
1625
+ code: "invalid_token",
1626
+ message: "Secret required for HMAC algorithms",
1627
+ status: 500
1628
+ }
1629
+ };
1630
+ }
1631
+ isValid = await verifyHMAC(signedData, signature, config.secret, algorithmParams.hash);
1632
+ } else {
1633
+ if (!config.publicKey) {
1634
+ return {
1635
+ payload: null,
1636
+ error: {
1637
+ code: "invalid_token",
1638
+ message: "Public key required for asymmetric algorithms",
1639
+ status: 500
1640
+ }
1641
+ };
1642
+ }
1643
+ isValid = await verifyAsymmetric(signedData, signature, config.publicKey, algorithmParams);
1644
+ }
1645
+ } catch {
1646
+ isValid = false;
1647
+ }
1648
+ if (!isValid) {
1649
+ return {
1650
+ payload: null,
1651
+ error: {
1652
+ code: "invalid_signature",
1653
+ message: "Invalid token signature",
1654
+ status: 401
1655
+ }
1656
+ };
1657
+ }
1658
+ const claimsError = validateClaims(payload, config);
1659
+ if (claimsError) {
1660
+ return { payload: null, error: claimsError };
1661
+ }
1662
+ return { payload, error: null };
1663
+ }
1664
+ function extractBearerToken(authHeader) {
1665
+ if (!authHeader) return null;
1666
+ if (!authHeader.startsWith("Bearer ")) return null;
1667
+ return authHeader.slice(7);
1668
+ }
1669
+
1670
+ // src/middleware/auth/middleware.ts
1671
+ function defaultErrorResponse2(_req, error) {
1672
+ return new Response(
1673
+ JSON.stringify({
1674
+ error: error.code,
1675
+ message: error.message
1676
+ }),
1677
+ {
1678
+ status: error.status,
1679
+ headers: { "Content-Type": "application/json" }
1680
+ }
1681
+ );
1682
+ }
1683
+ async function getTokenFromRequest(req, config) {
1684
+ if (config?.getToken) {
1685
+ return config.getToken(req);
1686
+ }
1687
+ return extractBearerToken(req.headers.get("authorization"));
1688
+ }
1689
+ function withJWT(handler, config) {
1690
+ const secret = config.secret || process.env.JWT_SECRET;
1691
+ const effectiveConfig = { ...config, secret };
1692
+ return async (req) => {
1693
+ const token = await getTokenFromRequest(req, effectiveConfig);
1694
+ if (!token) {
1695
+ return defaultErrorResponse2(req, {
1696
+ code: "missing_token",
1697
+ message: "Authentication required",
1698
+ status: 401
1699
+ });
1700
+ }
1701
+ const { payload, error } = await verifyJWT(token, effectiveConfig);
1702
+ if (error) {
1703
+ return defaultErrorResponse2(req, error);
1704
+ }
1705
+ const user = effectiveConfig.mapUser ? await effectiveConfig.mapUser(payload) : {
1706
+ id: payload.sub || "",
1707
+ email: payload.email,
1708
+ name: payload.name,
1709
+ roles: payload.roles,
1710
+ permissions: payload.permissions
1711
+ };
1712
+ return handler(req, { user, token });
1713
+ };
1714
+ }
1715
+ function withAPIKey(handler, config) {
1716
+ const headerName = config.headerName || "x-api-key";
1717
+ const queryParam = config.queryParam || "api_key";
1718
+ return async (req) => {
1719
+ let apiKey = req.headers.get(headerName);
1720
+ if (!apiKey) {
1721
+ const url = new URL(req.url);
1722
+ apiKey = url.searchParams.get(queryParam);
1723
+ }
1724
+ if (!apiKey) {
1725
+ return defaultErrorResponse2(req, {
1726
+ code: "missing_api_key",
1727
+ message: "API key required",
1728
+ status: 401
1729
+ });
1730
+ }
1731
+ const user = await config.validate(apiKey, req);
1732
+ if (!user) {
1733
+ return defaultErrorResponse2(req, {
1734
+ code: "invalid_api_key",
1735
+ message: "Invalid API key",
1736
+ status: 401
1737
+ });
1738
+ }
1739
+ return handler(req, { user });
1740
+ };
1741
+ }
1742
+ function withSession(handler, config) {
1743
+ const cookieName = config.cookieName || "session";
1744
+ return async (req) => {
1745
+ const sessionId = req.cookies.get(cookieName)?.value;
1746
+ if (!sessionId) {
1747
+ return defaultErrorResponse2(req, {
1748
+ code: "missing_session",
1749
+ message: "Session required",
1750
+ status: 401
1751
+ });
1752
+ }
1753
+ const user = await config.validate(sessionId, req);
1754
+ if (!user) {
1755
+ return defaultErrorResponse2(req, {
1756
+ code: "invalid_session",
1757
+ message: "Invalid or expired session",
1758
+ status: 401
1759
+ });
1760
+ }
1761
+ return handler(req, { user });
1762
+ };
1763
+ }
1764
+ function withRoles(handler, config) {
1765
+ return async (req, ctx) => {
1766
+ const { user } = ctx;
1767
+ const userRoles = config.getUserRoles ? config.getUserRoles(user) : user.roles || [];
1768
+ if (config.roles && config.roles.length > 0) {
1769
+ const hasRole = config.roles.some((role) => userRoles.includes(role));
1770
+ if (!hasRole) {
1771
+ return defaultErrorResponse2(req, {
1772
+ code: "insufficient_roles",
1773
+ message: "Insufficient permissions",
1774
+ status: 403
1775
+ });
1776
+ }
1777
+ }
1778
+ const userPermissions = config.getUserPermissions ? config.getUserPermissions(user) : user.permissions || [];
1779
+ if (config.permissions && config.permissions.length > 0) {
1780
+ const hasAllPermissions = config.permissions.every(
1781
+ (perm) => userPermissions.includes(perm)
1782
+ );
1783
+ if (!hasAllPermissions) {
1784
+ return defaultErrorResponse2(req, {
1785
+ code: "insufficient_permissions",
1786
+ message: "Insufficient permissions",
1787
+ status: 403
1788
+ });
1789
+ }
1790
+ }
1791
+ if (config.authorize) {
1792
+ const authorized = await config.authorize(user, req);
1793
+ if (!authorized) {
1794
+ return defaultErrorResponse2(req, {
1795
+ code: "unauthorized",
1796
+ message: "Unauthorized",
1797
+ status: 403
1798
+ });
1799
+ }
1800
+ }
1801
+ return handler(req, ctx);
1802
+ };
1803
+ }
1804
+ function withAuth(handler, config) {
1805
+ const onError = config.onError || defaultErrorResponse2;
1806
+ return async (req) => {
1807
+ let user = null;
1808
+ let token;
1809
+ if (config.jwt) {
1810
+ const secret = config.jwt.secret || process.env.JWT_SECRET;
1811
+ const jwtConfig = { ...config.jwt, secret };
1812
+ const jwtToken = await getTokenFromRequest(req, jwtConfig);
1813
+ if (jwtToken) {
1814
+ const { payload, error } = await verifyJWT(jwtToken, jwtConfig);
1815
+ if (!error && payload) {
1816
+ user = jwtConfig.mapUser ? await jwtConfig.mapUser(payload) : {
1817
+ id: payload.sub || "",
1818
+ email: payload.email,
1819
+ name: payload.name,
1820
+ roles: payload.roles
1821
+ };
1822
+ token = jwtToken;
1823
+ }
1824
+ }
1825
+ }
1826
+ if (!user && config.apiKey) {
1827
+ const headerName = config.apiKey.headerName || "x-api-key";
1828
+ const queryParam = config.apiKey.queryParam || "api_key";
1829
+ let apiKey = req.headers.get(headerName);
1830
+ if (!apiKey) {
1831
+ const url = new URL(req.url);
1832
+ apiKey = url.searchParams.get(queryParam);
1833
+ }
1834
+ if (apiKey) {
1835
+ const apiUser = await config.apiKey.validate(apiKey, req);
1836
+ if (apiUser) {
1837
+ user = apiUser;
1838
+ }
1839
+ }
1840
+ }
1841
+ if (!user && config.session) {
1842
+ const cookieName = config.session.cookieName || "session";
1843
+ const sessionId = req.cookies.get(cookieName)?.value;
1844
+ if (sessionId) {
1845
+ const sessionUser = await config.session.validate(sessionId, req);
1846
+ if (sessionUser) {
1847
+ user = sessionUser;
1848
+ }
1849
+ }
1850
+ }
1851
+ if (!user) {
1852
+ return onError(req, {
1853
+ code: "unauthorized",
1854
+ message: "Authentication required",
1855
+ status: 401
1856
+ });
1857
+ }
1858
+ if (config.rbac) {
1859
+ const userRoles = config.rbac.getUserRoles ? config.rbac.getUserRoles(user) : user.roles || [];
1860
+ if (config.rbac.roles && config.rbac.roles.length > 0) {
1861
+ const hasRole = config.rbac.roles.some((role) => userRoles.includes(role));
1862
+ if (!hasRole) {
1863
+ return onError(req, {
1864
+ code: "insufficient_roles",
1865
+ message: "Insufficient permissions",
1866
+ status: 403
1867
+ });
1868
+ }
1869
+ }
1870
+ const userPermissions = config.rbac.getUserPermissions ? config.rbac.getUserPermissions(user) : user.permissions || [];
1871
+ if (config.rbac.permissions && config.rbac.permissions.length > 0) {
1872
+ const hasAllPermissions = config.rbac.permissions.every(
1873
+ (perm) => userPermissions.includes(perm)
1874
+ );
1875
+ if (!hasAllPermissions) {
1876
+ return onError(req, {
1877
+ code: "insufficient_permissions",
1878
+ message: "Insufficient permissions",
1879
+ status: 403
1880
+ });
1881
+ }
1882
+ }
1883
+ if (config.rbac.authorize) {
1884
+ const authorized = await config.rbac.authorize(user, req);
1885
+ if (!authorized) {
1886
+ return onError(req, {
1887
+ code: "unauthorized",
1888
+ message: "Unauthorized",
1889
+ status: 403
1890
+ });
1891
+ }
1892
+ }
1893
+ }
1894
+ if (config.onSuccess) {
1895
+ await config.onSuccess(req, user);
1896
+ }
1897
+ return handler(req, { user, token });
1898
+ };
1899
+ }
1900
+ function withOptionalAuth(handler, config) {
1901
+ return async (req) => {
1902
+ let user = null;
1903
+ let token;
1904
+ if (config.jwt) {
1905
+ const secret = config.jwt.secret || process.env.JWT_SECRET;
1906
+ const jwtConfig = { ...config.jwt, secret };
1907
+ const jwtToken = await getTokenFromRequest(req, jwtConfig);
1908
+ if (jwtToken) {
1909
+ const { payload, error } = await verifyJWT(jwtToken, jwtConfig);
1910
+ if (!error && payload) {
1911
+ user = jwtConfig.mapUser ? await jwtConfig.mapUser(payload) : {
1912
+ id: payload.sub || "",
1913
+ email: payload.email,
1914
+ name: payload.name,
1915
+ roles: payload.roles
1916
+ };
1917
+ token = jwtToken;
1918
+ }
1919
+ }
1920
+ }
1921
+ if (!user && config.apiKey) {
1922
+ const headerName = config.apiKey.headerName || "x-api-key";
1923
+ let apiKey = req.headers.get(headerName);
1924
+ if (apiKey) {
1925
+ const apiUser = await config.apiKey.validate(apiKey, req);
1926
+ if (apiUser) user = apiUser;
1927
+ }
1928
+ }
1929
+ if (!user && config.session) {
1930
+ const cookieName = config.session.cookieName || "session";
1931
+ const sessionId = req.cookies.get(cookieName)?.value;
1932
+ if (sessionId) {
1933
+ const sessionUser = await config.session.validate(sessionId, req);
1934
+ if (sessionUser) user = sessionUser;
1935
+ }
1936
+ }
1937
+ return handler(req, { user, token });
1938
+ };
1939
+ }
1940
+
1941
+ // src/middleware/validation/utils.ts
1942
+ function isZodSchema(schema) {
1943
+ return typeof schema === "object" && schema !== null && "safeParse" in schema && typeof schema.safeParse === "function";
1944
+ }
1945
+ function isCustomSchema(schema) {
1946
+ if (typeof schema !== "object" || schema === null) return false;
1947
+ if ("safeParse" in schema) return false;
1948
+ const entries = Object.entries(schema);
1949
+ if (entries.length === 0) return false;
1950
+ return entries.every(([_, rule]) => {
1951
+ return typeof rule === "object" && rule !== null && "type" in rule;
1952
+ });
1953
+ }
1954
+ var EMAIL_PATTERN = /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/;
1955
+ var URL_PATTERN = /^https?:\/\/(?:(?:www\.)?[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}|localhost|(?:\d{1,3}\.){3}\d{1,3})(?::\d{1,5})?(?:[-a-zA-Z0-9()@:%_+.~#?&/=]*)$/;
1956
+ var UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
1957
+ var DATE_PATTERN = /^\d{4}-\d{2}-\d{2}(?:T\d{2}:\d{2}:\d{2}(?:\.\d{3})?(?:Z|[+-]\d{2}:?\d{2})?)?$/;
1958
+ function validateField(value, rule, fieldName) {
1959
+ if (value === void 0 || value === null || value === "") {
1960
+ if (rule.required) {
1961
+ return {
1962
+ field: fieldName,
1963
+ code: "required",
1964
+ message: rule.message || `${fieldName} is required`,
1965
+ received: value
1966
+ };
1967
+ }
1968
+ return null;
1969
+ }
1970
+ switch (rule.type) {
1971
+ case "string":
1972
+ if (typeof value !== "string") {
1973
+ return {
1974
+ field: fieldName,
1975
+ code: "invalid_type",
1976
+ message: rule.message || `${fieldName} must be a string`,
1977
+ expected: "string",
1978
+ received: typeof value
1979
+ };
1980
+ }
1981
+ if (rule.minLength !== void 0 && value.length < rule.minLength) {
1982
+ return {
1983
+ field: fieldName,
1984
+ code: "too_short",
1985
+ message: rule.message || `${fieldName} must be at least ${rule.minLength} characters`,
1986
+ received: value.length
1987
+ };
1988
+ }
1989
+ if (rule.maxLength !== void 0 && value.length > rule.maxLength) {
1990
+ return {
1991
+ field: fieldName,
1992
+ code: "too_long",
1993
+ message: rule.message || `${fieldName} must be at most ${rule.maxLength} characters`,
1994
+ received: value.length
1995
+ };
1996
+ }
1997
+ if (rule.pattern && !rule.pattern.test(value)) {
1998
+ return {
1999
+ field: fieldName,
2000
+ code: "invalid_pattern",
2001
+ message: rule.message || `${fieldName} has invalid format`,
2002
+ received: value
2003
+ };
2004
+ }
2005
+ break;
2006
+ case "number":
2007
+ const num = typeof value === "number" ? value : Number(value);
2008
+ if (isNaN(num)) {
2009
+ return {
2010
+ field: fieldName,
2011
+ code: "invalid_type",
2012
+ message: rule.message || `${fieldName} must be a number`,
2013
+ expected: "number",
2014
+ received: typeof value
2015
+ };
2016
+ }
2017
+ if (rule.integer && !Number.isInteger(num)) {
2018
+ return {
2019
+ field: fieldName,
2020
+ code: "invalid_integer",
2021
+ message: rule.message || `${fieldName} must be an integer`,
2022
+ received: num
2023
+ };
2024
+ }
2025
+ if (rule.min !== void 0 && num < rule.min) {
2026
+ return {
2027
+ field: fieldName,
2028
+ code: "too_small",
2029
+ message: rule.message || `${fieldName} must be at least ${rule.min}`,
2030
+ received: num
2031
+ };
2032
+ }
2033
+ if (rule.max !== void 0 && num > rule.max) {
2034
+ return {
2035
+ field: fieldName,
2036
+ code: "too_large",
2037
+ message: rule.message || `${fieldName} must be at most ${rule.max}`,
2038
+ received: num
2039
+ };
2040
+ }
2041
+ break;
2042
+ case "boolean":
2043
+ if (typeof value !== "boolean" && value !== "true" && value !== "false") {
2044
+ return {
2045
+ field: fieldName,
2046
+ code: "invalid_type",
2047
+ message: rule.message || `${fieldName} must be a boolean`,
2048
+ expected: "boolean",
2049
+ received: typeof value
2050
+ };
2051
+ }
2052
+ break;
2053
+ case "email":
2054
+ if (typeof value !== "string" || !EMAIL_PATTERN.test(value)) {
2055
+ return {
2056
+ field: fieldName,
2057
+ code: "invalid_email",
2058
+ message: rule.message || `${fieldName} must be a valid email address`,
2059
+ received: value
2060
+ };
2061
+ }
2062
+ break;
2063
+ case "url":
2064
+ if (typeof value !== "string" || !URL_PATTERN.test(value)) {
2065
+ return {
2066
+ field: fieldName,
2067
+ code: "invalid_url",
2068
+ message: rule.message || `${fieldName} must be a valid URL`,
2069
+ received: value
2070
+ };
2071
+ }
2072
+ break;
2073
+ case "uuid":
2074
+ if (typeof value !== "string" || !UUID_PATTERN.test(value)) {
2075
+ return {
2076
+ field: fieldName,
2077
+ code: "invalid_uuid",
2078
+ message: rule.message || `${fieldName} must be a valid UUID`,
2079
+ received: value
2080
+ };
2081
+ }
2082
+ break;
2083
+ case "date":
2084
+ if (typeof value !== "string" || !DATE_PATTERN.test(value)) {
2085
+ const parsed = new Date(value);
2086
+ if (isNaN(parsed.getTime())) {
2087
+ return {
2088
+ field: fieldName,
2089
+ code: "invalid_date",
2090
+ message: rule.message || `${fieldName} must be a valid date`,
2091
+ received: value
2092
+ };
2093
+ }
2094
+ }
2095
+ break;
2096
+ case "array":
2097
+ if (!Array.isArray(value)) {
2098
+ return {
2099
+ field: fieldName,
2100
+ code: "invalid_type",
2101
+ message: rule.message || `${fieldName} must be an array`,
2102
+ expected: "array",
2103
+ received: typeof value
2104
+ };
2105
+ }
2106
+ if (rule.minItems !== void 0 && value.length < rule.minItems) {
2107
+ return {
2108
+ field: fieldName,
2109
+ code: "too_few_items",
2110
+ message: rule.message || `${fieldName} must have at least ${rule.minItems} items`,
2111
+ received: value.length
2112
+ };
2113
+ }
2114
+ if (rule.maxItems !== void 0 && value.length > rule.maxItems) {
2115
+ return {
2116
+ field: fieldName,
2117
+ code: "too_many_items",
2118
+ message: rule.message || `${fieldName} must have at most ${rule.maxItems} items`,
2119
+ received: value.length
2120
+ };
2121
+ }
2122
+ if (rule.items) {
2123
+ for (let i = 0; i < value.length; i++) {
2124
+ const itemError = validateField(value[i], rule.items, `${fieldName}[${i}]`);
2125
+ if (itemError) return itemError;
2126
+ }
2127
+ }
2128
+ break;
2129
+ case "object":
2130
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
2131
+ return {
2132
+ field: fieldName,
2133
+ code: "invalid_type",
2134
+ message: rule.message || `${fieldName} must be an object`,
2135
+ expected: "object",
2136
+ received: Array.isArray(value) ? "array" : typeof value
2137
+ };
2138
+ }
2139
+ break;
2140
+ }
2141
+ if (rule.custom) {
2142
+ const result = rule.custom(value);
2143
+ if (result !== true) {
2144
+ return {
2145
+ field: fieldName,
2146
+ code: "custom_validation",
2147
+ message: typeof result === "string" ? result : rule.message || `${fieldName} failed validation`,
2148
+ received: value
2149
+ };
2150
+ }
2151
+ }
2152
+ return null;
2153
+ }
2154
+ function validateCustomSchema(data, schema) {
2155
+ if (typeof data !== "object" || data === null) {
2156
+ return {
2157
+ success: false,
2158
+ errors: [{
2159
+ field: "_root",
2160
+ code: "invalid_type",
2161
+ message: "Expected an object",
2162
+ received: data
2163
+ }]
2164
+ };
2165
+ }
2166
+ const errors = [];
2167
+ const record = data;
2168
+ for (const [fieldName, rule] of Object.entries(schema)) {
2169
+ const error = validateField(record[fieldName], rule, fieldName);
2170
+ if (error) {
2171
+ errors.push(error);
2172
+ }
2173
+ }
2174
+ if (errors.length > 0) {
2175
+ return { success: false, errors };
2176
+ }
2177
+ return { success: true, data };
2178
+ }
2179
+ function validateZodSchema(data, schema) {
2180
+ const result = schema.safeParse(data);
2181
+ if (result.success) {
2182
+ return { success: true, data: result.data };
2183
+ }
2184
+ const errors = result.error.issues.map((issue) => ({
2185
+ field: issue.path.join(".") || "_root",
2186
+ code: issue.code,
2187
+ message: issue.message,
2188
+ path: issue.path.map(String)
2189
+ }));
2190
+ return { success: false, errors };
2191
+ }
2192
+ function walkObject(obj, fn, path = "") {
2193
+ if (typeof obj === "string") {
2194
+ return fn(obj, path);
2195
+ }
2196
+ if (Array.isArray(obj)) {
2197
+ return obj.map((item, i) => walkObject(item, fn, `${path}[${i}]`));
2198
+ }
2199
+ if (typeof obj === "object" && obj !== null) {
2200
+ const result = {};
2201
+ for (const [key, value] of Object.entries(obj)) {
2202
+ const newPath = path ? `${path}.${key}` : key;
2203
+ result[key] = walkObject(value, fn, newPath);
2204
+ }
2205
+ return result;
2206
+ }
2207
+ return obj;
2208
+ }
2209
+ function parseQueryString(url) {
2210
+ const result = {};
2211
+ try {
2212
+ const urlObj = new URL(url);
2213
+ for (const [key, value] of urlObj.searchParams.entries()) {
2214
+ if (key in result) {
2215
+ const existing = result[key];
2216
+ if (Array.isArray(existing)) {
2217
+ existing.push(value);
2218
+ } else {
2219
+ result[key] = [existing, value];
2220
+ }
2221
+ } else {
2222
+ result[key] = value;
2223
+ }
2224
+ }
2225
+ } catch {
2226
+ }
2227
+ return result;
2228
+ }
2229
+
2230
+ // src/middleware/validation/validators/schema.ts
2231
+ function validate(data, schema) {
2232
+ if (isZodSchema(schema)) {
2233
+ return validateZodSchema(data, schema);
2234
+ }
2235
+ if (isCustomSchema(schema)) {
2236
+ return validateCustomSchema(data, schema);
2237
+ }
2238
+ return {
2239
+ success: false,
2240
+ errors: [{
2241
+ field: "_schema",
2242
+ code: "invalid_schema",
2243
+ message: "Invalid schema provided"
2244
+ }]
2245
+ };
2246
+ }
2247
+ async function validateBody(request, schema) {
2248
+ let body;
2249
+ try {
2250
+ const contentType = request.headers.get("content-type") || "";
2251
+ if (contentType.includes("application/json")) {
2252
+ body = await request.json();
2253
+ } else if (contentType.includes("application/x-www-form-urlencoded")) {
2254
+ const text = await request.text();
2255
+ body = Object.fromEntries(new URLSearchParams(text));
2256
+ } else if (contentType.includes("multipart/form-data")) {
2257
+ const formData = await request.formData();
2258
+ const obj = {};
2259
+ formData.forEach((value, key) => {
2260
+ if (typeof value === "string") {
2261
+ obj[key] = value;
2262
+ }
2263
+ });
2264
+ body = obj;
2265
+ } else {
2266
+ try {
2267
+ body = await request.json();
2268
+ } catch {
2269
+ body = {};
2270
+ }
2271
+ }
2272
+ } catch (error) {
2273
+ return {
2274
+ success: false,
2275
+ errors: [{
2276
+ field: "_body",
2277
+ code: "parse_error",
2278
+ message: "Failed to parse request body"
2279
+ }]
2280
+ };
2281
+ }
2282
+ return validate(body, schema);
2283
+ }
2284
+ function validateQuery(request, schema) {
2285
+ const query = parseQueryString(request.url);
2286
+ return validate(query, schema);
2287
+ }
2288
+ function validateParams(params, schema) {
2289
+ return validate(params, schema);
2290
+ }
2291
+ async function validateRequest(request, config) {
2292
+ const allErrors = [];
2293
+ const data = {};
2294
+ if (config.body) {
2295
+ const bodyResult = await validateBody(request, config.body);
2296
+ if (!bodyResult.success) {
2297
+ allErrors.push(...(bodyResult.errors || []).map((e) => ({
2298
+ ...e,
2299
+ field: `body.${e.field}`.replace("body._root", "body")
2300
+ })));
2301
+ } else {
2302
+ data.body = bodyResult.data;
2303
+ }
2304
+ } else {
2305
+ data.body = {};
2306
+ }
2307
+ if (config.query) {
2308
+ const queryResult = validateQuery(request, config.query);
2309
+ if (!queryResult.success) {
2310
+ allErrors.push(...(queryResult.errors || []).map((e) => ({
2311
+ ...e,
2312
+ field: `query.${e.field}`.replace("query._root", "query")
2313
+ })));
2314
+ } else {
2315
+ data.query = queryResult.data;
2316
+ }
2317
+ } else {
2318
+ data.query = {};
2319
+ }
2320
+ if (config.params && config.routeParams) {
2321
+ const paramsResult = validateParams(config.routeParams, config.params);
2322
+ if (!paramsResult.success) {
2323
+ allErrors.push(...(paramsResult.errors || []).map((e) => ({
2324
+ ...e,
2325
+ field: `params.${e.field}`.replace("params._root", "params")
2326
+ })));
2327
+ } else {
2328
+ data.params = paramsResult.data;
2329
+ }
2330
+ } else {
2331
+ data.params = {};
2332
+ }
2333
+ if (allErrors.length > 0) {
2334
+ return { success: false, errors: allErrors };
2335
+ }
2336
+ return {
2337
+ success: true,
2338
+ data
2339
+ };
2340
+ }
2341
+ function defaultValidationErrorResponse(errors) {
2342
+ return new Response(
2343
+ JSON.stringify({
2344
+ error: "validation_error",
2345
+ message: "Request validation failed",
2346
+ details: errors.map((e) => ({
2347
+ field: e.field,
2348
+ code: e.code,
2349
+ message: e.message
2350
+ }))
2351
+ }),
2352
+ {
2353
+ status: 400,
2354
+ headers: { "Content-Type": "application/json" }
2355
+ }
2356
+ );
2357
+ }
2358
+ function createValidator(schema) {
2359
+ return (data) => validate(data, schema);
2360
+ }
2361
+
2362
+ // src/middleware/validation/validators/content-type.ts
2363
+ var MIME_TYPES = {
2364
+ // Text
2365
+ TEXT_PLAIN: "text/plain",
2366
+ TEXT_HTML: "text/html",
2367
+ TEXT_CSS: "text/css",
2368
+ TEXT_JAVASCRIPT: "text/javascript",
2369
+ // Application
2370
+ JSON: "application/json",
2371
+ FORM_URLENCODED: "application/x-www-form-urlencoded",
2372
+ MULTIPART_FORM: "multipart/form-data",
2373
+ XML: "application/xml",
2374
+ PDF: "application/pdf",
2375
+ ZIP: "application/zip",
2376
+ GZIP: "application/gzip",
2377
+ OCTET_STREAM: "application/octet-stream",
2378
+ // Image
2379
+ IMAGE_PNG: "image/png",
2380
+ IMAGE_JPEG: "image/jpeg",
2381
+ IMAGE_GIF: "image/gif",
2382
+ IMAGE_WEBP: "image/webp",
2383
+ IMAGE_SVG: "image/svg+xml",
2384
+ // Audio
2385
+ AUDIO_MP3: "audio/mpeg",
2386
+ AUDIO_WAV: "audio/wav",
2387
+ AUDIO_OGG: "audio/ogg",
2388
+ // Video
2389
+ VIDEO_MP4: "video/mp4",
2390
+ VIDEO_WEBM: "video/webm"
2391
+ };
2392
+ function parseContentType(header) {
2393
+ if (!header) {
2394
+ return {
2395
+ type: "",
2396
+ subtype: "",
2397
+ mediaType: "",
2398
+ parameters: {}
2399
+ };
2400
+ }
2401
+ const parts = header.split(";").map((p) => p.trim());
2402
+ const mediaType = parts[0].toLowerCase();
2403
+ const [type = "", subtype = ""] = mediaType.split("/");
2404
+ const parameters = {};
2405
+ for (let i = 1; i < parts.length; i++) {
2406
+ const [key, value] = parts[i].split("=").map((p) => p.trim());
2407
+ if (key && value) {
2408
+ parameters[key.toLowerCase()] = value.replace(/^["']|["']$/g, "");
2409
+ }
2410
+ }
2411
+ return {
2412
+ type,
2413
+ subtype,
2414
+ mediaType,
2415
+ charset: parameters["charset"],
2416
+ boundary: parameters["boundary"],
2417
+ parameters
2418
+ };
2419
+ }
2420
+ function isAllowedContentType(contentType, allowedTypes, strict = false) {
2421
+ if (!contentType) {
2422
+ return !strict;
2423
+ }
2424
+ const { mediaType } = parseContentType(contentType);
2425
+ return allowedTypes.some((allowed) => {
2426
+ const normalizedAllowed = allowed.toLowerCase().trim();
2427
+ if (mediaType === normalizedAllowed) {
2428
+ return true;
2429
+ }
2430
+ if (normalizedAllowed.endsWith("/*")) {
2431
+ const prefix = normalizedAllowed.slice(0, -2);
2432
+ return mediaType.startsWith(prefix + "/");
2433
+ }
2434
+ if (!normalizedAllowed.includes("/")) {
2435
+ const { type } = parseContentType(contentType);
2436
+ return type === normalizedAllowed;
2437
+ }
2438
+ return false;
2439
+ });
2440
+ }
2441
+ function validateContentType(request, config) {
2442
+ const contentType = request.headers.get("content-type");
2443
+ const { allowed, strict = false, charset } = config;
2444
+ if (strict && !contentType) {
2445
+ return {
2446
+ valid: false,
2447
+ contentType: null,
2448
+ reason: "Content-Type header is required"
2449
+ };
2450
+ }
2451
+ if (contentType && !isAllowedContentType(contentType, allowed, strict)) {
2452
+ return {
2453
+ valid: false,
2454
+ contentType,
2455
+ reason: `Content-Type '${contentType}' is not allowed`
2456
+ };
2457
+ }
2458
+ if (charset && contentType) {
2459
+ const parsed = parseContentType(contentType);
2460
+ if (parsed.charset && parsed.charset.toLowerCase() !== charset.toLowerCase()) {
2461
+ return {
2462
+ valid: false,
2463
+ contentType,
2464
+ reason: `Charset '${parsed.charset}' is not allowed, expected '${charset}'`
2465
+ };
2466
+ }
2467
+ }
2468
+ return { valid: true, contentType };
2469
+ }
2470
+ function defaultContentTypeErrorResponse(contentType, reason) {
2471
+ return new Response(
2472
+ JSON.stringify({
2473
+ error: "invalid_content_type",
2474
+ message: reason,
2475
+ received: contentType
2476
+ }),
2477
+ {
2478
+ status: 415,
2479
+ // Unsupported Media Type
2480
+ headers: { "Content-Type": "application/json" }
2481
+ }
2482
+ );
2483
+ }
2484
+ function isJsonRequest(request) {
2485
+ return isAllowedContentType(
2486
+ request.headers.get("content-type"),
2487
+ [MIME_TYPES.JSON]
2488
+ );
2489
+ }
2490
+ function isFormRequest(request) {
2491
+ return isAllowedContentType(
2492
+ request.headers.get("content-type"),
2493
+ [MIME_TYPES.FORM_URLENCODED, MIME_TYPES.MULTIPART_FORM]
2494
+ );
2495
+ }
2496
+
2497
+ // src/middleware/validation/sanitizers/path.ts
2498
+ var DANGEROUS_PATTERNS = [
2499
+ // Unix path traversal
2500
+ /\.\.\//g,
2501
+ /\.\./g,
2502
+ // Windows path traversal
2503
+ /\.\.\\/g,
2504
+ // Null byte (can truncate paths in some systems)
2505
+ /%00/g,
2506
+ /\0/g,
2507
+ // URL encoded traversal
2508
+ /%2e%2e%2f/gi,
2509
+ // ../
2510
+ /%2e%2e\//gi,
2511
+ // ../
2512
+ /%2e%2e%5c/gi,
2513
+ // ..\
2514
+ /%2e%2e\\/gi,
2515
+ // ..\
2516
+ // Double URL encoding
2517
+ /%252e%252e%252f/gi,
2518
+ /%252e%252e%255c/gi,
2519
+ // Unicode encoding
2520
+ /\.%u002e\//gi,
2521
+ /%u002e%u002e%u002f/gi,
2522
+ // Overlong UTF-8 encoding
2523
+ /%c0%ae%c0%ae%c0%af/gi,
2524
+ /%c1%9c/gi
2525
+ // Backslash variant
2526
+ ];
2527
+ var DEFAULT_BLOCKED_EXTENSIONS = [
2528
+ ".exe",
2529
+ ".dll",
2530
+ ".so",
2531
+ ".dylib",
2532
+ // Executables
2533
+ ".sh",
2534
+ ".bash",
2535
+ ".bat",
2536
+ ".cmd",
2537
+ ".ps1",
2538
+ // Scripts
2539
+ ".php",
2540
+ ".asp",
2541
+ ".aspx",
2542
+ ".jsp",
2543
+ ".cgi",
2544
+ // Server scripts
2545
+ ".htaccess",
2546
+ ".htpasswd",
2547
+ // Apache config
2548
+ ".env",
2549
+ ".git",
2550
+ ".svn"
2551
+ // Config/VCS
2552
+ ];
2553
+ function normalizePathSeparators(path) {
2554
+ return path.replace(/\\/g, "/");
2555
+ }
2556
+ function decodePathComponent(path) {
2557
+ let result = path;
2558
+ let previous = "";
2559
+ while (result !== previous) {
2560
+ previous = result;
2561
+ try {
2562
+ result = decodeURIComponent(result);
2563
+ } catch {
2564
+ break;
2565
+ }
2566
+ }
2567
+ return result;
2568
+ }
2569
+ function hasPathTraversal(path) {
2570
+ if (!path || typeof path !== "string") return false;
2571
+ const normalized = normalizePathSeparators(decodePathComponent(path));
2572
+ for (const pattern of DANGEROUS_PATTERNS) {
2573
+ pattern.lastIndex = 0;
2574
+ if (pattern.test(normalized)) {
2575
+ return true;
2576
+ }
2577
+ }
2578
+ if (normalized.includes("..")) {
2579
+ return true;
2580
+ }
2581
+ return false;
2582
+ }
2583
+ function validatePath(path, config = {}) {
2584
+ if (!path || typeof path !== "string") {
2585
+ return { valid: false, reason: "Path is empty or not a string" };
2586
+ }
2587
+ const {
2588
+ allowAbsolute = false,
2589
+ allowedPrefixes = [],
2590
+ allowedExtensions,
2591
+ blockedExtensions = DEFAULT_BLOCKED_EXTENSIONS,
2592
+ maxDepth = 10,
2593
+ maxLength = 255,
2594
+ normalize = true
2595
+ } = config;
2596
+ if (path.length > maxLength) {
2597
+ return { valid: false, reason: `Path exceeds maximum length of ${maxLength}` };
2598
+ }
2599
+ let normalized = decodePathComponent(path);
2600
+ if (normalize) {
2601
+ normalized = normalizePathSeparators(normalized);
2602
+ }
2603
+ if (normalized.includes("\0") || path.includes("%00")) {
2604
+ return { valid: false, reason: "Path contains null bytes" };
2605
+ }
2606
+ if (hasPathTraversal(path)) {
2607
+ return { valid: false, reason: "Path contains traversal sequences" };
2608
+ }
2609
+ const isAbsolute = normalized.startsWith("/") || /^[a-zA-Z]:/.test(normalized) || // Windows drive letter
2610
+ normalized.startsWith("\\\\");
2611
+ if (isAbsolute && !allowAbsolute) {
2612
+ return { valid: false, reason: "Absolute paths are not allowed" };
2613
+ }
2614
+ if (allowedPrefixes.length > 0) {
2615
+ const hasValidPrefix = allowedPrefixes.some((prefix) => {
2616
+ const normalizedPrefix = normalizePathSeparators(prefix);
2617
+ return normalized.startsWith(normalizedPrefix);
2618
+ });
2619
+ if (!hasValidPrefix) {
2620
+ return { valid: false, reason: "Path does not start with an allowed prefix" };
2621
+ }
2622
+ }
2623
+ const segments = normalized.split("/").filter((s) => s && s !== ".");
2624
+ if (segments.length > maxDepth) {
2625
+ return { valid: false, reason: `Path depth exceeds maximum of ${maxDepth}` };
2626
+ }
2627
+ const lastSegment = segments[segments.length - 1] || "";
2628
+ const dotIndex = lastSegment.lastIndexOf(".");
2629
+ const extension = dotIndex > 0 ? lastSegment.slice(dotIndex).toLowerCase() : "";
2630
+ if (extension && blockedExtensions.length > 0) {
2631
+ if (blockedExtensions.map((e) => e.toLowerCase()).includes(extension)) {
2632
+ return { valid: false, reason: `Extension ${extension} is not allowed` };
2633
+ }
2634
+ }
2635
+ if (extension && allowedExtensions && allowedExtensions.length > 0) {
2636
+ if (!allowedExtensions.map((e) => e.toLowerCase()).includes(extension)) {
2637
+ return { valid: false, reason: `Extension ${extension} is not in allowed list` };
2638
+ }
2639
+ }
2640
+ const sanitized = normalized.replace(/\/+/g, "/");
2641
+ return { valid: true, sanitized };
2642
+ }
2643
+ function sanitizePath(path, config = {}) {
2644
+ if (!path || typeof path !== "string") return "";
2645
+ const { normalize = true, maxLength = 255 } = config;
2646
+ let result = decodePathComponent(path);
2647
+ if (normalize) {
2648
+ result = normalizePathSeparators(result);
2649
+ }
2650
+ result = result.replace(/\0/g, "").replace(/%00/g, "");
2651
+ result = result.replace(/\.\.\//g, "").replace(/\.\.\\/g, "");
2652
+ if (!config.allowAbsolute) {
2653
+ result = result.replace(/^\/+/, "");
2654
+ result = result.replace(/^[a-zA-Z]:/, "");
2655
+ result = result.replace(/^\\\\/, "");
2656
+ }
2657
+ result = result.replace(/\/+/g, "/");
2658
+ result = result.replace(/\/+$/, "");
2659
+ if (result.length > maxLength) {
2660
+ result = result.slice(0, maxLength);
2661
+ }
2662
+ return result;
2663
+ }
2664
+ function getExtension(path) {
2665
+ if (!path || typeof path !== "string") return "";
2666
+ const normalized = normalizePathSeparators(path);
2667
+ const segments = normalized.split("/");
2668
+ const filename = segments[segments.length - 1] || "";
2669
+ const dotIndex = filename.lastIndexOf(".");
2670
+ if (dotIndex <= 0) return "";
2671
+ return filename.slice(dotIndex).toLowerCase();
2672
+ }
2673
+ function sanitizeFilename(filename) {
2674
+ if (typeof filename !== "string") return "file";
2675
+ if (!filename) return "file";
2676
+ let result = filename;
2677
+ result = result.replace(/[/\\]/g, "");
2678
+ result = result.replace(/\0/g, "");
2679
+ result = result.replace(/[\x00-\x1f\x7f]/g, "");
2680
+ result = result.replace(/[<>:"|?*]/g, "");
2681
+ result = result.replace(/^[.\s]+|[.\s]+$/g, "");
2682
+ if (result.length > 255) {
2683
+ const ext = getExtension(result);
2684
+ const name = result.slice(0, 255 - ext.length);
2685
+ result = name + ext;
2686
+ }
2687
+ return result || "file";
2688
+ }
2689
+
2690
+ // src/middleware/validation/validators/file.ts
2691
+ var MAGIC_NUMBERS = [
2692
+ // Images
2693
+ { type: "image/jpeg", extension: ".jpg", signature: [255, 216, 255] },
2694
+ { type: "image/png", extension: ".png", signature: [137, 80, 78, 71, 13, 10, 26, 10] },
2695
+ { type: "image/gif", extension: ".gif", signature: [71, 73, 70, 56] },
2696
+ // GIF87a or GIF89a
2697
+ { type: "image/webp", extension: ".webp", signature: [82, 73, 70, 70], offset: 0 },
2698
+ // RIFF
2699
+ { type: "image/bmp", extension: ".bmp", signature: [66, 77] },
2700
+ { type: "image/tiff", extension: ".tiff", signature: [73, 73, 42, 0] },
2701
+ // Little endian
2702
+ { type: "image/tiff", extension: ".tiff", signature: [77, 77, 0, 42] },
2703
+ // Big endian
2704
+ { type: "image/x-icon", extension: ".ico", signature: [0, 0, 1, 0] },
2705
+ { type: "image/svg+xml", extension: ".svg", signature: [60, 63, 120, 109, 108] },
2706
+ // <?xml
2707
+ // Documents
2708
+ { type: "application/pdf", extension: ".pdf", signature: [37, 80, 68, 70] },
2709
+ // %PDF
2710
+ { type: "application/zip", extension: ".zip", signature: [80, 75, 3, 4] },
2711
+ // PK
2712
+ { type: "application/gzip", extension: ".gz", signature: [31, 139] },
2713
+ { type: "application/x-rar-compressed", extension: ".rar", signature: [82, 97, 114, 33] },
2714
+ { type: "application/x-7z-compressed", extension: ".7z", signature: [55, 122, 188, 175, 39, 28] },
2715
+ // Microsoft Office (new format - zip based)
2716
+ { type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", extension: ".xlsx", signature: [80, 75, 3, 4] },
2717
+ { type: "application/vnd.openxmlformats-officedocument.wordprocessingml.document", extension: ".docx", signature: [80, 75, 3, 4] },
2718
+ { type: "application/vnd.openxmlformats-officedocument.presentationml.presentation", extension: ".pptx", signature: [80, 75, 3, 4] },
2719
+ // Microsoft Office (old format)
2720
+ { type: "application/msword", extension: ".doc", signature: [208, 207, 17, 224, 161, 177, 26, 225] },
2721
+ { type: "application/vnd.ms-excel", extension: ".xls", signature: [208, 207, 17, 224, 161, 177, 26, 225] },
2722
+ // Audio
2723
+ { type: "audio/mpeg", extension: ".mp3", signature: [255, 251] },
2724
+ // MP3 frame sync
2725
+ { type: "audio/mpeg", extension: ".mp3", signature: [73, 68, 51] },
2726
+ // ID3
2727
+ { type: "audio/wav", extension: ".wav", signature: [82, 73, 70, 70] },
2728
+ // RIFF
2729
+ { type: "audio/ogg", extension: ".ogg", signature: [79, 103, 103, 83] },
2730
+ { type: "audio/flac", extension: ".flac", signature: [102, 76, 97, 67] },
2731
+ // Video
2732
+ { type: "video/mp4", extension: ".mp4", signature: [0, 0, 0], offset: 0 },
2733
+ // Partial match
2734
+ { type: "video/webm", extension: ".webm", signature: [26, 69, 223, 163] },
2735
+ { type: "video/avi", extension: ".avi", signature: [82, 73, 70, 70] },
2736
+ // RIFF
2737
+ { type: "video/quicktime", extension: ".mov", signature: [0, 0, 0, 20, 102, 116, 121, 112] },
2738
+ // Web
2739
+ { type: "application/wasm", extension: ".wasm", signature: [0, 97, 115, 109] },
2740
+ // \0asm
2741
+ // Fonts
2742
+ { type: "font/woff", extension: ".woff", signature: [119, 79, 70, 70] },
2743
+ { type: "font/woff2", extension: ".woff2", signature: [119, 79, 70, 50] }
2744
+ ];
2745
+ var DEFAULT_MAX_FILE_SIZE = 10 * 1024 * 1024;
2746
+ var DEFAULT_MAX_FILES = 10;
2747
+ var DANGEROUS_EXTENSIONS = [
2748
+ ".exe",
2749
+ ".dll",
2750
+ ".so",
2751
+ ".dylib",
2752
+ ".bin",
2753
+ ".sh",
2754
+ ".bash",
2755
+ ".bat",
2756
+ ".cmd",
2757
+ ".ps1",
2758
+ ".vbs",
2759
+ ".php",
2760
+ ".asp",
2761
+ ".aspx",
2762
+ ".jsp",
2763
+ ".cgi",
2764
+ ".pl",
2765
+ ".py",
2766
+ ".rb",
2767
+ ".jar",
2768
+ ".class",
2769
+ ".msi",
2770
+ ".dmg",
2771
+ ".pkg",
2772
+ ".deb",
2773
+ ".rpm",
2774
+ ".scr",
2775
+ ".pif",
2776
+ ".com",
2777
+ ".hta"
2778
+ ];
2779
+ function checkMagicNumber(bytes, magicNumber) {
2780
+ const offset = magicNumber.offset || 0;
2781
+ const signature = magicNumber.signature;
2782
+ if (bytes.length < offset + signature.length) {
2783
+ return false;
2784
+ }
2785
+ for (let i = 0; i < signature.length; i++) {
2786
+ if (bytes[offset + i] !== signature[i]) {
2787
+ return false;
2788
+ }
2789
+ }
2790
+ return true;
2791
+ }
2792
+ function detectFileType(bytes) {
2793
+ for (const magic of MAGIC_NUMBERS) {
2794
+ if (checkMagicNumber(bytes, magic)) {
2795
+ return { type: magic.type, extension: magic.extension };
2796
+ }
2797
+ }
2798
+ return null;
2799
+ }
2800
+ async function validateFile(file, config = {}) {
2801
+ const {
2802
+ maxSize = DEFAULT_MAX_FILE_SIZE,
2803
+ minSize = 0,
2804
+ allowedTypes = [],
2805
+ blockedTypes = [],
2806
+ allowedExtensions = [],
2807
+ blockedExtensions = DANGEROUS_EXTENSIONS,
2808
+ validateMagicNumbers = true,
2809
+ sanitizeFilename: doSanitize = true
2810
+ } = config;
2811
+ const errors = [];
2812
+ const extension = getExtension(file.name);
2813
+ const info = {
2814
+ filename: doSanitize ? sanitizeFilename(file.name) : file.name,
2815
+ size: file.size,
2816
+ type: file.type,
2817
+ extension
2818
+ };
2819
+ if (file.size > maxSize) {
2820
+ errors.push({
2821
+ filename: file.name,
2822
+ code: "size_exceeded",
2823
+ message: `File size (${formatBytes(file.size)}) exceeds maximum allowed (${formatBytes(maxSize)})`,
2824
+ details: { size: file.size, maxSize }
2825
+ });
2826
+ }
2827
+ if (file.size < minSize) {
2828
+ errors.push({
2829
+ filename: file.name,
2830
+ code: "size_too_small",
2831
+ message: `File size (${formatBytes(file.size)}) is below minimum required (${formatBytes(minSize)})`,
2832
+ details: { size: file.size, minSize }
2833
+ });
2834
+ }
2835
+ if (blockedExtensions.length > 0 && extension) {
2836
+ if (blockedExtensions.map((e) => e.toLowerCase()).includes(extension.toLowerCase())) {
2837
+ errors.push({
2838
+ filename: file.name,
2839
+ code: "extension_not_allowed",
2840
+ message: `File extension '${extension}' is not allowed`,
2841
+ details: { extension, blockedExtensions }
2842
+ });
2843
+ }
2844
+ }
2845
+ if (allowedExtensions.length > 0 && extension) {
2846
+ if (!allowedExtensions.map((e) => e.toLowerCase()).includes(extension.toLowerCase())) {
2847
+ errors.push({
2848
+ filename: file.name,
2849
+ code: "extension_not_allowed",
2850
+ message: `File extension '${extension}' is not in allowed list`,
2851
+ details: { extension, allowedExtensions }
2852
+ });
2853
+ }
2854
+ }
2855
+ if (blockedTypes.length > 0 && file.type) {
2856
+ if (blockedTypes.includes(file.type)) {
2857
+ errors.push({
2858
+ filename: file.name,
2859
+ code: "type_not_allowed",
2860
+ message: `File type '${file.type}' is not allowed`,
2861
+ details: { type: file.type, blockedTypes }
2862
+ });
2863
+ }
2864
+ }
2865
+ if (allowedTypes.length > 0) {
2866
+ if (!allowedTypes.includes(file.type)) {
2867
+ errors.push({
2868
+ filename: file.name,
2869
+ code: "type_not_allowed",
2870
+ message: `File type '${file.type}' is not in allowed list`,
2871
+ details: { type: file.type, allowedTypes }
2872
+ });
2873
+ }
2874
+ }
2875
+ if (validateMagicNumbers && errors.length === 0) {
2876
+ try {
2877
+ const buffer = await file.arrayBuffer();
2878
+ const bytes = new Uint8Array(buffer.slice(0, 32));
2879
+ const detected = detectFileType(bytes);
2880
+ if (detected) {
2881
+ if (file.type && detected.type !== file.type) {
2882
+ const isSimilar = detected.type.startsWith("image/") && file.type.startsWith("image/") || detected.type.startsWith("audio/") && file.type.startsWith("audio/") || detected.type.startsWith("video/") && file.type.startsWith("video/");
2883
+ if (!isSimilar) {
2884
+ errors.push({
2885
+ filename: file.name,
2886
+ code: "invalid_content",
2887
+ message: `File content doesn't match declared type (claimed: ${file.type}, detected: ${detected.type})`,
2888
+ details: { claimed: file.type, detected: detected.type }
2889
+ });
2890
+ }
2891
+ }
2892
+ }
2893
+ } catch {
2894
+ }
2895
+ }
2896
+ return {
2897
+ valid: errors.length === 0,
2898
+ info,
2899
+ errors
2900
+ };
2901
+ }
2902
+ async function validateFiles(files, config = {}) {
2903
+ const { maxFiles = DEFAULT_MAX_FILES } = config;
2904
+ const allErrors = [];
2905
+ const infos = [];
2906
+ if (files.length > maxFiles) {
2907
+ allErrors.push({
2908
+ filename: "",
2909
+ code: "too_many_files",
2910
+ message: `Too many files (${files.length}), maximum allowed is ${maxFiles}`,
2911
+ details: { count: files.length, maxFiles }
2912
+ });
2913
+ }
2914
+ for (const file of files) {
2915
+ const result = await validateFile(file, config);
2916
+ infos.push(result.info);
2917
+ allErrors.push(...result.errors);
2918
+ }
2919
+ return {
2920
+ valid: allErrors.length === 0,
2921
+ infos,
2922
+ errors: allErrors
2923
+ };
2924
+ }
2925
+ function extractFilesFromFormData(formData) {
2926
+ const files = /* @__PURE__ */ new Map();
2927
+ formData.forEach((value, key) => {
2928
+ if (value instanceof File) {
2929
+ const existing = files.get(key) || [];
2930
+ existing.push(value);
2931
+ files.set(key, existing);
2932
+ }
2933
+ });
2934
+ return files;
2935
+ }
2936
+ async function validateFilesFromRequest(request, config = {}) {
2937
+ const contentType = request.headers.get("content-type") || "";
2938
+ if (!contentType.includes("multipart/form-data")) {
2939
+ return { valid: true, files: /* @__PURE__ */ new Map(), errors: [] };
2940
+ }
2941
+ try {
2942
+ const formData = await request.formData();
2943
+ const fileMap = extractFilesFromFormData(formData);
2944
+ const allInfos = /* @__PURE__ */ new Map();
2945
+ const allErrors = [];
2946
+ let totalFileCount = 0;
2947
+ for (const [field, files] of fileMap.entries()) {
2948
+ totalFileCount += files.length;
2949
+ const result = await validateFiles(files, { ...config, maxFiles: Infinity });
2950
+ allInfos.set(field, result.infos);
2951
+ allErrors.push(...result.errors.map((e) => ({ ...e, field })));
2952
+ }
2953
+ const maxFiles = config.maxFiles ?? DEFAULT_MAX_FILES;
2954
+ if (totalFileCount > maxFiles) {
2955
+ allErrors.push({
2956
+ filename: "",
2957
+ code: "too_many_files",
2958
+ message: `Total file count (${totalFileCount}) exceeds maximum (${maxFiles})`,
2959
+ details: { count: totalFileCount, maxFiles }
2960
+ });
2961
+ }
2962
+ return {
2963
+ valid: allErrors.length === 0,
2964
+ files: allInfos,
2965
+ errors: allErrors
2966
+ };
2967
+ } catch {
2968
+ return {
2969
+ valid: false,
2970
+ files: /* @__PURE__ */ new Map(),
2971
+ errors: [{
2972
+ filename: "",
2973
+ code: "invalid_content",
2974
+ message: "Failed to parse multipart form data"
2975
+ }]
2976
+ };
2977
+ }
2978
+ }
2979
+ function defaultFileErrorResponse(errors) {
2980
+ return new Response(
2981
+ JSON.stringify({
2982
+ error: "file_validation_error",
2983
+ message: "File validation failed",
2984
+ details: errors.map((e) => ({
2985
+ filename: e.filename,
2986
+ field: e.field,
2987
+ code: e.code,
2988
+ message: e.message
2989
+ }))
2990
+ }),
2991
+ {
2992
+ status: 400,
2993
+ headers: { "Content-Type": "application/json" }
2994
+ }
2995
+ );
2996
+ }
2997
+ function formatBytes(bytes) {
2998
+ if (bytes === 0) return "0 B";
2999
+ const units = ["B", "KB", "MB", "GB"];
3000
+ const k = 1024;
3001
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
3002
+ return `${parseFloat((bytes / Math.pow(k, i)).toFixed(2))} ${units[i]}`;
3003
+ }
3004
+
3005
+ // src/middleware/validation/sanitizers/xss.ts
3006
+ var DEFAULT_ALLOWED_TAGS = [
3007
+ "a",
3008
+ "abbr",
3009
+ "b",
3010
+ "blockquote",
3011
+ "br",
3012
+ "code",
3013
+ "del",
3014
+ "em",
3015
+ "h1",
3016
+ "h2",
3017
+ "h3",
3018
+ "h4",
3019
+ "h5",
3020
+ "h6",
3021
+ "hr",
3022
+ "i",
3023
+ "ins",
3024
+ "li",
3025
+ "mark",
3026
+ "ol",
3027
+ "p",
3028
+ "pre",
3029
+ "q",
3030
+ "s",
3031
+ "small",
3032
+ "span",
3033
+ "strong",
3034
+ "sub",
3035
+ "sup",
3036
+ "u",
3037
+ "ul"
3038
+ ];
3039
+ var DEFAULT_ALLOWED_ATTRIBUTES = {
3040
+ a: ["href", "title", "target", "rel"],
3041
+ img: ["src", "alt", "title", "width", "height"],
3042
+ abbr: ["title"],
3043
+ q: ["cite"],
3044
+ blockquote: ["cite"]
3045
+ };
3046
+ var DEFAULT_SAFE_PROTOCOLS = ["http:", "https:", "mailto:", "tel:"];
3047
+ var DANGEROUS_PATTERNS2 = [
3048
+ // Event handlers
3049
+ /\bon\w+\s*=/gi,
3050
+ // JavaScript protocol
3051
+ /javascript\s*:/gi,
3052
+ // VBScript protocol
3053
+ /vbscript\s*:/gi,
3054
+ // Data URI with scripts
3055
+ /data\s*:[^,]*(?:text\/html|application\/javascript|text\/javascript)/gi,
3056
+ // Expression in CSS
3057
+ /expression\s*\(/gi,
3058
+ // Binding in CSS (Firefox)
3059
+ /-moz-binding\s*:/gi,
3060
+ // Behavior in CSS (IE)
3061
+ /behavior\s*:/gi,
3062
+ // Import in CSS
3063
+ /@import/gi,
3064
+ // Script tags
3065
+ /<\s*script/gi,
3066
+ // Style tags with expressions
3067
+ /<\s*style[^>]*>[^<]*expression/gi,
3068
+ // SVG with scripts
3069
+ /<\s*svg[^>]*onload/gi,
3070
+ // Object/embed/applet tags
3071
+ /<\s*(object|embed|applet)/gi,
3072
+ // Base tag (can redirect resources)
3073
+ /<\s*base/gi,
3074
+ // Meta refresh
3075
+ /<\s*meta[^>]*http-equiv\s*=\s*["']?refresh/gi,
3076
+ // Form action hijacking
3077
+ /<\s*form[^>]*action\s*=\s*["']?javascript/gi,
3078
+ // Link tag with import
3079
+ /<\s*link[^>]*rel\s*=\s*["']?import/gi
3080
+ ];
3081
+ var HTML_ENTITIES = {
3082
+ "&": "&amp;",
3083
+ "<": "&lt;",
3084
+ ">": "&gt;",
3085
+ '"': "&quot;",
3086
+ "'": "&#x27;",
3087
+ "/": "&#x2F;",
3088
+ "`": "&#x60;",
3089
+ "=": "&#x3D;"
3090
+ };
3091
+ function escapeHtml(str) {
3092
+ return str.replace(/[&<>"'`=/]/g, (char) => HTML_ENTITIES[char] || char);
3093
+ }
3094
+ function unescapeHtml(str) {
3095
+ const entityMap = {
3096
+ "&amp;": "&",
3097
+ "&lt;": "<",
3098
+ "&gt;": ">",
3099
+ "&quot;": '"',
3100
+ "&#x27;": "'",
3101
+ "&#x2F;": "/",
3102
+ "&#x60;": "`",
3103
+ "&#x3D;": "=",
3104
+ "&#39;": "'",
3105
+ "&#47;": "/"
3106
+ };
3107
+ return str.replace(/&(?:amp|lt|gt|quot|#x27|#x2F|#x60|#x3D|#39|#47);/gi, (entity) => {
3108
+ return entityMap[entity.toLowerCase()] || entity;
3109
+ });
3110
+ }
3111
+ function stripHtml(str) {
3112
+ let result = str.replace(/<script[^>]*>[\s\S]*?<\/script>/gi, "");
3113
+ result = result.replace(/<style[^>]*>[\s\S]*?<\/style>/gi, "");
3114
+ result = result.replace(/<[^>]*>/g, "");
3115
+ result = unescapeHtml(result);
3116
+ result = result.replace(/\0/g, "");
3117
+ return result.trim();
3118
+ }
3119
+ function isSafeUrl(url, allowedProtocols = DEFAULT_SAFE_PROTOCOLS) {
3120
+ if (!url) return true;
3121
+ const trimmed = url.trim().toLowerCase();
3122
+ if (trimmed.startsWith("javascript:")) return false;
3123
+ if (trimmed.startsWith("vbscript:")) return false;
3124
+ if (trimmed.startsWith("data:image/")) return true;
3125
+ if (trimmed.startsWith("data:")) return false;
3126
+ try {
3127
+ const parsed = new URL(url, "https://example.com");
3128
+ if (parsed.protocol && !allowedProtocols.includes(parsed.protocol)) {
3129
+ if (!url.includes(":")) return true;
3130
+ return false;
3131
+ }
3132
+ } catch {
3133
+ return true;
3134
+ }
3135
+ return true;
3136
+ }
3137
+ function sanitizeHtml(str, allowedTags = DEFAULT_ALLOWED_TAGS, allowedAttributes = DEFAULT_ALLOWED_ATTRIBUTES, allowedProtocols = DEFAULT_SAFE_PROTOCOLS) {
3138
+ let result = str.replace(/\0/g, "");
3139
+ result = result.replace(/<script[^>]*>[\s\S]*?<\/script>/gi, "");
3140
+ result = result.replace(/<style[^>]*>[\s\S]*?<\/style>/gi, "");
3141
+ result = result.replace(/<!--[\s\S]*?-->/g, "");
3142
+ result = result.replace(/<\/?([a-z][a-z0-9]*)\b([^>]*)>/gi, (match, tagName, attributes) => {
3143
+ const lowerTag = tagName.toLowerCase();
3144
+ const isClosing = match.startsWith("</");
3145
+ if (!allowedTags.includes(lowerTag)) {
3146
+ return "";
3147
+ }
3148
+ if (isClosing) {
3149
+ return `</${lowerTag}>`;
3150
+ }
3151
+ const allowedAttrs = allowedAttributes[lowerTag] || [];
3152
+ const safeAttrs = [];
3153
+ const attrRegex = /([a-z][a-z0-9-]*)\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s>]*))/gi;
3154
+ let attrMatch;
3155
+ while ((attrMatch = attrRegex.exec(attributes)) !== null) {
3156
+ const attrName = attrMatch[1].toLowerCase();
3157
+ const attrValue = attrMatch[2] || attrMatch[3] || attrMatch[4] || "";
3158
+ if (!allowedAttrs.includes(attrName)) continue;
3159
+ if (DANGEROUS_PATTERNS2.some((pattern) => pattern.test(attrValue))) continue;
3160
+ if (["href", "src", "action", "formaction"].includes(attrName)) {
3161
+ if (!isSafeUrl(attrValue, allowedProtocols)) continue;
3162
+ }
3163
+ const safeValue = escapeHtml(attrValue);
3164
+ safeAttrs.push(`${attrName}="${safeValue}"`);
3165
+ }
3166
+ const attrStr = safeAttrs.length > 0 ? " " + safeAttrs.join(" ") : "";
3167
+ return `<${lowerTag}${attrStr}>`;
3168
+ });
3169
+ for (const pattern of DANGEROUS_PATTERNS2) {
3170
+ result = result.replace(pattern, "");
3171
+ }
3172
+ return result;
3173
+ }
3174
+ function detectXSS(str) {
3175
+ if (!str || typeof str !== "string") return false;
3176
+ const normalized = str.replace(/\\x([0-9a-f]{2})/gi, (_, hex) => String.fromCharCode(parseInt(hex, 16))).replace(/\\u([0-9a-f]{4})/gi, (_, hex) => String.fromCharCode(parseInt(hex, 16))).replace(/&#x([0-9a-f]+);?/gi, (_, hex) => String.fromCharCode(parseInt(hex, 16))).replace(/&#(\d+);?/gi, (_, dec) => String.fromCharCode(parseInt(dec, 10)));
3177
+ for (const pattern of DANGEROUS_PATTERNS2) {
3178
+ pattern.lastIndex = 0;
3179
+ if (pattern.test(normalized)) {
3180
+ return true;
3181
+ }
3182
+ }
3183
+ return false;
3184
+ }
3185
+ function sanitize(input, config = {}) {
3186
+ if (!input || typeof input !== "string") return "";
3187
+ const {
3188
+ mode = "escape",
3189
+ allowedTags = DEFAULT_ALLOWED_TAGS,
3190
+ allowedAttributes = DEFAULT_ALLOWED_ATTRIBUTES,
3191
+ allowedProtocols = DEFAULT_SAFE_PROTOCOLS,
3192
+ maxLength,
3193
+ stripNull = true
3194
+ } = config;
3195
+ let result = input;
3196
+ if (stripNull) {
3197
+ result = result.replace(/\0/g, "");
3198
+ }
3199
+ switch (mode) {
3200
+ case "escape":
3201
+ result = escapeHtml(result);
3202
+ break;
3203
+ case "strip":
3204
+ result = stripHtml(result);
3205
+ break;
3206
+ case "allow-safe":
3207
+ result = sanitizeHtml(result, allowedTags, allowedAttributes, allowedProtocols);
3208
+ break;
3209
+ }
3210
+ if (maxLength !== void 0 && result.length > maxLength) {
3211
+ result = result.slice(0, maxLength);
3212
+ }
3213
+ return result;
3214
+ }
3215
+ function sanitizeObject(obj, config = {}) {
3216
+ if (typeof obj === "string") {
3217
+ return sanitize(obj, config);
3218
+ }
3219
+ if (Array.isArray(obj)) {
3220
+ return obj.map((item) => sanitizeObject(item, config));
3221
+ }
3222
+ if (typeof obj === "object" && obj !== null) {
3223
+ const result = {};
3224
+ for (const [key, value] of Object.entries(obj)) {
3225
+ result[key] = sanitizeObject(value, config);
3226
+ }
3227
+ return result;
3228
+ }
3229
+ return obj;
3230
+ }
3231
+ function sanitizeFields(obj, fields, config = {}) {
3232
+ const result = { ...obj };
3233
+ for (const field of fields) {
3234
+ if (field in result && typeof result[field] === "string") {
3235
+ result[field] = sanitize(result[field], config);
3236
+ }
3237
+ }
3238
+ return result;
3239
+ }
3240
+
3241
+ // src/middleware/validation/sanitizers/sql.ts
3242
+ var SQL_PATTERNS = [
3243
+ // High severity - Definite attacks
3244
+ {
3245
+ pattern: /'\s*OR\s+'?\d+'?\s*=\s*'?\d+'?/gi,
3246
+ name: "OR '1'='1' attack",
3247
+ severity: "high"
3248
+ },
3249
+ {
3250
+ pattern: /'\s*OR\s+'[^']*'\s*=\s*'[^']*'/gi,
3251
+ name: "OR 'x'='x' attack",
3252
+ severity: "high"
3253
+ },
3254
+ {
3255
+ pattern: /;\s*DROP\s+(TABLE|DATABASE|INDEX|VIEW)/gi,
3256
+ name: "DROP statement",
3257
+ severity: "high"
3258
+ },
3259
+ {
3260
+ pattern: /;\s*DELETE\s+FROM/gi,
3261
+ name: "DELETE statement",
3262
+ severity: "high"
3263
+ },
3264
+ {
3265
+ pattern: /;\s*TRUNCATE\s+/gi,
3266
+ name: "TRUNCATE statement",
3267
+ severity: "high"
3268
+ },
3269
+ {
3270
+ pattern: /;\s*INSERT\s+INTO/gi,
3271
+ name: "INSERT statement",
3272
+ severity: "high"
3273
+ },
3274
+ {
3275
+ pattern: /;\s*UPDATE\s+\w+\s+SET/gi,
3276
+ name: "UPDATE statement",
3277
+ severity: "high"
3278
+ },
3279
+ {
3280
+ pattern: /UNION\s+(ALL\s+)?SELECT/gi,
3281
+ name: "UNION SELECT attack",
3282
+ severity: "high"
3283
+ },
3284
+ {
3285
+ pattern: /EXEC(\s+|\()+(sp_|xp_)/gi,
3286
+ name: "SQL Server stored procedure",
3287
+ severity: "high"
3288
+ },
3289
+ {
3290
+ pattern: /EXECUTE\s+IMMEDIATE/gi,
3291
+ name: "Oracle EXECUTE IMMEDIATE",
3292
+ severity: "high"
3293
+ },
3294
+ {
3295
+ pattern: /INTO\s+(OUT|DUMP)FILE/gi,
3296
+ name: "MySQL file write",
3297
+ severity: "high"
3298
+ },
3299
+ {
3300
+ pattern: /LOAD_FILE\s*\(/gi,
3301
+ name: "MySQL file read",
3302
+ severity: "high"
3303
+ },
3304
+ {
3305
+ pattern: /BENCHMARK\s*\(\s*\d+\s*,/gi,
3306
+ name: "MySQL BENCHMARK DoS",
3307
+ severity: "high"
3308
+ },
3309
+ {
3310
+ pattern: /SLEEP\s*\(\s*\d+\s*\)/gi,
3311
+ name: "SQL SLEEP time-based attack",
3312
+ severity: "high"
3313
+ },
3314
+ {
3315
+ pattern: /WAITFOR\s+DELAY/gi,
3316
+ name: "SQL Server WAITFOR DELAY",
3317
+ severity: "high"
3318
+ },
3319
+ {
3320
+ pattern: /PG_SLEEP\s*\(/gi,
3321
+ name: "PostgreSQL pg_sleep",
3322
+ severity: "high"
3323
+ },
3324
+ // Medium severity - Likely attacks
3325
+ {
3326
+ pattern: /'\s*--/g,
3327
+ name: "SQL comment injection",
3328
+ severity: "medium"
3329
+ },
3330
+ {
3331
+ pattern: /'\s*#/g,
3332
+ name: "MySQL comment injection",
3333
+ severity: "medium"
3334
+ },
3335
+ {
3336
+ pattern: /\/\*[\s\S]*?\*\//g,
3337
+ name: "Block comment",
3338
+ severity: "medium"
3339
+ },
3340
+ {
3341
+ pattern: /'\s*;\s*$/g,
3342
+ name: "Statement terminator",
3343
+ severity: "medium"
3344
+ },
3345
+ {
3346
+ pattern: /HAVING\s+\d+\s*=\s*\d+/gi,
3347
+ name: "HAVING clause injection",
3348
+ severity: "medium"
3349
+ },
3350
+ {
3351
+ pattern: /GROUP\s+BY\s+\d+/gi,
3352
+ name: "GROUP BY injection",
3353
+ severity: "medium"
3354
+ },
3355
+ {
3356
+ pattern: /ORDER\s+BY\s+\d+/gi,
3357
+ name: "ORDER BY injection",
3358
+ severity: "medium"
3359
+ },
3360
+ {
3361
+ pattern: /CONCAT\s*\(/gi,
3362
+ name: "CONCAT function",
3363
+ severity: "medium"
3364
+ },
3365
+ {
3366
+ pattern: /CHAR\s*\(\s*\d+\s*\)/gi,
3367
+ name: "CHAR function bypass",
3368
+ severity: "medium"
3369
+ },
3370
+ {
3371
+ pattern: /0x[0-9a-f]{2,}/gi,
3372
+ name: "Hex encoded value",
3373
+ severity: "medium"
3374
+ },
3375
+ {
3376
+ pattern: /CONVERT\s*\(/gi,
3377
+ name: "CONVERT function",
3378
+ severity: "medium"
3379
+ },
3380
+ {
3381
+ pattern: /CAST\s*\(/gi,
3382
+ name: "CAST function",
3383
+ severity: "medium"
3384
+ },
3385
+ // Low severity - Suspicious but may be false positives
3386
+ {
3387
+ pattern: /'\s*AND\s+'?\d+'?\s*=\s*'?\d+'?/gi,
3388
+ name: "AND '1'='1' pattern",
3389
+ severity: "low"
3390
+ },
3391
+ {
3392
+ pattern: /'\s*AND\s+'[^']*'\s*=\s*'[^']*'/gi,
3393
+ name: "AND 'x'='x' pattern",
3394
+ severity: "low"
3395
+ },
3396
+ {
3397
+ pattern: /SELECT\s+[\w\s,*]+\s+FROM/gi,
3398
+ name: "SELECT statement",
3399
+ severity: "low"
3400
+ },
3401
+ {
3402
+ pattern: /'\s*\+\s*'/g,
3403
+ name: "String concatenation",
3404
+ severity: "low"
3405
+ },
3406
+ {
3407
+ pattern: /'\s*\|\|\s*'/g,
3408
+ name: "Oracle string concatenation",
3409
+ severity: "low"
3410
+ }
3411
+ ];
3412
+ var ENCODED_PATTERNS = [
3413
+ {
3414
+ pattern: /%27\s*%4f%52\s*%27/gi,
3415
+ // URL encoded ' OR '
3416
+ name: "URL encoded OR injection",
3417
+ severity: "high"
3418
+ },
3419
+ {
3420
+ pattern: /%27\s*%2d%2d/gi,
3421
+ // URL encoded ' --
3422
+ name: "URL encoded comment injection",
3423
+ severity: "medium"
3424
+ },
3425
+ {
3426
+ pattern: /\0|%00/g,
3427
+ // Null byte (decoded or encoded)
3428
+ name: "Null byte injection",
3429
+ severity: "high"
3430
+ },
3431
+ {
3432
+ pattern: /\\x27/gi,
3433
+ // Hex escape
3434
+ name: "Hex escaped quote",
3435
+ severity: "medium"
3436
+ },
3437
+ {
3438
+ pattern: /\\u0027/gi,
3439
+ // Unicode escape
3440
+ name: "Unicode escaped quote",
3441
+ severity: "medium"
3442
+ }
3443
+ ];
3444
+ function normalizeInput(input) {
3445
+ let result = input;
3446
+ try {
3447
+ result = decodeURIComponent(result);
3448
+ } catch {
3449
+ }
3450
+ result = result.replace(/&#x([0-9a-f]+);?/gi, (_, hex) => String.fromCharCode(parseInt(hex, 16))).replace(/&#(\d+);?/gi, (_, dec) => String.fromCharCode(parseInt(dec, 10))).replace(/&quot;/gi, '"').replace(/&apos;/gi, "'").replace(/&lt;/gi, "<").replace(/&gt;/gi, ">").replace(/&amp;/gi, "&");
3451
+ result = result.replace(
3452
+ /\\x([0-9a-f]{2})/gi,
3453
+ (_, hex) => String.fromCharCode(parseInt(hex, 16))
3454
+ );
3455
+ result = result.replace(
3456
+ /\\u([0-9a-f]{4})/gi,
3457
+ (_, hex) => String.fromCharCode(parseInt(hex, 16))
3458
+ );
3459
+ return result;
3460
+ }
3461
+ function detectSQLInjection(input, options = {}) {
3462
+ if (!input || typeof input !== "string") return [];
3463
+ const {
3464
+ customPatterns = [],
3465
+ checkEncoded = true,
3466
+ minSeverity = "low"
3467
+ } = options;
3468
+ const severityOrder = { low: 0, medium: 1, high: 2 };
3469
+ const minSeverityLevel = severityOrder[minSeverity];
3470
+ const detections = [];
3471
+ const seenPatterns = /* @__PURE__ */ new Set();
3472
+ const normalizedInput = checkEncoded ? normalizeInput(input) : input;
3473
+ const allPatterns = [
3474
+ ...SQL_PATTERNS,
3475
+ ...checkEncoded ? ENCODED_PATTERNS : [],
3476
+ ...customPatterns.map((p) => ({ pattern: p, name: "Custom pattern", severity: "high" }))
3477
+ ];
3478
+ for (const { pattern, name, severity } of allPatterns) {
3479
+ if (severityOrder[severity] < minSeverityLevel) continue;
3480
+ pattern.lastIndex = 0;
3481
+ const testInput = checkEncoded ? normalizedInput : input;
3482
+ if (pattern.test(testInput)) {
3483
+ const key = `${name}:${severity}`;
3484
+ if (!seenPatterns.has(key)) {
3485
+ seenPatterns.add(key);
3486
+ detections.push({
3487
+ field: "",
3488
+ // Will be set by caller
3489
+ value: input,
3490
+ pattern: name,
3491
+ severity
3492
+ });
3493
+ }
3494
+ }
3495
+ }
3496
+ return detections;
3497
+ }
3498
+ function hasSQLInjection(input, minSeverity = "medium") {
3499
+ return detectSQLInjection(input, { minSeverity }).length > 0;
3500
+ }
3501
+ function sanitizeSQLInput(input) {
3502
+ if (!input || typeof input !== "string") return "";
3503
+ let result = input;
3504
+ result = result.replace(/\0/g, "");
3505
+ result = result.replace(/'/g, "''");
3506
+ result = result.replace(/;/g, "");
3507
+ result = result.replace(/--/g, "");
3508
+ result = result.replace(/\/\*/g, "");
3509
+ result = result.replace(/\*\//g, "");
3510
+ result = result.replace(/0x[0-9a-f]+/gi, "");
3511
+ return result;
3512
+ }
3513
+ function detectSQLInjectionInObject(obj, options = {}) {
3514
+ const { fields, deep = true, customPatterns, minSeverity } = options;
3515
+ const detections = [];
3516
+ function walk(value, path) {
3517
+ if (typeof value === "string") {
3518
+ if (fields && fields.length > 0) {
3519
+ const fieldName = path.split(".").pop() || path;
3520
+ if (!fields.includes(fieldName)) return;
3521
+ }
3522
+ const detected = detectSQLInjection(value, { customPatterns, minSeverity });
3523
+ for (const d of detected) {
3524
+ detections.push({ ...d, field: path });
3525
+ }
3526
+ } else if (deep && Array.isArray(value)) {
3527
+ value.forEach((item, i) => walk(item, `${path}[${i}]`));
3528
+ } else if (deep && typeof value === "object" && value !== null) {
3529
+ for (const [key, val] of Object.entries(value)) {
3530
+ walk(val, path ? `${path}.${key}` : key);
3531
+ }
3532
+ }
3533
+ }
3534
+ walk(obj, "");
3535
+ return detections;
3536
+ }
3537
+
3538
+ // src/middleware/validation/middleware.ts
3539
+ function withValidation(handler, config) {
3540
+ const onError = config.onError || ((_, errors) => defaultValidationErrorResponse(errors));
3541
+ return async (req) => {
3542
+ const result = await validateRequest(req, {
3543
+ body: config.body,
3544
+ query: config.query,
3545
+ params: config.params,
3546
+ routeParams: config.routeParams
3547
+ });
3548
+ if (!result.success) {
3549
+ return onError(req, result.errors || []);
3550
+ }
3551
+ return handler(req, { validated: result.data });
3552
+ };
3553
+ }
3554
+ function withSanitization(handler, config = {}) {
3555
+ const {
3556
+ fields,
3557
+ mode = "escape",
3558
+ allowedTags,
3559
+ skip,
3560
+ onSanitized
3561
+ } = config;
3562
+ return async (req) => {
3563
+ if (skip && await skip(req)) {
3564
+ return handler(req, { sanitized: null, changes: [] });
3565
+ }
3566
+ let body;
3567
+ try {
3568
+ body = await req.json();
3569
+ } catch {
3570
+ return handler(req, { sanitized: null, changes: [] });
3571
+ }
3572
+ const changes = [];
3573
+ const sanitized = walkObject(body, (value, path) => {
3574
+ if (fields && fields.length > 0) {
3575
+ const fieldName = path.split(".").pop() || path;
3576
+ if (!fields.includes(fieldName)) {
3577
+ return value;
3578
+ }
3579
+ }
3580
+ const cleaned = sanitize(value, { mode, allowedTags });
3581
+ if (cleaned !== value) {
3582
+ changes.push({
3583
+ field: path,
3584
+ original: value,
3585
+ sanitized: cleaned
3586
+ });
3587
+ }
3588
+ return cleaned;
3589
+ }, "");
3590
+ if (onSanitized && changes.length > 0) {
3591
+ onSanitized(req, changes);
3592
+ }
3593
+ return handler(req, { sanitized, changes });
3594
+ };
3595
+ }
3596
+ function withXSSProtection(handler, config = {}) {
3597
+ const { fields, onDetection, checkQuery = true } = config;
3598
+ return async (req) => {
3599
+ const detections = [];
3600
+ if (checkQuery) {
3601
+ const url = new URL(req.url);
3602
+ for (const [key, value] of url.searchParams.entries()) {
3603
+ if (detectXSS(value)) {
3604
+ detections.push({ field: `query.${key}`, value });
3605
+ }
3606
+ }
3607
+ }
3608
+ let body;
3609
+ try {
3610
+ body = await req.json();
3611
+ } catch {
3612
+ body = null;
3613
+ }
3614
+ if (body) {
3615
+ walkObject(body, (value, path) => {
3616
+ if (fields && fields.length > 0) {
3617
+ const fieldName = path.split(".").pop() || path;
3618
+ if (!fields.includes(fieldName)) {
3619
+ return value;
3620
+ }
3621
+ }
3622
+ if (detectXSS(value)) {
3623
+ detections.push({ field: path, value });
3624
+ }
3625
+ return value;
3626
+ }, "");
3627
+ }
3628
+ if (detections.length > 0) {
3629
+ if (onDetection) {
3630
+ for (const { field, value } of detections) {
3631
+ const result = await onDetection(req, field, value);
3632
+ if (result instanceof Response) {
3633
+ return result;
3634
+ }
3635
+ }
3636
+ }
3637
+ return new Response(
3638
+ JSON.stringify({
3639
+ error: "xss_detected",
3640
+ message: "Potentially malicious content detected",
3641
+ fields: detections.map((d) => d.field)
3642
+ }),
3643
+ {
3644
+ status: 400,
3645
+ headers: { "Content-Type": "application/json" }
3646
+ }
3647
+ );
3648
+ }
3649
+ return handler(req);
3650
+ };
3651
+ }
3652
+ function withSQLProtection(handler, config = {}) {
3653
+ const {
3654
+ fields,
3655
+ deep = true,
3656
+ mode = "block",
3657
+ customPatterns,
3658
+ allowList = [],
3659
+ onDetection
3660
+ } = config;
3661
+ return async (req) => {
3662
+ let body;
3663
+ try {
3664
+ body = await req.json();
3665
+ } catch {
3666
+ return handler(req);
3667
+ }
3668
+ const detections = detectSQLInjectionInObject(body, {
3669
+ fields,
3670
+ deep,
3671
+ customPatterns,
3672
+ minSeverity: mode === "detect" ? "low" : "medium"
3673
+ });
3674
+ const filtered = detections.filter((d) => !allowList.includes(d.value));
3675
+ if (filtered.length > 0) {
3676
+ if (onDetection) {
3677
+ const result = await onDetection(req, filtered);
3678
+ if (result instanceof Response) {
3679
+ return result;
3680
+ }
3681
+ }
3682
+ if (mode === "block") {
3683
+ return new Response(
3684
+ JSON.stringify({
3685
+ error: "sql_injection_detected",
3686
+ message: "Potentially malicious SQL detected",
3687
+ detections: filtered.map((d) => ({
3688
+ field: d.field,
3689
+ pattern: d.pattern,
3690
+ severity: d.severity
3691
+ }))
3692
+ }),
3693
+ {
3694
+ status: 400,
3695
+ headers: { "Content-Type": "application/json" }
3696
+ }
3697
+ );
3698
+ }
3699
+ }
3700
+ return handler(req);
3701
+ };
3702
+ }
3703
+ function withContentType(handler, config) {
3704
+ const onInvalid = config.onInvalid || ((_, contentType) => defaultContentTypeErrorResponse(contentType, `Content-Type '${contentType}' is not allowed`));
3705
+ return async (req) => {
3706
+ const result = validateContentType(req, config);
3707
+ if (!result.valid) {
3708
+ return onInvalid(req, result.contentType);
3709
+ }
3710
+ return handler(req);
3711
+ };
3712
+ }
3713
+ function withFileValidation(handler, config = {}) {
3714
+ const onInvalid = config.onInvalid || ((_, errors) => defaultFileErrorResponse(errors));
3715
+ return async (req) => {
3716
+ const result = await validateFilesFromRequest(req, config);
3717
+ if (!result.valid) {
3718
+ return onInvalid(req, result.errors);
3719
+ }
3720
+ return handler(req, { files: result.files });
3721
+ };
3722
+ }
3723
+ function withSecureValidation(handler, config) {
3724
+ return async (req) => {
3725
+ const allErrors = [];
3726
+ if (config.contentType) {
3727
+ const ctResult = validateContentType(req, config.contentType);
3728
+ if (!ctResult.valid) {
3729
+ allErrors.push({
3730
+ field: "Content-Type",
3731
+ code: "invalid_content_type",
3732
+ message: ctResult.reason || "Invalid Content-Type"
3733
+ });
3734
+ }
3735
+ }
3736
+ let files;
3737
+ if (config.files) {
3738
+ const fileResult = await validateFilesFromRequest(req, config.files);
3739
+ if (!fileResult.valid) {
3740
+ allErrors.push(...fileResult.errors.map((e) => ({
3741
+ field: e.field || e.filename,
3742
+ code: e.code,
3743
+ message: e.message
3744
+ })));
3745
+ } else {
3746
+ files = fileResult.files;
3747
+ }
3748
+ }
3749
+ if (allErrors.length > 0) {
3750
+ const onError = config.onError || ((_, errors) => defaultValidationErrorResponse(errors));
3751
+ return onError(req, allErrors);
3752
+ }
3753
+ let validated;
3754
+ if (config.schema) {
3755
+ const schemaResult = await validateRequest(req, {
3756
+ body: config.schema.body,
3757
+ query: config.schema.query,
3758
+ params: config.schema.params,
3759
+ routeParams: config.routeParams
3760
+ });
3761
+ if (!schemaResult.success) {
3762
+ allErrors.push(...schemaResult.errors || []);
3763
+ } else {
3764
+ validated = schemaResult.data;
3765
+ }
3766
+ } else {
3767
+ validated = {
3768
+ body: {},
3769
+ query: {},
3770
+ params: {}
3771
+ };
3772
+ }
3773
+ if (config.sql && validated?.body) {
3774
+ const sqlDetections = detectSQLInjectionInObject(validated.body, {
3775
+ fields: config.sql.fields,
3776
+ deep: config.sql.deep,
3777
+ customPatterns: config.sql.customPatterns
3778
+ });
3779
+ if (sqlDetections.length > 0 && config.sql.mode !== "detect") {
3780
+ allErrors.push(...sqlDetections.map((d) => ({
3781
+ field: d.field,
3782
+ code: "sql_injection",
3783
+ message: `Potential SQL injection detected: ${d.pattern}`
3784
+ })));
3785
+ }
3786
+ }
3787
+ if (config.xss?.enabled && validated?.body) {
3788
+ walkObject(validated.body, (value, path) => {
3789
+ if (config.xss?.fields && config.xss.fields.length > 0) {
3790
+ const fieldName = path.split(".").pop() || path;
3791
+ if (!config.xss.fields.includes(fieldName)) {
3792
+ return value;
3793
+ }
3794
+ }
3795
+ if (detectXSS(value)) {
3796
+ allErrors.push({
3797
+ field: path,
3798
+ code: "xss_detected",
3799
+ message: "Potentially malicious content detected"
3800
+ });
3801
+ }
3802
+ return value;
3803
+ }, "");
3804
+ }
3805
+ if (allErrors.length > 0) {
3806
+ const onError = config.onError || ((_, errors) => defaultValidationErrorResponse(errors));
3807
+ return onError(req, allErrors);
3808
+ }
3809
+ return handler(req, { validated, files });
3810
+ };
3811
+ }
3812
+
3813
+ // src/middleware/audit/stores/memory.ts
3814
+ var MemoryStore2 = class {
3815
+ entries = [];
3816
+ maxEntries;
3817
+ ttl;
3818
+ constructor(options = {}) {
3819
+ this.maxEntries = options.maxEntries || 1e3;
3820
+ this.ttl = options.ttl || 0;
3821
+ }
3822
+ async write(entry) {
3823
+ this.entries.push(entry);
3824
+ if (this.entries.length > this.maxEntries) {
3825
+ this.entries = this.entries.slice(-this.maxEntries);
3826
+ }
3827
+ if (this.ttl > 0) {
3828
+ this.cleanExpired();
3829
+ }
3830
+ }
3831
+ async query(options = {}) {
3832
+ let result = [...this.entries];
3833
+ if (options.level) {
3834
+ const levels = Array.isArray(options.level) ? options.level : [options.level];
3835
+ result = result.filter((e) => levels.includes(e.level));
3836
+ }
3837
+ if (options.type) {
3838
+ result = result.filter((e) => e.type === options.type);
3839
+ }
3840
+ if (options.event) {
3841
+ const events = Array.isArray(options.event) ? options.event : [options.event];
3842
+ result = result.filter(
3843
+ (e) => e.type === "security" && events.includes(e.event)
3844
+ );
3845
+ }
3846
+ if (options.startTime) {
3847
+ result = result.filter((e) => e.timestamp >= options.startTime);
3848
+ }
3849
+ if (options.endTime) {
3850
+ result = result.filter((e) => e.timestamp <= options.endTime);
3851
+ }
3852
+ if (options.ip) {
3853
+ result = result.filter((e) => {
3854
+ if (e.type === "request") return e.request.ip === options.ip;
3855
+ if (e.type === "security") return e.source.ip === options.ip;
3856
+ return false;
3857
+ });
3858
+ }
3859
+ if (options.userId) {
3860
+ result = result.filter((e) => {
3861
+ if (e.type === "request") return e.user?.id === options.userId;
3862
+ if (e.type === "security") return e.source.userId === options.userId;
3863
+ return false;
3864
+ });
3865
+ }
3866
+ if (options.offset) {
3867
+ result = result.slice(options.offset);
3868
+ }
3869
+ if (options.limit) {
3870
+ result = result.slice(0, options.limit);
3871
+ }
3872
+ return result;
3873
+ }
3874
+ async flush() {
3875
+ }
3876
+ async close() {
3877
+ this.entries = [];
3878
+ }
3879
+ /**
3880
+ * Get all entries (for testing)
3881
+ */
3882
+ getEntries() {
3883
+ return [...this.entries];
3884
+ }
3885
+ /**
3886
+ * Clear all entries
3887
+ */
3888
+ clear() {
3889
+ this.entries = [];
3890
+ }
3891
+ /**
3892
+ * Get entry count
3893
+ */
3894
+ size() {
3895
+ return this.entries.length;
3896
+ }
3897
+ /**
3898
+ * Clean expired entries
3899
+ */
3900
+ cleanExpired() {
3901
+ if (this.ttl <= 0) return;
3902
+ const now = Date.now();
3903
+ this.entries = this.entries.filter((e) => {
3904
+ const age = now - e.timestamp.getTime();
3905
+ return age < this.ttl;
3906
+ });
3907
+ }
3908
+ };
3909
+ function createMemoryStore2(options) {
3910
+ return new MemoryStore2(options);
3911
+ }
3912
+
3913
+ // src/middleware/audit/stores/console.ts
3914
+ var COLORS = {
3915
+ reset: "\x1B[0m",
3916
+ bold: "\x1B[1m",
3917
+ dim: "\x1B[2m",
3918
+ // Log levels
3919
+ debug: "\x1B[36m",
3920
+ // Cyan
3921
+ info: "\x1B[32m",
3922
+ // Green
3923
+ warn: "\x1B[33m",
3924
+ // Yellow
3925
+ error: "\x1B[31m",
3926
+ // Red
3927
+ critical: "\x1B[35m",
3928
+ // Magenta
3929
+ // Security severity
3930
+ low: "\x1B[36m",
3931
+ // Cyan
3932
+ medium: "\x1B[33m",
3933
+ // Yellow
3934
+ high: "\x1B[31m",
3935
+ // Red
3936
+ // Other
3937
+ timestamp: "\x1B[90m",
3938
+ // Gray
3939
+ method: "\x1B[34m",
3940
+ // Blue
3941
+ status2xx: "\x1B[32m",
3942
+ // Green
3943
+ status3xx: "\x1B[36m",
3944
+ // Cyan
3945
+ status4xx: "\x1B[33m",
3946
+ // Yellow
3947
+ status5xx: "\x1B[31m"
3948
+ // Red
3949
+ };
3950
+ var LEVEL_PRIORITY = {
3951
+ debug: 0,
3952
+ info: 1,
3953
+ warn: 2,
3954
+ error: 3,
3955
+ critical: 4
3956
+ };
3957
+ var ConsoleStore = class {
3958
+ colorize;
3959
+ showTimestamp;
3960
+ pretty;
3961
+ minLevel;
3962
+ constructor(options = {}) {
3963
+ this.colorize = options.colorize ?? process.env.NODE_ENV !== "production";
3964
+ this.showTimestamp = options.timestamp ?? true;
3965
+ this.pretty = options.pretty ?? false;
3966
+ this.minLevel = options.level || "info";
3967
+ }
3968
+ async write(entry) {
3969
+ if (LEVEL_PRIORITY[entry.level] < LEVEL_PRIORITY[this.minLevel]) {
3970
+ return;
3971
+ }
3972
+ const output = this.pretty ? this.formatPretty(entry) : this.formatCompact(entry);
3973
+ switch (entry.level) {
3974
+ case "debug":
3975
+ console.debug(output);
3976
+ break;
3977
+ case "info":
3978
+ console.info(output);
3979
+ break;
3980
+ case "warn":
3981
+ console.warn(output);
3982
+ break;
3983
+ case "error":
3984
+ case "critical":
3985
+ console.error(output);
3986
+ break;
3987
+ default:
3988
+ console.log(output);
3989
+ }
3990
+ }
3991
+ async flush() {
3992
+ }
3993
+ async close() {
3994
+ }
3995
+ /**
3996
+ * Format entry in compact single-line format
3997
+ */
3998
+ formatCompact(entry) {
3999
+ const parts = [];
4000
+ if (this.showTimestamp) {
4001
+ const ts = entry.timestamp.toISOString();
4002
+ parts.push(this.color(ts, "timestamp"));
4003
+ }
4004
+ parts.push(this.colorLevel(entry.level));
4005
+ if (entry.type === "request") {
4006
+ const req = entry.request;
4007
+ const res = entry.response;
4008
+ parts.push(this.color(req.method, "method"));
4009
+ parts.push(req.path);
4010
+ if (res) {
4011
+ parts.push(this.colorStatus(res.status));
4012
+ parts.push(this.color(`${res.duration}ms`, "dim"));
4013
+ }
4014
+ if (req.ip) {
4015
+ parts.push(this.color(`[${req.ip}]`, "dim"));
4016
+ }
4017
+ if (entry.user?.id) {
4018
+ parts.push(this.color(`user:${entry.user.id}`, "dim"));
4019
+ }
4020
+ if (entry.error) {
4021
+ parts.push(this.color(`ERROR: ${entry.error.message}`, "error"));
4022
+ }
4023
+ } else if (entry.type === "security") {
4024
+ parts.push(this.colorSeverity(entry.severity));
4025
+ parts.push(entry.event);
4026
+ if (entry.source.ip) {
4027
+ parts.push(this.color(`[${entry.source.ip}]`, "dim"));
4028
+ }
4029
+ if (entry.source.userId) {
4030
+ parts.push(this.color(`user:${entry.source.userId}`, "dim"));
4031
+ }
4032
+ parts.push(entry.message);
4033
+ }
4034
+ return parts.join(" ");
4035
+ }
4036
+ /**
4037
+ * Format entry in pretty multi-line format
4038
+ */
4039
+ formatPretty(entry) {
4040
+ const lines = [];
4041
+ const header = [
4042
+ this.color(entry.timestamp.toISOString(), "timestamp"),
4043
+ this.colorLevel(entry.level),
4044
+ `[${entry.type.toUpperCase()}]`
4045
+ ].join(" ");
4046
+ lines.push(header);
4047
+ if (entry.type === "request") {
4048
+ const req = entry.request;
4049
+ const res = entry.response;
4050
+ lines.push(` ${this.color(req.method, "method")} ${req.url}`);
4051
+ if (req.ip) lines.push(` IP: ${req.ip}`);
4052
+ if (req.userAgent) lines.push(` UA: ${req.userAgent}`);
4053
+ if (res) {
4054
+ lines.push(` Status: ${this.colorStatus(res.status)} (${res.duration}ms)`);
4055
+ }
4056
+ if (entry.user) {
4057
+ lines.push(` User: ${JSON.stringify(entry.user)}`);
4058
+ }
4059
+ if (entry.error) {
4060
+ lines.push(` ${this.color("Error:", "error")} ${entry.error.message}`);
4061
+ if (entry.error.stack) {
4062
+ lines.push(` ${this.color(entry.error.stack, "dim")}`);
4063
+ }
4064
+ }
4065
+ } else if (entry.type === "security") {
4066
+ lines.push(` Event: ${entry.event}`);
4067
+ lines.push(` Severity: ${this.colorSeverity(entry.severity)}`);
4068
+ lines.push(` Message: ${entry.message}`);
4069
+ if (entry.source.ip) lines.push(` Source IP: ${entry.source.ip}`);
4070
+ if (entry.source.userId) lines.push(` Source User: ${entry.source.userId}`);
4071
+ if (entry.target) {
4072
+ lines.push(` Target: ${JSON.stringify(entry.target)}`);
4073
+ }
4074
+ if (entry.details) {
4075
+ lines.push(` Details: ${JSON.stringify(entry.details)}`);
4076
+ }
4077
+ }
4078
+ if (entry.metadata && Object.keys(entry.metadata).length > 0) {
4079
+ lines.push(` Metadata: ${JSON.stringify(entry.metadata)}`);
4080
+ }
4081
+ return lines.join("\n");
4082
+ }
4083
+ /**
4084
+ * Apply color if enabled
4085
+ */
4086
+ color(text, colorName) {
4087
+ if (!this.colorize) return text;
4088
+ return `${COLORS[colorName]}${text}${COLORS.reset}`;
4089
+ }
4090
+ /**
4091
+ * Color log level
4092
+ */
4093
+ colorLevel(level) {
4094
+ const text = level.toUpperCase().padEnd(8);
4095
+ if (!this.colorize) return `[${text}]`;
4096
+ return `[${COLORS[level]}${text}${COLORS.reset}]`;
4097
+ }
4098
+ /**
4099
+ * Color HTTP status
4100
+ */
4101
+ colorStatus(status) {
4102
+ const text = status.toString();
4103
+ if (!this.colorize) return text;
4104
+ if (status >= 500) return `${COLORS.status5xx}${text}${COLORS.reset}`;
4105
+ if (status >= 400) return `${COLORS.status4xx}${text}${COLORS.reset}`;
4106
+ if (status >= 300) return `${COLORS.status3xx}${text}${COLORS.reset}`;
4107
+ return `${COLORS.status2xx}${text}${COLORS.reset}`;
4108
+ }
4109
+ /**
4110
+ * Color severity
4111
+ */
4112
+ colorSeverity(severity) {
4113
+ const text = `[${severity.toUpperCase()}]`;
4114
+ if (!this.colorize) return text;
4115
+ const colorKey = severity === "critical" ? "critical" : severity;
4116
+ return `${COLORS[colorKey]}${text}${COLORS.reset}`;
4117
+ }
4118
+ };
4119
+ function createConsoleStore(options) {
4120
+ return new ConsoleStore(options);
4121
+ }
4122
+
4123
+ // src/middleware/audit/stores/external.ts
4124
+ var ExternalStore = class {
4125
+ endpoint;
4126
+ headers;
4127
+ batchSize;
4128
+ flushInterval;
4129
+ retryAttempts;
4130
+ timeout;
4131
+ buffer = [];
4132
+ flushTimer = null;
4133
+ isFlushing = false;
4134
+ constructor(options) {
4135
+ this.endpoint = options.endpoint;
4136
+ this.headers = {
4137
+ "Content-Type": "application/json",
4138
+ ...options.apiKey ? { "Authorization": `Bearer ${options.apiKey}` } : {},
4139
+ ...options.headers
4140
+ };
4141
+ this.batchSize = options.batchSize || 100;
4142
+ this.flushInterval = options.flushInterval || 5e3;
4143
+ this.retryAttempts = options.retryAttempts || 3;
4144
+ this.timeout = options.timeout || 1e4;
4145
+ if (this.flushInterval > 0) {
4146
+ this.flushTimer = setInterval(() => this.flush(), this.flushInterval);
4147
+ }
4148
+ }
4149
+ async write(entry) {
4150
+ this.buffer.push(entry);
4151
+ if (this.buffer.length >= this.batchSize) {
4152
+ await this.flush();
4153
+ }
4154
+ }
4155
+ async flush() {
4156
+ if (this.isFlushing || this.buffer.length === 0) return;
4157
+ this.isFlushing = true;
4158
+ const entries = [...this.buffer];
4159
+ this.buffer = [];
4160
+ try {
4161
+ await this.send(entries);
4162
+ } catch (error) {
4163
+ this.buffer = [...entries, ...this.buffer];
4164
+ console.error("[ExternalStore] Failed to flush logs:", error);
4165
+ } finally {
4166
+ this.isFlushing = false;
4167
+ }
4168
+ }
4169
+ async close() {
4170
+ if (this.flushTimer) {
4171
+ clearInterval(this.flushTimer);
4172
+ this.flushTimer = null;
4173
+ }
4174
+ await this.flush();
4175
+ }
4176
+ /**
4177
+ * Send entries to external endpoint
4178
+ */
4179
+ async send(entries) {
4180
+ let lastError = null;
4181
+ for (let attempt = 0; attempt < this.retryAttempts; attempt++) {
4182
+ try {
4183
+ const controller = new AbortController();
4184
+ const timeoutId = setTimeout(() => controller.abort(), this.timeout);
4185
+ const response = await fetch(this.endpoint, {
4186
+ method: "POST",
4187
+ headers: this.headers,
4188
+ body: JSON.stringify({
4189
+ logs: entries.map((e) => this.serialize(e)),
4190
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
4191
+ count: entries.length
4192
+ }),
4193
+ signal: controller.signal
4194
+ });
4195
+ clearTimeout(timeoutId);
4196
+ if (!response.ok) {
4197
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
4198
+ }
4199
+ return;
4200
+ } catch (error) {
4201
+ lastError = error instanceof Error ? error : new Error(String(error));
4202
+ if (attempt < this.retryAttempts - 1) {
4203
+ await this.sleep(Math.pow(2, attempt) * 1e3);
4204
+ }
4205
+ }
4206
+ }
4207
+ throw lastError || new Error("Failed to send logs");
4208
+ }
4209
+ /**
4210
+ * Serialize entry for transmission
4211
+ */
4212
+ serialize(entry) {
4213
+ return {
4214
+ ...entry,
4215
+ timestamp: entry.timestamp.toISOString()
4216
+ };
4217
+ }
4218
+ /**
4219
+ * Sleep helper
4220
+ */
4221
+ sleep(ms) {
4222
+ return new Promise((resolve) => setTimeout(resolve, ms));
4223
+ }
4224
+ /**
4225
+ * Get buffer size (for monitoring)
4226
+ */
4227
+ getBufferSize() {
4228
+ return this.buffer.length;
4229
+ }
4230
+ };
4231
+ function createExternalStore(options) {
4232
+ return new ExternalStore(options);
4233
+ }
4234
+ function createDatadogStore(options) {
4235
+ const site = options.site || "datadoghq.com";
4236
+ const endpoint = `https://http-intake.logs.${site}/api/v2/logs`;
4237
+ return new ExternalStore({
4238
+ endpoint,
4239
+ headers: {
4240
+ "DD-API-KEY": options.apiKey,
4241
+ "Content-Type": "application/json"
4242
+ },
4243
+ batchSize: options.batchSize || 100,
4244
+ flushInterval: options.flushInterval || 5e3
4245
+ });
4246
+ }
4247
+ var MultiStore = class {
4248
+ stores;
4249
+ constructor(stores) {
4250
+ this.stores = stores;
4251
+ }
4252
+ async write(entry) {
4253
+ await Promise.all(this.stores.map((store) => store.write(entry)));
4254
+ }
4255
+ async query(options) {
4256
+ for (const store of this.stores) {
4257
+ if (store.query) {
4258
+ return store.query(options);
4259
+ }
4260
+ }
4261
+ return [];
4262
+ }
4263
+ async flush() {
4264
+ await Promise.all(this.stores.map((store) => store.flush?.()));
4265
+ }
4266
+ async close() {
4267
+ await Promise.all(this.stores.map((store) => store.close?.()));
4268
+ }
4269
+ };
4270
+ function createMultiStore(stores) {
4271
+ return new MultiStore(stores);
4272
+ }
4273
+
4274
+ // src/middleware/audit/formatters.ts
4275
+ var JSONFormatter = class {
4276
+ pretty;
4277
+ includeTimestamp;
4278
+ constructor(options = {}) {
4279
+ this.pretty = options.pretty ?? false;
4280
+ this.includeTimestamp = options.includeTimestamp ?? true;
4281
+ }
4282
+ format(entry) {
4283
+ const output = {
4284
+ ...entry,
4285
+ timestamp: this.includeTimestamp ? entry.timestamp.toISOString() : void 0
4286
+ };
4287
+ return this.pretty ? JSON.stringify(output, null, 2) : JSON.stringify(output);
4288
+ }
4289
+ };
4290
+ var TextFormatter = class {
4291
+ template;
4292
+ dateFormat;
4293
+ constructor(options = {}) {
4294
+ this.template = options.template || "{timestamp} [{level}] {message}";
4295
+ this.dateFormat = options.dateFormat || "iso";
4296
+ }
4297
+ format(entry) {
4298
+ let output = this.template;
4299
+ output = output.replace("{timestamp}", this.formatDate(entry.timestamp));
4300
+ output = output.replace("{level}", entry.level.toUpperCase().padEnd(8));
4301
+ output = output.replace("{message}", entry.message);
4302
+ output = output.replace("{type}", entry.type);
4303
+ output = output.replace("{id}", entry.id);
4304
+ if (entry.category) {
4305
+ output = output.replace("{category}", entry.category);
4306
+ }
4307
+ if (entry.type === "request") {
4308
+ output = output.replace("{method}", entry.request.method);
4309
+ output = output.replace("{path}", entry.request.path);
4310
+ output = output.replace("{url}", entry.request.url);
4311
+ output = output.replace("{ip}", entry.request.ip || "-");
4312
+ output = output.replace("{status}", entry.response?.status?.toString() || "-");
4313
+ output = output.replace("{duration}", entry.response?.duration?.toString() || "-");
4314
+ }
4315
+ if (entry.type === "security") {
4316
+ output = output.replace("{event}", entry.event);
4317
+ output = output.replace("{severity}", entry.severity);
4318
+ }
4319
+ return output;
4320
+ }
4321
+ formatDate(date) {
4322
+ switch (this.dateFormat) {
4323
+ case "utc":
4324
+ return date.toUTCString();
4325
+ case "local":
4326
+ return date.toLocaleString();
4327
+ case "iso":
4328
+ default:
4329
+ return date.toISOString();
4330
+ }
4331
+ }
4332
+ };
4333
+ var CLFFormatter = class {
4334
+ format(entry) {
4335
+ if (entry.type !== "request") {
4336
+ return `[${entry.timestamp.toISOString()}] ${entry.level.toUpperCase()} ${entry.message}`;
4337
+ }
4338
+ const req = entry.request;
4339
+ const res = entry.response;
4340
+ const host = req.ip || "-";
4341
+ const ident = "-";
4342
+ const authuser = entry.user?.id || "-";
4343
+ const date = this.formatCLFDate(entry.timestamp);
4344
+ const request = `${req.method} ${req.path} HTTP/1.1`;
4345
+ const status = res?.status || 0;
4346
+ const bytes = res?.contentLength || 0;
4347
+ return `${host} ${ident} ${authuser} [${date}] "${request}" ${status} ${bytes}`;
4348
+ }
4349
+ formatCLFDate(date) {
4350
+ const months = [
4351
+ "Jan",
4352
+ "Feb",
4353
+ "Mar",
4354
+ "Apr",
4355
+ "May",
4356
+ "Jun",
4357
+ "Jul",
4358
+ "Aug",
4359
+ "Sep",
4360
+ "Oct",
4361
+ "Nov",
4362
+ "Dec"
4363
+ ];
4364
+ const day = date.getDate().toString().padStart(2, "0");
4365
+ const month = months[date.getMonth()];
4366
+ const year = date.getFullYear();
4367
+ const hours = date.getHours().toString().padStart(2, "0");
4368
+ const minutes = date.getMinutes().toString().padStart(2, "0");
4369
+ const seconds = date.getSeconds().toString().padStart(2, "0");
4370
+ const offset = -date.getTimezoneOffset();
4371
+ const offsetSign = offset >= 0 ? "+" : "-";
4372
+ const offsetHours = Math.floor(Math.abs(offset) / 60).toString().padStart(2, "0");
4373
+ const offsetMins = (Math.abs(offset) % 60).toString().padStart(2, "0");
4374
+ return `${day}/${month}/${year}:${hours}:${minutes}:${seconds} ${offsetSign}${offsetHours}${offsetMins}`;
4375
+ }
4376
+ };
4377
+ var StructuredFormatter = class {
4378
+ delimiter;
4379
+ kvSeparator;
4380
+ constructor(options = {}) {
4381
+ this.delimiter = options.delimiter || " ";
4382
+ this.kvSeparator = options.kvSeparator || "=";
4383
+ }
4384
+ format(entry) {
4385
+ const pairs = [];
4386
+ pairs.push(this.pair("timestamp", entry.timestamp.toISOString()));
4387
+ pairs.push(this.pair("level", entry.level));
4388
+ pairs.push(this.pair("type", entry.type));
4389
+ pairs.push(this.pair("id", entry.id));
4390
+ pairs.push(this.pair("message", this.escape(entry.message)));
4391
+ if (entry.category) {
4392
+ pairs.push(this.pair("category", entry.category));
4393
+ }
4394
+ if (entry.type === "request") {
4395
+ pairs.push(this.pair("method", entry.request.method));
4396
+ pairs.push(this.pair("path", entry.request.path));
4397
+ if (entry.request.ip) pairs.push(this.pair("ip", entry.request.ip));
4398
+ if (entry.response) {
4399
+ pairs.push(this.pair("status", entry.response.status.toString()));
4400
+ pairs.push(this.pair("duration_ms", entry.response.duration.toString()));
4401
+ }
4402
+ if (entry.user?.id) pairs.push(this.pair("user_id", entry.user.id));
4403
+ if (entry.error) {
4404
+ pairs.push(this.pair("error", this.escape(entry.error.message)));
4405
+ }
4406
+ }
4407
+ if (entry.type === "security") {
4408
+ pairs.push(this.pair("event", entry.event));
4409
+ pairs.push(this.pair("severity", entry.severity));
4410
+ if (entry.source.ip) pairs.push(this.pair("source_ip", entry.source.ip));
4411
+ if (entry.source.userId) pairs.push(this.pair("source_user", entry.source.userId));
4412
+ }
4413
+ return pairs.join(this.delimiter);
4414
+ }
4415
+ pair(key, value) {
4416
+ return `${key}${this.kvSeparator}${value}`;
4417
+ }
4418
+ escape(value) {
4419
+ if (value.includes(" ") || value.includes('"')) {
4420
+ return `"${value.replace(/"/g, '\\"')}"`;
4421
+ }
4422
+ return value;
4423
+ }
4424
+ };
4425
+ function createJSONFormatter(options) {
4426
+ return new JSONFormatter(options);
4427
+ }
4428
+ function createTextFormatter(options) {
4429
+ return new TextFormatter(options);
4430
+ }
4431
+ function createCLFFormatter() {
4432
+ return new CLFFormatter();
4433
+ }
4434
+ function createStructuredFormatter(options) {
4435
+ return new StructuredFormatter(options);
4436
+ }
4437
+
4438
+ // src/middleware/audit/redaction.ts
4439
+ var DEFAULT_PII_FIELDS = [
4440
+ // Authentication
4441
+ "password",
4442
+ "passwd",
4443
+ "secret",
4444
+ "token",
4445
+ "api_key",
4446
+ "apiKey",
4447
+ "api-key",
4448
+ "access_token",
4449
+ "accessToken",
4450
+ "refresh_token",
4451
+ "refreshToken",
4452
+ "authorization",
4453
+ "auth",
4454
+ // Personal information
4455
+ "ssn",
4456
+ "social_security",
4457
+ "socialSecurity",
4458
+ "credit_card",
4459
+ "creditCard",
4460
+ "card_number",
4461
+ "cardNumber",
4462
+ "cvv",
4463
+ "cvc",
4464
+ "pin",
4465
+ // Contact
4466
+ "email",
4467
+ "phone",
4468
+ "phone_number",
4469
+ "phoneNumber",
4470
+ "mobile",
4471
+ "address",
4472
+ "street",
4473
+ "zip",
4474
+ "zipcode",
4475
+ "postal_code",
4476
+ "postalCode",
4477
+ // Identity
4478
+ "date_of_birth",
4479
+ "dateOfBirth",
4480
+ "dob",
4481
+ "birth_date",
4482
+ "birthDate",
4483
+ "passport",
4484
+ "license",
4485
+ "national_id",
4486
+ "nationalId"
4487
+ ];
4488
+ function mask(value, options = {}) {
4489
+ const {
4490
+ char = "*",
4491
+ preserveLength = false,
4492
+ showFirst = 0,
4493
+ showLast = 0
4494
+ } = options;
4495
+ if (!value) return value;
4496
+ const len = value.length;
4497
+ if (preserveLength) {
4498
+ const first2 = value.slice(0, showFirst);
4499
+ const last2 = value.slice(-showLast || len);
4500
+ const middle = char.repeat(Math.max(0, len - showFirst - showLast));
4501
+ return first2 + middle + (showLast > 0 ? last2 : "");
4502
+ }
4503
+ const maskLen = 8;
4504
+ const first = showFirst > 0 ? value.slice(0, showFirst) : "";
4505
+ const last = showLast > 0 ? value.slice(-showLast) : "";
4506
+ return first + char.repeat(maskLen) + last;
4507
+ }
4508
+ function hash(value, salt = "") {
4509
+ const str = salt + value;
4510
+ let hash2 = 0;
4511
+ for (let i = 0; i < str.length; i++) {
4512
+ const char = str.charCodeAt(i);
4513
+ hash2 = (hash2 << 5) - hash2 + char;
4514
+ hash2 = hash2 & hash2;
4515
+ }
4516
+ const hex = Math.abs(hash2).toString(16).padStart(8, "0");
4517
+ return hex + hex.slice(0, 8);
4518
+ }
4519
+ function redactValue(value, field, config) {
4520
+ if (typeof value !== "string") return value;
4521
+ if (!value) return value;
4522
+ const shouldRedact = config.fields.some((f) => {
4523
+ const fieldLower = field.toLowerCase();
4524
+ const fLower = f.toLowerCase();
4525
+ return fieldLower === fLower || fieldLower.endsWith("." + fLower) || fieldLower.includes("[" + fLower + "]");
4526
+ });
4527
+ if (!shouldRedact) return value;
4528
+ if (config.customRedactor) {
4529
+ return config.customRedactor(value, field);
4530
+ }
4531
+ switch (config.mode) {
4532
+ case "mask":
4533
+ return mask(value, {
4534
+ char: config.maskChar || "*",
4535
+ preserveLength: config.preserveLength,
4536
+ showFirst: 2,
4537
+ showLast: 2
4538
+ });
4539
+ case "hash":
4540
+ return `[HASH:${hash(value)}]`;
4541
+ case "remove":
4542
+ return "[REDACTED]";
4543
+ default:
4544
+ return "[REDACTED]";
4545
+ }
4546
+ }
4547
+ function redactObject(obj, config, path = "") {
4548
+ if (typeof obj === "string") {
4549
+ return redactValue(obj, path, config);
4550
+ }
4551
+ if (Array.isArray(obj)) {
4552
+ return obj.map((item, i) => redactObject(item, config, `${path}[${i}]`));
4553
+ }
4554
+ if (typeof obj === "object" && obj !== null) {
4555
+ const result = {};
4556
+ for (const [key, value] of Object.entries(obj)) {
4557
+ const newPath = path ? `${path}.${key}` : key;
4558
+ result[key] = redactObject(value, config, newPath);
4559
+ }
4560
+ return result;
4561
+ }
4562
+ return obj;
4563
+ }
4564
+ function createRedactor(config = {}) {
4565
+ const fullConfig = {
4566
+ fields: config.fields || DEFAULT_PII_FIELDS,
4567
+ mode: config.mode || "mask",
4568
+ maskChar: config.maskChar || "*",
4569
+ preserveLength: config.preserveLength || false,
4570
+ customRedactor: config.customRedactor
4571
+ };
4572
+ return (obj) => redactObject(obj, fullConfig);
4573
+ }
4574
+ function redactHeaders(headers, sensitiveHeaders = ["authorization", "cookie", "x-api-key", "x-auth-token"]) {
4575
+ const result = {};
4576
+ for (const [key, value] of Object.entries(headers)) {
4577
+ const keyLower = key.toLowerCase();
4578
+ if (sensitiveHeaders.some((h) => keyLower === h.toLowerCase())) {
4579
+ result[key] = "[REDACTED]";
4580
+ } else {
4581
+ result[key] = value;
4582
+ }
4583
+ }
4584
+ return result;
4585
+ }
4586
+ function redactQuery(query, sensitiveParams = ["token", "key", "secret", "password", "auth"]) {
4587
+ const result = {};
4588
+ for (const [key, value] of Object.entries(query)) {
4589
+ const keyLower = key.toLowerCase();
4590
+ if (sensitiveParams.some((p) => keyLower.includes(p.toLowerCase()))) {
4591
+ result[key] = "[REDACTED]";
4592
+ } else {
4593
+ result[key] = value;
4594
+ }
4595
+ }
4596
+ return result;
4597
+ }
4598
+ function redactEmail(email) {
4599
+ if (!email || !email.includes("@")) return mask(email);
4600
+ const [, domain] = email.split("@");
4601
+ return `****@${domain}`;
4602
+ }
4603
+ function redactCreditCard(cardNumber) {
4604
+ const cleaned = cardNumber.replace(/\D/g, "");
4605
+ if (cleaned.length < 4) return mask(cardNumber);
4606
+ return "**** **** **** " + cleaned.slice(-4);
4607
+ }
4608
+ function redactPhone(phone) {
4609
+ const cleaned = phone.replace(/\D/g, "");
4610
+ if (cleaned.length < 4) return mask(phone);
4611
+ return mask(phone, { preserveLength: true, showLast: 4 });
4612
+ }
4613
+ function redactIP(ip) {
4614
+ if (ip.includes(":")) {
4615
+ const parts2 = ip.split(":");
4616
+ return parts2[0] + ":****:****:****";
4617
+ }
4618
+ const parts = ip.split(".");
4619
+ if (parts.length !== 4) return mask(ip);
4620
+ return `${parts[0]}.${parts[1]}.*.*`;
4621
+ }
4622
+
4623
+ // src/middleware/audit/events.ts
4624
+ function generateId() {
4625
+ return `evt_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 9)}`;
4626
+ }
4627
+ function severityToLevel(severity) {
4628
+ switch (severity) {
4629
+ case "low":
4630
+ return "info";
4631
+ case "medium":
4632
+ return "warn";
4633
+ case "high":
4634
+ return "error";
4635
+ case "critical":
4636
+ return "critical";
4637
+ }
4638
+ }
4639
+ var SecurityEventTracker = class {
4640
+ store;
4641
+ defaultSeverity;
4642
+ onEvent;
4643
+ constructor(config) {
4644
+ this.store = config.store;
4645
+ this.defaultSeverity = config.defaultSeverity || "medium";
4646
+ this.onEvent = config.onEvent;
4647
+ }
4648
+ /**
4649
+ * Track a security event
4650
+ */
4651
+ async track(options) {
4652
+ const severity = options.severity || this.defaultSeverity;
4653
+ const entry = {
4654
+ id: generateId(),
4655
+ timestamp: /* @__PURE__ */ new Date(),
4656
+ type: "security",
4657
+ level: severityToLevel(severity),
4658
+ message: options.message,
4659
+ event: options.event,
4660
+ severity,
4661
+ source: options.source || {},
4662
+ target: options.target,
4663
+ details: options.details,
4664
+ mitigated: options.mitigated,
4665
+ metadata: options.metadata
4666
+ };
4667
+ await this.store.write(entry);
4668
+ if (this.onEvent) {
4669
+ await this.onEvent(entry);
4670
+ }
4671
+ return entry;
4672
+ }
4673
+ // Convenience methods for common events
4674
+ /**
4675
+ * Track failed authentication
4676
+ */
4677
+ async authFailed(options) {
4678
+ return this.track({
4679
+ event: "auth.failed",
4680
+ message: options.reason || "Authentication failed",
4681
+ severity: "medium",
4682
+ source: {
4683
+ ip: options.ip,
4684
+ userAgent: options.userAgent
4685
+ },
4686
+ details: {
4687
+ attemptedEmail: options.email,
4688
+ reason: options.reason
4689
+ },
4690
+ metadata: options.metadata
4691
+ });
4692
+ }
4693
+ /**
4694
+ * Track successful login
4695
+ */
4696
+ async authLogin(options) {
4697
+ return this.track({
4698
+ event: "auth.login",
4699
+ message: `User ${options.userId} logged in`,
4700
+ severity: "low",
4701
+ source: {
4702
+ ip: options.ip,
4703
+ userAgent: options.userAgent,
4704
+ userId: options.userId
4705
+ },
4706
+ details: {
4707
+ method: options.method || "credentials"
4708
+ },
4709
+ metadata: options.metadata
4710
+ });
4711
+ }
4712
+ /**
4713
+ * Track logout
4714
+ */
4715
+ async authLogout(options) {
4716
+ return this.track({
4717
+ event: "auth.logout",
4718
+ message: `User ${options.userId} logged out`,
4719
+ severity: "low",
4720
+ source: {
4721
+ ip: options.ip,
4722
+ userId: options.userId
4723
+ },
4724
+ details: {
4725
+ reason: options.reason || "user"
4726
+ },
4727
+ metadata: options.metadata
4728
+ });
4729
+ }
4730
+ /**
4731
+ * Track permission denied
4732
+ */
4733
+ async permissionDenied(options) {
4734
+ return this.track({
4735
+ event: "auth.permission_denied",
4736
+ message: `Permission denied for ${options.action} on ${options.resource}`,
4737
+ severity: "medium",
4738
+ source: {
4739
+ ip: options.ip,
4740
+ userId: options.userId
4741
+ },
4742
+ target: {
4743
+ resource: options.resource,
4744
+ action: options.action
4745
+ },
4746
+ details: {
4747
+ requiredRole: options.requiredRole
4748
+ },
4749
+ metadata: options.metadata
4750
+ });
4751
+ }
4752
+ /**
4753
+ * Track rate limit exceeded
4754
+ */
4755
+ async rateLimitExceeded(options) {
4756
+ return this.track({
4757
+ event: "ratelimit.exceeded",
4758
+ message: `Rate limit exceeded for ${options.endpoint}`,
4759
+ severity: "medium",
4760
+ source: {
4761
+ ip: options.ip,
4762
+ userId: options.userId
4763
+ },
4764
+ target: {
4765
+ resource: options.endpoint
4766
+ },
4767
+ details: {
4768
+ limit: options.limit,
4769
+ window: options.window
4770
+ },
4771
+ metadata: options.metadata
4772
+ });
4773
+ }
4774
+ /**
4775
+ * Track CSRF validation failure
4776
+ */
4777
+ async csrfInvalid(options) {
4778
+ return this.track({
4779
+ event: "csrf.invalid",
4780
+ message: `CSRF validation failed for ${options.endpoint}`,
4781
+ severity: "high",
4782
+ source: {
4783
+ ip: options.ip,
4784
+ userId: options.userId
4785
+ },
4786
+ target: {
4787
+ resource: options.endpoint
4788
+ },
4789
+ details: {
4790
+ reason: options.reason
4791
+ },
4792
+ metadata: options.metadata
4793
+ });
4794
+ }
4795
+ /**
4796
+ * Track XSS detection
4797
+ */
4798
+ async xssDetected(options) {
4799
+ return this.track({
4800
+ event: "xss.detected",
4801
+ message: `XSS payload detected in ${options.field}`,
4802
+ severity: "high",
4803
+ source: {
4804
+ ip: options.ip,
4805
+ userId: options.userId
4806
+ },
4807
+ target: {
4808
+ resource: options.endpoint
4809
+ },
4810
+ details: {
4811
+ field: options.field,
4812
+ payload: options.payload?.slice(0, 100)
4813
+ // Truncate
4814
+ },
4815
+ mitigated: true,
4816
+ metadata: options.metadata
4817
+ });
4818
+ }
4819
+ /**
4820
+ * Track SQL injection detection
4821
+ */
4822
+ async sqliDetected(options) {
4823
+ return this.track({
4824
+ event: "sqli.detected",
4825
+ message: `SQL injection attempt detected in ${options.field}`,
4826
+ severity: options.severity || "high",
4827
+ source: {
4828
+ ip: options.ip,
4829
+ userId: options.userId
4830
+ },
4831
+ target: {
4832
+ resource: options.endpoint
4833
+ },
4834
+ details: {
4835
+ field: options.field,
4836
+ pattern: options.pattern
4837
+ },
4838
+ mitigated: true,
4839
+ metadata: options.metadata
4840
+ });
4841
+ }
4842
+ /**
4843
+ * Track IP block
4844
+ */
4845
+ async ipBlocked(options) {
4846
+ return this.track({
4847
+ event: "ip.blocked",
4848
+ message: `IP ${options.ip} blocked: ${options.reason}`,
4849
+ severity: "high",
4850
+ source: {
4851
+ ip: options.ip
4852
+ },
4853
+ details: {
4854
+ reason: options.reason,
4855
+ duration: options.duration
4856
+ },
4857
+ metadata: options.metadata
4858
+ });
4859
+ }
4860
+ /**
4861
+ * Track suspicious activity
4862
+ */
4863
+ async suspicious(options) {
4864
+ return this.track({
4865
+ event: "ip.suspicious",
4866
+ message: options.activity,
4867
+ severity: options.severity || "medium",
4868
+ source: {
4869
+ ip: options.ip,
4870
+ userId: options.userId
4871
+ },
4872
+ details: options.details,
4873
+ metadata: options.metadata
4874
+ });
4875
+ }
4876
+ /**
4877
+ * Track custom event
4878
+ */
4879
+ async custom(options) {
4880
+ return this.track({
4881
+ event: "custom",
4882
+ ...options
4883
+ });
4884
+ }
4885
+ };
4886
+ function createSecurityTracker(config) {
4887
+ return new SecurityEventTracker(config);
4888
+ }
4889
+ async function trackSecurityEvent(store, options) {
4890
+ const tracker = new SecurityEventTracker({ store });
4891
+ return tracker.track(options);
4892
+ }
4893
+
4894
+ // src/middleware/audit/middleware.ts
4895
+ function generateRequestId() {
4896
+ return `req_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 9)}`;
4897
+ }
4898
+ function getClientIP(req) {
4899
+ return req.headers.get("x-forwarded-for")?.split(",")[0]?.trim() || req.headers.get("x-real-ip") || req.headers.get("cf-connecting-ip") || void 0;
4900
+ }
4901
+ function headersToRecord(headers, includeHeaders) {
4902
+ if (!includeHeaders) return {};
4903
+ const result = {};
4904
+ if (includeHeaders === true) {
4905
+ headers.forEach((value, key) => {
4906
+ result[key] = value;
4907
+ });
4908
+ } else if (Array.isArray(includeHeaders)) {
4909
+ for (const key of includeHeaders) {
4910
+ const value = headers.get(key);
4911
+ if (value) result[key] = value;
4912
+ }
4913
+ }
4914
+ return result;
4915
+ }
4916
+ function parseQuery(url) {
4917
+ const result = {};
4918
+ try {
4919
+ const urlObj = new URL(url);
4920
+ urlObj.searchParams.forEach((value, key) => {
4921
+ result[key] = value;
4922
+ });
4923
+ } catch {
4924
+ }
4925
+ return result;
4926
+ }
4927
+ function statusToLevel(status) {
4928
+ if (status >= 500) return "error";
4929
+ if (status >= 400) return "warn";
4930
+ return "info";
4931
+ }
4932
+ function shouldSkip(req, status, exclude) {
4933
+ if (!exclude) return false;
4934
+ const url = new URL(req.url);
4935
+ if (exclude.paths?.length) {
4936
+ const matchesPath = exclude.paths.some((pattern) => {
4937
+ if (pattern.includes("*")) {
4938
+ const regex = new RegExp("^" + pattern.replace(/\*/g, ".*") + "$");
4939
+ return regex.test(url.pathname);
4940
+ }
4941
+ return url.pathname === pattern || url.pathname.startsWith(pattern);
4942
+ });
4943
+ if (matchesPath) return true;
4944
+ }
4945
+ if (exclude.methods?.includes(req.method)) {
4946
+ return true;
4947
+ }
4948
+ if (exclude.statusCodes?.includes(status)) {
4949
+ return true;
4950
+ }
4951
+ return false;
4952
+ }
4953
+ function withAuditLog(handler, config) {
4954
+ const {
4955
+ enabled = true,
4956
+ store,
4957
+ include = {},
4958
+ exclude,
4959
+ pii,
4960
+ getUser,
4961
+ requestIdHeader = "x-request-id",
4962
+ generateRequestId: customGenerateId,
4963
+ onError,
4964
+ skip
4965
+ } = config;
4966
+ const includeSettings = {
4967
+ ip: include.ip ?? true,
4968
+ userAgent: include.userAgent ?? true,
4969
+ headers: include.headers ?? false,
4970
+ query: include.query ?? true,
4971
+ body: include.body ?? false,
4972
+ response: include.response ?? true,
4973
+ responseBody: include.responseBody ?? false,
4974
+ duration: include.duration ?? true,
4975
+ user: include.user ?? true
4976
+ };
4977
+ const piiConfig = pii || {
4978
+ fields: DEFAULT_PII_FIELDS,
4979
+ mode: "mask"
4980
+ };
4981
+ return async (req) => {
4982
+ if (!enabled) {
4983
+ return handler(req);
4984
+ }
4985
+ if (skip && await skip(req)) {
4986
+ return handler(req);
4987
+ }
4988
+ const startTime = Date.now();
4989
+ const requestId = req.headers.get(requestIdHeader) || (customGenerateId ? customGenerateId() : generateRequestId());
4990
+ const url = new URL(req.url);
4991
+ let requestInfo = {
4992
+ id: requestId,
4993
+ method: req.method,
4994
+ url: req.url,
4995
+ path: url.pathname
4996
+ };
4997
+ if (includeSettings.ip) {
4998
+ requestInfo.ip = getClientIP(req);
4999
+ }
5000
+ if (includeSettings.userAgent) {
5001
+ requestInfo.userAgent = req.headers.get("user-agent") || void 0;
5002
+ }
5003
+ if (includeSettings.headers) {
5004
+ let headers = headersToRecord(req.headers, includeSettings.headers);
5005
+ headers = redactHeaders(headers);
5006
+ requestInfo.headers = headers;
5007
+ }
5008
+ if (includeSettings.query) {
5009
+ let query = parseQuery(req.url);
5010
+ query = redactQuery(query);
5011
+ requestInfo.query = query;
5012
+ }
5013
+ requestInfo.contentType = req.headers.get("content-type") || void 0;
5014
+ requestInfo.contentLength = parseInt(req.headers.get("content-length") || "0", 10) || void 0;
5015
+ let user;
5016
+ if (includeSettings.user && getUser) {
5017
+ try {
5018
+ user = await getUser(req) || void 0;
5019
+ } catch {
5020
+ }
5021
+ }
5022
+ let response;
5023
+ let error;
5024
+ try {
5025
+ response = await handler(req);
5026
+ } catch (err) {
5027
+ error = err instanceof Error ? err : new Error(String(err));
5028
+ throw err;
5029
+ } finally {
5030
+ const duration = Date.now() - startTime;
5031
+ const status = response?.status || 500;
5032
+ if (!shouldSkip(req, status, exclude)) {
5033
+ const entry = {
5034
+ id: requestId,
5035
+ timestamp: /* @__PURE__ */ new Date(),
5036
+ type: "request",
5037
+ level: error ? "error" : statusToLevel(status),
5038
+ message: `${req.method} ${url.pathname} ${status} ${duration}ms`,
5039
+ request: requestInfo,
5040
+ user
5041
+ };
5042
+ if (includeSettings.response && response) {
5043
+ entry.response = {
5044
+ status: response.status,
5045
+ duration
5046
+ };
5047
+ if (includeSettings.headers) {
5048
+ entry.response.headers = headersToRecord(response.headers, includeSettings.headers);
5049
+ }
5050
+ }
5051
+ if (error) {
5052
+ entry.error = {
5053
+ name: error.name,
5054
+ message: error.message,
5055
+ stack: error.stack
5056
+ };
5057
+ }
5058
+ const redactedEntry = redactObject(entry, piiConfig);
5059
+ try {
5060
+ await store.write(redactedEntry);
5061
+ } catch (writeError) {
5062
+ if (onError) {
5063
+ onError(writeError instanceof Error ? writeError : new Error(String(writeError)), entry);
5064
+ } else {
5065
+ console.error("[AuditLog] Failed to write log:", writeError);
5066
+ }
5067
+ }
5068
+ }
5069
+ }
5070
+ return response;
5071
+ };
5072
+ }
5073
+ function createAuditMiddleware(config) {
5074
+ return (handler) => withAuditLog(handler, config);
5075
+ }
5076
+ function withRequestId(handler, options = {}) {
5077
+ const { headerName = "x-request-id", generateId: generateId2 = generateRequestId } = options;
5078
+ return async (req) => {
5079
+ const requestId = req.headers.get(headerName) || generateId2();
5080
+ const response = await handler(req);
5081
+ const newResponse = new Response(response.body, {
5082
+ status: response.status,
5083
+ statusText: response.statusText,
5084
+ headers: new Headers(response.headers)
5085
+ });
5086
+ newResponse.headers.set(headerName, requestId);
5087
+ return newResponse;
5088
+ };
5089
+ }
5090
+ function withTiming(handler, options = {}) {
5091
+ const { headerName = "x-response-time", log = false } = options;
5092
+ return async (req) => {
5093
+ const start = Date.now();
5094
+ const response = await handler(req);
5095
+ const duration = Date.now() - start;
5096
+ const newResponse = new Response(response.body, {
5097
+ status: response.status,
5098
+ statusText: response.statusText,
5099
+ headers: new Headers(response.headers)
5100
+ });
5101
+ newResponse.headers.set(headerName, `${duration}ms`);
5102
+ if (log) {
5103
+ const url = new URL(req.url);
5104
+ console.log(`${req.method} ${url.pathname} ${response.status} ${duration}ms`);
5105
+ }
5106
+ return newResponse;
5107
+ };
5108
+ }
1449
5109
 
1450
5110
  // src/index.ts
1451
- var VERSION = "0.3.0";
5111
+ var VERSION = "0.6.0";
1452
5112
 
5113
+ exports.AuditMemoryStore = MemoryStore2;
1453
5114
  exports.AuthenticationError = AuthenticationError;
1454
5115
  exports.AuthorizationError = AuthorizationError;
5116
+ exports.CLFFormatter = CLFFormatter;
1455
5117
  exports.ConfigurationError = ConfigurationError;
5118
+ exports.ConsoleStore = ConsoleStore;
1456
5119
  exports.CsrfError = CsrfError;
5120
+ exports.DANGEROUS_EXTENSIONS = DANGEROUS_EXTENSIONS;
5121
+ exports.DEFAULT_PII_FIELDS = DEFAULT_PII_FIELDS;
5122
+ exports.ExternalStore = ExternalStore;
5123
+ exports.JSONFormatter = JSONFormatter;
5124
+ exports.MIME_TYPES = MIME_TYPES;
1457
5125
  exports.MemoryStore = MemoryStore;
5126
+ exports.MultiStore = MultiStore;
1458
5127
  exports.PRESET_API = PRESET_API;
1459
5128
  exports.PRESET_RELAXED = PRESET_RELAXED;
1460
5129
  exports.PRESET_STRICT = PRESET_STRICT;
1461
5130
  exports.RateLimitError = RateLimitError;
1462
5131
  exports.SecureError = SecureError;
5132
+ exports.SecurityEventTracker = SecurityEventTracker;
5133
+ exports.StructuredFormatter = StructuredFormatter;
5134
+ exports.TextFormatter = TextFormatter;
1463
5135
  exports.VERSION = VERSION;
1464
5136
  exports.ValidationError = ValidationError;
1465
5137
  exports.anonymizeIp = anonymizeIp;
@@ -1468,11 +5140,30 @@ exports.buildHSTS = buildHSTS;
1468
5140
  exports.buildPermissionsPolicy = buildPermissionsPolicy;
1469
5141
  exports.checkRateLimit = checkRateLimit;
1470
5142
  exports.clearAllRateLimits = clearAllRateLimits;
5143
+ exports.createAuditMemoryStore = createMemoryStore2;
5144
+ exports.createAuditMiddleware = createAuditMiddleware;
5145
+ exports.createCLFFormatter = createCLFFormatter;
1471
5146
  exports.createCSRFToken = createToken;
5147
+ exports.createConsoleStore = createConsoleStore;
5148
+ exports.createDatadogStore = createDatadogStore;
5149
+ exports.createExternalStore = createExternalStore;
5150
+ exports.createJSONFormatter = createJSONFormatter;
1472
5151
  exports.createMemoryStore = createMemoryStore;
5152
+ exports.createMultiStore = createMultiStore;
1473
5153
  exports.createRateLimiter = createRateLimiter;
5154
+ exports.createRedactor = createRedactor;
1474
5155
  exports.createSecurityHeaders = createSecurityHeaders;
1475
5156
  exports.createSecurityHeadersObject = createSecurityHeadersObject;
5157
+ exports.createSecurityTracker = createSecurityTracker;
5158
+ exports.createStructuredFormatter = createStructuredFormatter;
5159
+ exports.createTextFormatter = createTextFormatter;
5160
+ exports.createValidator = createValidator;
5161
+ exports.decodeJWT = decodeJWT;
5162
+ exports.detectFileType = detectFileType;
5163
+ exports.detectSQLInjection = detectSQLInjection;
5164
+ exports.detectXSS = detectXSS;
5165
+ exports.escapeHtml = escapeHtml;
5166
+ exports.extractBearerToken = extractBearerToken;
1476
5167
  exports.formatDuration = formatDuration;
1477
5168
  exports.generateCSRF = generateCSRF;
1478
5169
  exports.getClientIp = getClientIp;
@@ -1480,22 +5171,67 @@ exports.getGeoInfo = getGeoInfo;
1480
5171
  exports.getGlobalMemoryStore = getGlobalMemoryStore;
1481
5172
  exports.getPreset = getPreset;
1482
5173
  exports.getRateLimitStatus = getRateLimitStatus;
5174
+ exports.hasPathTraversal = hasPathTraversal;
5175
+ exports.hasSQLInjection = hasSQLInjection;
5176
+ exports.isFormRequest = isFormRequest;
5177
+ exports.isJsonRequest = isJsonRequest;
1483
5178
  exports.isLocalhost = isLocalhost;
1484
5179
  exports.isPrivateIp = isPrivateIp;
1485
5180
  exports.isSecureError = isSecureError;
1486
5181
  exports.isValidIp = isValidIp;
5182
+ exports.mask = mask;
1487
5183
  exports.normalizeIp = normalizeIp;
1488
5184
  exports.nowInMs = nowInMs;
1489
5185
  exports.nowInSeconds = nowInSeconds;
1490
5186
  exports.parseDuration = parseDuration;
5187
+ exports.redactCreditCard = redactCreditCard;
5188
+ exports.redactEmail = redactEmail;
5189
+ exports.redactHeaders = redactHeaders;
5190
+ exports.redactIP = redactIP;
5191
+ exports.redactObject = redactObject;
5192
+ exports.redactPhone = redactPhone;
5193
+ exports.redactQuery = redactQuery;
1491
5194
  exports.resetRateLimit = resetRateLimit;
5195
+ exports.sanitize = sanitize;
5196
+ exports.sanitizeFields = sanitizeFields;
5197
+ exports.sanitizeFilename = sanitizeFilename;
5198
+ exports.sanitizeObject = sanitizeObject;
5199
+ exports.sanitizePath = sanitizePath;
5200
+ exports.sanitizeSQLInput = sanitizeSQLInput;
1492
5201
  exports.sleep = sleep;
5202
+ exports.stripHtml = stripHtml;
1493
5203
  exports.toSecureError = toSecureError;
1494
5204
  exports.tokensMatch = tokensMatch;
5205
+ exports.trackSecurityEvent = trackSecurityEvent;
5206
+ exports.validate = validate;
5207
+ exports.validateBody = validateBody;
1495
5208
  exports.validateCSRF = validateCSRF;
5209
+ exports.validateContentType = validateContentType;
5210
+ exports.validateFile = validateFile;
5211
+ exports.validateFiles = validateFiles;
5212
+ exports.validatePath = validatePath;
5213
+ exports.validateQuery = validateQuery;
5214
+ exports.validateRequest = validateRequest;
1496
5215
  exports.verifyCSRFToken = verifyToken;
5216
+ exports.verifyJWT = verifyJWT;
5217
+ exports.withAPIKey = withAPIKey;
5218
+ exports.withAuditLog = withAuditLog;
5219
+ exports.withAuth = withAuth;
1497
5220
  exports.withCSRF = withCSRF;
5221
+ exports.withContentType = withContentType;
5222
+ exports.withFileValidation = withFileValidation;
5223
+ exports.withJWT = withJWT;
5224
+ exports.withOptionalAuth = withOptionalAuth;
1498
5225
  exports.withRateLimit = withRateLimit;
5226
+ exports.withRequestId = withRequestId;
5227
+ exports.withRoles = withRoles;
5228
+ exports.withSQLProtection = withSQLProtection;
5229
+ exports.withSanitization = withSanitization;
5230
+ exports.withSecureValidation = withSecureValidation;
1499
5231
  exports.withSecurityHeaders = withSecurityHeaders;
5232
+ exports.withSession = withSession;
5233
+ exports.withTiming = withTiming;
5234
+ exports.withValidation = withValidation;
5235
+ exports.withXSSProtection = withXSSProtection;
1500
5236
  //# sourceMappingURL=index.cjs.map
1501
5237
  //# sourceMappingURL=index.cjs.map