@arcis/node 1.5.2 → 1.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.
Files changed (66) hide show
  1. package/README.md +48 -7
  2. package/dist/astro/index.js.map +1 -1
  3. package/dist/astro/index.mjs.map +1 -1
  4. package/dist/bun/index.js.map +1 -1
  5. package/dist/bun/index.mjs.map +1 -1
  6. package/dist/core/constants.d.ts +2 -2
  7. package/dist/core/constants.d.ts.map +1 -1
  8. package/dist/core/index.js +19 -1
  9. package/dist/core/index.js.map +1 -1
  10. package/dist/core/index.mjs +19 -1
  11. package/dist/core/index.mjs.map +1 -1
  12. package/dist/fastify/index.js.map +1 -1
  13. package/dist/fastify/index.mjs.map +1 -1
  14. package/dist/hono/index.js.map +1 -1
  15. package/dist/hono/index.mjs.map +1 -1
  16. package/dist/index.d.ts +3 -1
  17. package/dist/index.d.ts.map +1 -1
  18. package/dist/index.js +407 -8
  19. package/dist/index.js.map +1 -1
  20. package/dist/index.mjs +407 -9
  21. package/dist/index.mjs.map +1 -1
  22. package/dist/koa/index.js.map +1 -1
  23. package/dist/koa/index.mjs.map +1 -1
  24. package/dist/logging/index.js.map +1 -1
  25. package/dist/logging/index.mjs.map +1 -1
  26. package/dist/middleware/correlation.d.ts +87 -0
  27. package/dist/middleware/correlation.d.ts.map +1 -0
  28. package/dist/middleware/graphql.d.ts.map +1 -1
  29. package/dist/middleware/index.d.ts +3 -1
  30. package/dist/middleware/index.d.ts.map +1 -1
  31. package/dist/middleware/index.js +366 -8
  32. package/dist/middleware/index.js.map +1 -1
  33. package/dist/middleware/index.mjs +366 -9
  34. package/dist/middleware/index.mjs.map +1 -1
  35. package/dist/middleware/protect.d.ts +32 -0
  36. package/dist/middleware/protect.d.ts.map +1 -1
  37. package/dist/nestjs/index.js +55 -2
  38. package/dist/nestjs/index.js.map +1 -1
  39. package/dist/nestjs/index.mjs +55 -2
  40. package/dist/nestjs/index.mjs.map +1 -1
  41. package/dist/nextjs/index.js.map +1 -1
  42. package/dist/nextjs/index.mjs.map +1 -1
  43. package/dist/nuxt/index.js.map +1 -1
  44. package/dist/nuxt/index.mjs.map +1 -1
  45. package/dist/sanitizers/deserialization.d.ts +30 -0
  46. package/dist/sanitizers/deserialization.d.ts.map +1 -0
  47. package/dist/sanitizers/graphql.d.ts +20 -3
  48. package/dist/sanitizers/graphql.d.ts.map +1 -1
  49. package/dist/sanitizers/index.d.ts +2 -0
  50. package/dist/sanitizers/index.d.ts.map +1 -1
  51. package/dist/sanitizers/index.js +150 -7
  52. package/dist/sanitizers/index.js.map +1 -1
  53. package/dist/sanitizers/index.mjs +149 -8
  54. package/dist/sanitizers/index.mjs.map +1 -1
  55. package/dist/sanitizers/prompt-injection.d.ts.map +1 -1
  56. package/dist/sanitizers/sanitize.d.ts +0 -20
  57. package/dist/sanitizers/sanitize.d.ts.map +1 -1
  58. package/dist/stores/index.js.map +1 -1
  59. package/dist/stores/index.mjs.map +1 -1
  60. package/dist/sveltekit/index.js.map +1 -1
  61. package/dist/sveltekit/index.mjs.map +1 -1
  62. package/dist/validation/index.js +55 -2
  63. package/dist/validation/index.js.map +1 -1
  64. package/dist/validation/index.mjs +55 -2
  65. package/dist/validation/index.mjs.map +1 -1
  66. package/package.json +2 -2
@@ -100,7 +100,16 @@ var SQL_PATTERNS = [
100
100
  /** Time-based blind: PostgreSQL pg_sleep() */
101
101
  /\bpg_sleep\s*\(/gi,
102
102
  /** Time-based blind: MSSQL WAITFOR DELAY */
103
- /\bWAITFOR\s+DELAY\b/gi
103
+ /\bWAITFOR\s+DELAY\b/gi,
104
+ /**
105
+ * Oracle DBMS_* stdlib packages used for time-based blind SQLi
106
+ * (DBMS_LOCK.SLEEP, DBMS_PIPE.RECEIVE_MESSAGE) and other Oracle
107
+ * abuse paths. No legitimate user input contains these. Mirrors
108
+ * `sqli-oracle-dbms-packages` in packages/core/patterns.json —
109
+ * improvements.md §1.1.e Q3. Must stay in sync until Node
110
+ * migrates to patterns.json-at-runtime (planned v1.7).
111
+ */
112
+ /\bDBMS_(?:LOCK|PIPE|UTILITY|XSLPROCESSOR|JAVA|OUTPUT|SCHEDULER)\b/gi
104
113
  ];
105
114
  var PATH_PATTERNS = [
106
115
  /** Unix path traversal */
@@ -138,6 +147,15 @@ var COMMAND_PATTERNS = [
138
147
  /[;&|`]/g,
139
148
  /** Command substitution: $( ... ) — matched as a pair to reduce false positives */
140
149
  /\$\(/g,
150
+ /**
151
+ * POSIX shell IFS-substitution: ${IFS} or ${IFS%??}.
152
+ * Attackers use this to inject spaces past metacharacter filters
153
+ * in payloads like `;cat${IFS}/etc/passwd`. Mirrors
154
+ * `cmdi-ifs-bypass` in packages/core/patterns.json — improvements.md
155
+ * §1.1.e Q5. Must stay in sync until Node migrates to
156
+ * patterns.json-at-runtime (planned v1.7).
157
+ */
158
+ /\$\{IFS(?:%[^}]*)?\}/g,
141
159
  /** URL-encoded control characters (%00-%0F): null, tab, vtab, formfeed, LF, CR */
142
160
  /%0[0-9a-f]/gi
143
161
  ];
@@ -675,6 +693,40 @@ var sanitizeEmailHeader = sanitizeHeaderValue;
675
693
  var detectEmailHeaderInjection = detectHeaderInjection;
676
694
 
677
695
  // src/sanitizers/sanitize.ts
696
+ function multiDecode(value, maxPasses = 4) {
697
+ for (let i = 0; i < maxPasses; i++) {
698
+ const prev = value;
699
+ try {
700
+ value = decodeURIComponent(value);
701
+ } catch {
702
+ }
703
+ value = htmlEntityDecode(value);
704
+ if (value === prev) break;
705
+ }
706
+ return value;
707
+ }
708
+ function htmlEntityDecode(s) {
709
+ s = s.replace(/&#(\d+);/g, (_m, n) => {
710
+ const code = parseInt(n, 10);
711
+ return Number.isFinite(code) && code >= 0 && code <= 1114111 ? String.fromCodePoint(code) : _m;
712
+ });
713
+ s = s.replace(/&#x([0-9a-fA-F]+);/g, (_m, h) => {
714
+ const code = parseInt(h, 16);
715
+ return Number.isFinite(code) && code >= 0 && code <= 1114111 ? String.fromCodePoint(code) : _m;
716
+ });
717
+ const named = {
718
+ "&lt;": "<",
719
+ "&gt;": ">",
720
+ "&amp;": "&",
721
+ "&quot;": '"',
722
+ "&apos;": "'",
723
+ "&nbsp;": " "
724
+ };
725
+ for (const [entity, ch] of Object.entries(named)) {
726
+ s = s.split(entity).join(ch);
727
+ }
728
+ return s;
729
+ }
678
730
  function sanitizeString(value, options = {}) {
679
731
  if (typeof value !== "string") return value;
680
732
  const maxSize = options.maxSize ?? INPUT.DEFAULT_MAX_SIZE;
@@ -682,7 +734,8 @@ function sanitizeString(value, options = {}) {
682
734
  throw new InputTooLargeError(maxSize, value.length);
683
735
  }
684
736
  const reject = options.mode === "reject";
685
- let result = value;
737
+ let result = value.normalize("NFKC");
738
+ result = multiDecode(result);
686
739
  if (options.sql !== false) {
687
740
  if (reject) {
688
741
  if (detectSql(result)) {
@@ -1135,7 +1188,9 @@ function encodeForCss(value) {
1135
1188
  var DEFAULTS = {
1136
1189
  maxDepth: 10,
1137
1190
  maxLength: 1e4,
1138
- blockIntrospection: true
1191
+ blockIntrospection: true,
1192
+ maxAliases: 50,
1193
+ blockFragmentCycles: true
1139
1194
  };
1140
1195
  var INTROSPECTION_PATTERN = /\b__(schema|type|typeKind|directive)\b/;
1141
1196
  function computeDepth(query) {
@@ -1152,28 +1207,114 @@ function computeDepth(query) {
1152
1207
  }
1153
1208
  return max;
1154
1209
  }
1210
+ var ALIAS_PATTERN = /\b([a-zA-Z_][a-zA-Z0-9_]*)\s*:\s*([a-zA-Z_][a-zA-Z0-9_]*)\b/g;
1211
+ var FRAGMENT_DEF_PATTERN = /\bfragment\s+([a-zA-Z_][a-zA-Z0-9_]*)\s+on\s+[a-zA-Z_][a-zA-Z0-9_]*\s*\{/g;
1212
+ var FRAGMENT_SPREAD_PATTERN = /\.\.\.\s*([a-zA-Z_][a-zA-Z0-9_]*)\b/g;
1213
+ function countAliases(query) {
1214
+ let n = 0;
1215
+ ALIAS_PATTERN.lastIndex = 0;
1216
+ while (ALIAS_PATTERN.exec(query) !== null) n++;
1217
+ return n;
1218
+ }
1219
+ function hasFragmentCycle(query) {
1220
+ const deps = /* @__PURE__ */ new Map();
1221
+ FRAGMENT_DEF_PATTERN.lastIndex = 0;
1222
+ let match;
1223
+ while ((match = FRAGMENT_DEF_PATTERN.exec(query)) !== null) {
1224
+ const name = match[1];
1225
+ const bodyStart = match.index + match[0].length;
1226
+ let depth = 1;
1227
+ let i = bodyStart;
1228
+ while (i < query.length && depth > 0) {
1229
+ const ch = query[i];
1230
+ if (ch === "{") depth++;
1231
+ else if (ch === "}") depth--;
1232
+ i++;
1233
+ }
1234
+ const bodyEnd = depth === 0 ? i - 1 : i;
1235
+ const body = query.slice(bodyStart, bodyEnd);
1236
+ const spreads = /* @__PURE__ */ new Set();
1237
+ FRAGMENT_SPREAD_PATTERN.lastIndex = 0;
1238
+ let sm;
1239
+ while ((sm = FRAGMENT_SPREAD_PATTERN.exec(body)) !== null) {
1240
+ spreads.add(sm[1]);
1241
+ }
1242
+ deps.set(name, spreads);
1243
+ }
1244
+ if (deps.size === 0) return false;
1245
+ const WHITE = 0;
1246
+ const GRAY = 1;
1247
+ const BLACK = 2;
1248
+ const color = /* @__PURE__ */ new Map();
1249
+ for (const name of deps.keys()) color.set(name, WHITE);
1250
+ function visit(name) {
1251
+ if (color.get(name) === GRAY) return true;
1252
+ if (color.get(name) === BLACK) return false;
1253
+ if (!deps.has(name)) return false;
1254
+ color.set(name, GRAY);
1255
+ for (const child of deps.get(name)) {
1256
+ if (visit(child)) return true;
1257
+ }
1258
+ color.set(name, BLACK);
1259
+ return false;
1260
+ }
1261
+ for (const name of deps.keys()) {
1262
+ if (visit(name)) return true;
1263
+ }
1264
+ return false;
1265
+ }
1155
1266
  function inspectGraphqlQuery(query, options = {}) {
1156
1267
  const maxDepth = options.maxDepth ?? DEFAULTS.maxDepth;
1157
1268
  const maxLength = options.maxLength ?? DEFAULTS.maxLength;
1158
1269
  const blockIntrospection = options.blockIntrospection ?? DEFAULTS.blockIntrospection;
1270
+ const maxAliases = options.maxAliases ?? DEFAULTS.maxAliases;
1271
+ const blockFragmentCycles = options.blockFragmentCycles ?? DEFAULTS.blockFragmentCycles;
1159
1272
  const length = query.length;
1160
1273
  const depth = computeDepth(query);
1274
+ const aliases = countAliases(query);
1161
1275
  if (depth > maxDepth) {
1162
- return { blocked: true, reason: "depth", depth, length };
1276
+ return { blocked: true, reason: "depth", depth, length, aliases };
1163
1277
  }
1164
1278
  if (blockIntrospection && INTROSPECTION_PATTERN.test(query)) {
1165
- return { blocked: true, reason: "introspection", depth, length };
1279
+ return { blocked: true, reason: "introspection", depth, length, aliases };
1280
+ }
1281
+ if (aliases > maxAliases) {
1282
+ return { blocked: true, reason: "aliases", depth, length, aliases };
1283
+ }
1284
+ if (blockFragmentCycles && hasFragmentCycle(query)) {
1285
+ return { blocked: true, reason: "fragment_cycle", depth, length, aliases };
1166
1286
  }
1167
1287
  if (length > maxLength) {
1168
- return { blocked: true, reason: "length", depth, length };
1288
+ return { blocked: true, reason: "length", depth, length, aliases };
1169
1289
  }
1170
- return { blocked: false, depth, length };
1290
+ return { blocked: false, depth, length, aliases };
1171
1291
  }
1172
1292
  function detectGraphqlAbuse(query, options) {
1173
1293
  if (typeof query !== "string" || query.length === 0) return false;
1174
1294
  return inspectGraphqlQuery(query, options).blocked;
1175
1295
  }
1176
1296
 
1177
- export { createSanitizer, detectCommandInjection, detectEmailHeaderInjection, detectGraphqlAbuse, detectHeaderInjection, detectJsonpInjection, detectLdapInjection, detectNoSqlInjection, detectPathTraversal, detectPii, detectPrototypePollution, detectSql, detectSsti, detectXpathInjection, detectXss, detectXxe, encodeForAttribute, encodeForCss, encodeForHtml, encodeForJs, encodeForUrl, encodeHtmlEntities, getDangerousOperators, getDangerousProtoKeys, inspectGraphqlQuery, isDangerousNoSqlKey, isDangerousProtoKey, isPlainObject, redactObjectPii, redactPii, sanitizeCommand, sanitizeEmailHeader, sanitizeHeaderValue, sanitizeHeaders, sanitizeJsonpCallback, sanitizeLdapDn, sanitizeLdapFilter, sanitizeObject, sanitizePath, sanitizeSql, sanitizeSsti, sanitizeString, sanitizeXpath, sanitizeXss, sanitizeXxe, scanObjectPii, scanPii, scanThreats };
1297
+ // src/sanitizers/deserialization.ts
1298
+ var PICKLE_HEAD = /^\x80[\x02-\x05]/;
1299
+ var RUBY_MARSHAL_HEAD = /^\x04\x08/;
1300
+ var DOTNET_BINFMT_HEAD = /^\x00\x01\x00\x00\x00/;
1301
+ var FASTJSON_AUTOTYPE = /"@type"\s*:\s*"[a-zA-Z_$][\w$.]*"/;
1302
+ var PHP_UNSERIALIZE = /O:\d+:"[a-zA-Z_\\][\w\\]*":\d+:\{/;
1303
+ function detectDeserialization(payload) {
1304
+ if (typeof payload !== "string" || payload.length === 0) {
1305
+ return null;
1306
+ }
1307
+ if (PICKLE_HEAD.test(payload)) return "python_pickle";
1308
+ if (RUBY_MARSHAL_HEAD.test(payload)) return "ruby_marshal";
1309
+ if (DOTNET_BINFMT_HEAD.test(payload)) return "dotnet_binary_formatter";
1310
+ if (FASTJSON_AUTOTYPE.test(payload)) return "java_fastjson";
1311
+ if (PHP_UNSERIALIZE.test(payload)) return "php_unserialize";
1312
+ return null;
1313
+ }
1314
+ function isSerializedPayload(payload) {
1315
+ return detectDeserialization(payload) !== null;
1316
+ }
1317
+
1318
+ export { createSanitizer, detectCommandInjection, detectDeserialization, detectEmailHeaderInjection, detectGraphqlAbuse, detectHeaderInjection, detectJsonpInjection, detectLdapInjection, detectNoSqlInjection, detectPathTraversal, detectPii, detectPrototypePollution, detectSql, detectSsti, detectXpathInjection, detectXss, detectXxe, encodeForAttribute, encodeForCss, encodeForHtml, encodeForJs, encodeForUrl, encodeHtmlEntities, getDangerousOperators, getDangerousProtoKeys, inspectGraphqlQuery, isDangerousNoSqlKey, isDangerousProtoKey, isPlainObject, isSerializedPayload, redactObjectPii, redactPii, sanitizeCommand, sanitizeEmailHeader, sanitizeHeaderValue, sanitizeHeaders, sanitizeJsonpCallback, sanitizeLdapDn, sanitizeLdapFilter, sanitizeObject, sanitizePath, sanitizeSql, sanitizeSsti, sanitizeString, sanitizeXpath, sanitizeXss, sanitizeXxe, scanObjectPii, scanPii, scanThreats };
1178
1319
  //# sourceMappingURL=index.mjs.map
1179
1320
  //# sourceMappingURL=index.mjs.map