@resq-systems/security 1.0.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.
@@ -0,0 +1,487 @@
1
+ import { assertNever } from "@resq-systems/types";
2
+ //#region src/validators.ts
3
+ /**
4
+ * Copyright 2026 ResQ Systems, Inc.
5
+ *
6
+ * Licensed under the Apache License, Version 2.0 (the "License");
7
+ * you may not use this file except in compliance with the License.
8
+ * You may obtain a copy of the License at
9
+ *
10
+ * http://www.apache.org/licenses/LICENSE-2.0
11
+ *
12
+ * Unless required by applicable law or agreed to in writing, software
13
+ * distributed under the License is distributed on an "AS IS" BASIS,
14
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ * See the License for the specific language governing permissions and
16
+ * limitations under the License.
17
+ */
18
+ /**
19
+ * XSS attack patterns
20
+ * Detects script injection, event handlers, and dangerous URIs
21
+ */
22
+ const XSS_PATTERNS = [
23
+ /<script\b/gi,
24
+ /\bon\w+\s*=/gi,
25
+ /javascript\s*:/gi,
26
+ /data\s*:\s*text\/html/gi,
27
+ /data\s*:\s*application\/javascript/gi,
28
+ /expression\s*\(/gi,
29
+ /vbscript\s*:/gi,
30
+ /<iframe\b/gi,
31
+ /<object\b/gi,
32
+ /<embed\b/gi,
33
+ /<style\b/gi,
34
+ /document\s*\.\s*(cookie|domain|write|location)/gi,
35
+ /window\s*\.\s*(location|open|eval)/gi,
36
+ /\beval\s*\(/gi,
37
+ /\bnew\s+Function\s*\(/gi,
38
+ /\.innerHTML\s*=/gi,
39
+ /__proto__/gi,
40
+ /constructor\s*\[/gi
41
+ ];
42
+ /**
43
+ * SQL injection patterns
44
+ * Detects common SQL attack vectors
45
+ */
46
+ const SQL_INJECTION_PATTERNS = [
47
+ /\bUNION\s+(ALL\s+)?SELECT\b/gi,
48
+ /\bDROP\s+(TABLE|DATABASE|INDEX|VIEW)\b/gi,
49
+ /\bDELETE\s+FROM\b/gi,
50
+ /\bTRUNCATE\s+TABLE\b/gi,
51
+ /--\s*$/gm,
52
+ /\/\*[\s\S]*?\*\//g,
53
+ /'\s*OR\s+'[\d\w]+'\s*=\s*'[\d\w]+/gi,
54
+ /'\s*OR\s+\d+\s*=\s*\d+/gi,
55
+ /"\s*OR\s+"[\d\w]+"\s*=\s*"[\d\w]+/gi,
56
+ /1\s*=\s*1/g,
57
+ /;\s*(SELECT|INSERT|UPDATE|DELETE|DROP|EXEC|UNION)/gi,
58
+ /SLEEP\s*\(\s*\d+\s*\)/gi,
59
+ /WAITFOR\s+DELAY/gi,
60
+ /BENCHMARK\s*\(/gi,
61
+ /INFORMATION_SCHEMA/gi,
62
+ /0x[0-9a-f]+/gi,
63
+ /\bEXEC(UTE)?\s*\(/gi,
64
+ /\bxp_\w+/gi
65
+ ];
66
+ /**
67
+ * NoSQL injection patterns
68
+ * Detects MongoDB and other NoSQL attack vectors
69
+ */
70
+ const NOSQL_INJECTION_PATTERNS = [
71
+ /\$(?:gt|gte|lt|lte|ne|eq|in|nin|and|or|not|nor|exists|type|mod|regex|text|where|all|elemMatch|size|slice|expr|jsonSchema|meta)\b/gi,
72
+ /\$where\s*:/gi,
73
+ /\$function\s*:/gi,
74
+ /\{\s*\$[a-z]+\s*:/gi,
75
+ /\[\s*\$[a-z]+\s*\]/gi
76
+ ];
77
+ /**
78
+ * Path traversal patterns
79
+ * Detects directory traversal attacks
80
+ */
81
+ const PATH_TRAVERSAL_PATTERNS = [
82
+ /\.\.[/\\]/g,
83
+ /%2e%2e[%2f%5c]/gi,
84
+ /%252e%252e%252f/gi,
85
+ /\.\.%2f/gi,
86
+ /\.\.%5c/gi,
87
+ /%00/g,
88
+ /\/etc\/passwd/gi,
89
+ /\/etc\/shadow/gi,
90
+ /\/proc\/self/gi,
91
+ /C:\\Windows/gi,
92
+ /C:\\System32/gi
93
+ ];
94
+ /**
95
+ * Homoglyph patterns
96
+ * Detects Unicode characters that look like ASCII but aren't
97
+ * Used in phishing and IDN homograph attacks
98
+ */
99
+ const HOMOGLYPH_MAP = {
100
+ a: [
101
+ "а",
102
+ "ɑ",
103
+ "α",
104
+ "а"
105
+ ],
106
+ c: [
107
+ "с",
108
+ "ϲ",
109
+ "ⅽ"
110
+ ],
111
+ e: [
112
+ "е",
113
+ "ε",
114
+ "ė"
115
+ ],
116
+ o: [
117
+ "о",
118
+ "ο",
119
+ "ᴏ",
120
+ "०"
121
+ ],
122
+ p: ["р", "ρ"],
123
+ s: ["ѕ", "ꜱ"],
124
+ x: ["х", "χ"],
125
+ y: ["у", "γ"],
126
+ B: ["В", "Β"],
127
+ H: ["Н", "Η"],
128
+ K: ["К", "Κ"],
129
+ M: ["М", "Μ"],
130
+ P: ["Р", "Ρ"],
131
+ T: ["Т", "Τ"]
132
+ };
133
+ /**
134
+ * Detect XSS-style payloads (script tags, event handlers, dangerous
135
+ * URI schemes, prototype pollution, …) in a UTF-8 input.
136
+ *
137
+ * Inputs longer than 100 000 characters are truncated before scanning
138
+ * to bound regex evaluation cost and prevent ReDoS on crafted
139
+ * payloads. Returns at most one finding — the regex catalog is
140
+ * exhaustive enough that the first hit is sufficient for a
141
+ * reject-or-sanitize decision.
142
+ *
143
+ * @param input - String to scan.
144
+ * @returns Empty array when nothing matches, or a single
145
+ * {@link ThreatFinding} of type `"xss"`.
146
+ *
147
+ * @example
148
+ * ```ts
149
+ * containsXSSPatterns(`<img src=x onerror="alert(1)">`);
150
+ * // → [{ type: "xss", description: "...", matchedPattern: "onerror=" }]
151
+ * ```
152
+ */
153
+ function containsXSSPatterns(input) {
154
+ const findings = [];
155
+ const bounded = input.length > 1e5 ? input.slice(0, 1e5) : input;
156
+ for (const pattern of XSS_PATTERNS) {
157
+ const match = bounded.match(pattern);
158
+ if (match) {
159
+ findings.push({
160
+ type: "xss",
161
+ description: "Potential cross-site scripting (XSS) detected",
162
+ matchedPattern: match[0].slice(0, 50)
163
+ });
164
+ break;
165
+ }
166
+ }
167
+ return findings;
168
+ }
169
+ /**
170
+ * Detect SQL-injection patterns (UNION SELECT, DROP TABLE,
171
+ * comment-based bypasses, always-true tautologies, stacked queries)
172
+ * in input.
173
+ *
174
+ * **Not a replacement for parameterised queries.** Use this as a
175
+ * defense-in-depth signal in addition to a properly bound prepared
176
+ * statement, never as the only barrier.
177
+ *
178
+ * @param input - String to scan. Truncated at 100 000 characters.
179
+ * @returns Empty array, or one finding of type `"sql_injection"`.
180
+ */
181
+ function containsSQLInjection(input) {
182
+ const findings = [];
183
+ const bounded = input.length > 1e5 ? input.slice(0, 1e5) : input;
184
+ for (const pattern of SQL_INJECTION_PATTERNS) {
185
+ const match = bounded.match(pattern);
186
+ if (match) {
187
+ findings.push({
188
+ type: "sql_injection",
189
+ description: "Potential SQL injection detected",
190
+ matchedPattern: match[0].slice(0, 50)
191
+ });
192
+ break;
193
+ }
194
+ }
195
+ return findings;
196
+ }
197
+ /**
198
+ * Detect NoSQL-injection patterns — Mongo-style operator injection
199
+ * (`$where`, `$ne`, `$regex`), JavaScript-in-query payloads, and
200
+ * structural manipulators that can bypass auth filters in document
201
+ * stores.
202
+ *
203
+ * @param input - String to scan.
204
+ * @returns Empty array, or one finding of type `"nosql_injection"`.
205
+ */
206
+ function containsNoSQLInjection(input) {
207
+ const findings = [];
208
+ for (const pattern of NOSQL_INJECTION_PATTERNS) {
209
+ const match = input.match(pattern);
210
+ if (match) {
211
+ findings.push({
212
+ type: "nosql_injection",
213
+ description: "Potential NoSQL injection detected",
214
+ matchedPattern: match[0].slice(0, 50)
215
+ });
216
+ break;
217
+ }
218
+ }
219
+ return findings;
220
+ }
221
+ /**
222
+ * Detect shell command-injection patterns: command substitution
223
+ * (`$(...)`, backticks), chained dangerous commands (`; rm`, `; curl`,
224
+ * …) and shell-piped exec (`| sh`, `| bash`).
225
+ *
226
+ * **Off by default in {@link detectThreatPatterns}** — these patterns
227
+ * occasionally fire on legitimate user content. Enable explicitly
228
+ * (`checkCommandInjection: true`) only when input flows into a child
229
+ * process or shell.
230
+ *
231
+ * @param input - String to scan. Truncated at 100 000 characters.
232
+ * @returns Empty array, or one finding of type `"command_injection"`.
233
+ */
234
+ function containsCommandInjection(input) {
235
+ const findings = [];
236
+ const dangerousPatterns = [
237
+ /\$\([^)]{1,200}\)/g,
238
+ /`[^`]{1,200}`/g,
239
+ /;\s*(rm|del|cat|wget|curl|nc)\b/gi,
240
+ /\|\s*(sh|bash|cmd)\b/gi
241
+ ];
242
+ const bounded = input.length > 1e5 ? input.slice(0, 1e5) : input;
243
+ for (const pattern of dangerousPatterns) {
244
+ const match = bounded.match(pattern);
245
+ if (match) {
246
+ findings.push({
247
+ type: "command_injection",
248
+ description: "Potential command injection detected",
249
+ matchedPattern: match[0].slice(0, 50)
250
+ });
251
+ break;
252
+ }
253
+ }
254
+ return findings;
255
+ }
256
+ /**
257
+ * Detect path-traversal payloads — `../`, encoded dots, raw absolute
258
+ * paths trying to escape a base directory. Pair with `path.resolve()`
259
+ * + a `startsWith()` containment check on the canonicalised path
260
+ * before reading or writing the file.
261
+ *
262
+ * @param input - String to scan.
263
+ * @returns Empty array, or one finding of type `"path_traversal"`.
264
+ */
265
+ function containsPathTraversal(input) {
266
+ const findings = [];
267
+ for (const pattern of PATH_TRAVERSAL_PATTERNS) {
268
+ const match = input.match(pattern);
269
+ if (match) {
270
+ findings.push({
271
+ type: "path_traversal",
272
+ description: "Potential path traversal attack detected",
273
+ matchedPattern: match[0].slice(0, 50)
274
+ });
275
+ break;
276
+ }
277
+ }
278
+ return findings;
279
+ }
280
+ /**
281
+ * Detect lookalike Unicode characters (Cyrillic / Greek glyphs that
282
+ * render identically to common ASCII letters). The classic phishing
283
+ * trick is `paypaӏ.com` (`ӏ` instead of `l`); this detector catches
284
+ * the building blocks.
285
+ *
286
+ * Use {@link normalizeUnicode} to *replace* homoglyphs with their
287
+ * ASCII equivalents — this function only flags their presence.
288
+ *
289
+ * @param input - String to scan.
290
+ * @returns Empty array, or one finding of type `"homoglyph"` (the
291
+ * first matched lookalike).
292
+ */
293
+ function containsHomoglyphs(input) {
294
+ const findings = [];
295
+ for (const [, homoglyphs] of Object.entries(HOMOGLYPH_MAP)) for (const homoglyph of homoglyphs) if (input.includes(homoglyph)) {
296
+ findings.push({
297
+ type: "homoglyph",
298
+ description: "Suspicious lookalike Unicode character detected",
299
+ matchedPattern: homoglyph
300
+ });
301
+ return findings;
302
+ }
303
+ return findings;
304
+ }
305
+ const DEFAULT_CONFIG = {
306
+ checkXSS: true,
307
+ checkSQLInjection: true,
308
+ checkNoSQLInjection: true,
309
+ checkCommandInjection: false,
310
+ checkPathTraversal: true,
311
+ checkHomoglyphs: true
312
+ };
313
+ /**
314
+ * Run every enabled detector against `input` and aggregate findings.
315
+ *
316
+ * Returns early-but-not-immediately: each individual detector still
317
+ * runs to completion, but each detector returns at most one finding,
318
+ * so the aggregate threats array is small (≤ 6 entries).
319
+ *
320
+ * Non-string inputs (`null`, `undefined`, numbers, …) are treated as
321
+ * safe — wrap caller-side validation around this if you want to
322
+ * reject non-strings.
323
+ *
324
+ * @param input - The candidate string.
325
+ * @param config - Detector toggles. Defaults turn on everything
326
+ * except command-injection.
327
+ * @returns `{ isSafe, threats }`.
328
+ *
329
+ * @example
330
+ * ```ts
331
+ * const result = detectThreatPatterns(req.body.query);
332
+ * if (!result.isSafe) return new Response(getThreatErrorMessage(result), { status: 400 });
333
+ * ```
334
+ */
335
+ function detectThreatPatterns(input, config = DEFAULT_CONFIG) {
336
+ if (!input || typeof input !== "string") return {
337
+ isSafe: true,
338
+ threats: []
339
+ };
340
+ const threats = [];
341
+ if (config.checkXSS !== false) threats.push(...containsXSSPatterns(input));
342
+ if (config.checkSQLInjection !== false) threats.push(...containsSQLInjection(input));
343
+ if (config.checkNoSQLInjection !== false) threats.push(...containsNoSQLInjection(input));
344
+ if (config.checkCommandInjection) threats.push(...containsCommandInjection(input));
345
+ if (config.checkPathTraversal !== false) threats.push(...containsPathTraversal(input));
346
+ if (config.checkHomoglyphs !== false) threats.push(...containsHomoglyphs(input));
347
+ return {
348
+ isSafe: threats.length === 0,
349
+ threats
350
+ };
351
+ }
352
+ /**
353
+ * Boolean shortcut over {@link detectThreatPatterns} — discards the
354
+ * findings list when you only need a yes/no decision.
355
+ *
356
+ * @param input - String to test.
357
+ * @param config - Optional detector toggles.
358
+ * @returns `true` when no detector fires.
359
+ */
360
+ function isSafeInput(input, config) {
361
+ return detectThreatPatterns(input, config).isSafe;
362
+ }
363
+ /**
364
+ * HTML-entity escape `&`, `<`, `>`, `"`, `'`, and `/` for safe
365
+ * insertion into HTML text and attribute contexts.
366
+ *
367
+ * **Limited scope.** This is appropriate for plain text destined for
368
+ * `textContent` or attribute values, not for unfiltered HTML
369
+ * rendering. For rich-text use a vetted sanitizer (DOMPurify on the
370
+ * client, sanitize-html or similar on the server).
371
+ *
372
+ * Returns `""` for non-string or empty input.
373
+ *
374
+ * @param input - Untrusted string.
375
+ * @returns Entity-escaped output safe to interpolate into HTML.
376
+ */
377
+ function sanitizeForDisplay(input) {
378
+ if (!input || typeof input !== "string") return "";
379
+ return input.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#x27;").replace(/\//g, "&#x2F;");
380
+ }
381
+ /**
382
+ * Canonicalise a string for safe equality checks against ASCII.
383
+ *
384
+ * Two-pass:
385
+ * 1. Normalize to NFC (composed form) so combining-character
386
+ * sequences don't compare differently from their pre-composed
387
+ * counterparts.
388
+ * 2. Replace known homoglyphs (Cyrillic `А`, Greek `Ε`, …) with their
389
+ * ASCII equivalents (`A`, `E`, …).
390
+ *
391
+ * Use before storing user-controlled identifiers (usernames, domain
392
+ * names) and before comparing them to a denylist or to each other.
393
+ *
394
+ * Returns `""` for non-string or empty input.
395
+ *
396
+ * @param input - Raw string from an untrusted source.
397
+ * @returns ASCII-normalized, NFC-composed string.
398
+ */
399
+ function normalizeUnicode(input) {
400
+ if (!input || typeof input !== "string") return "";
401
+ let normalized = input.normalize("NFC");
402
+ for (const [ascii, homoglyphs] of Object.entries(HOMOGLYPH_MAP)) for (const homoglyph of homoglyphs) normalized = normalized.replace(new RegExp(homoglyph, "g"), ascii);
403
+ return normalized;
404
+ }
405
+ /**
406
+ * Generic user-facing fallback message. Render this verbatim when a
407
+ * detector fires but you don't want to expose which one. Prefer
408
+ * {@link getThreatErrorMessage} for category-specific messages.
409
+ */
410
+ const THREAT_DETECTED_MESSAGE = "Input contains potentially unsafe content";
411
+ /**
412
+ * Boolean refinement helper for use with `zod.string().refine(...)`,
413
+ * `effect/Schema.filter(...)`, or any predicate-based validator.
414
+ *
415
+ * Equivalent to `isSafeInput(input)` with default config.
416
+ */
417
+ function validateSafeText(input) {
418
+ return isSafeInput(input);
419
+ }
420
+ /**
421
+ * Refinement for human name fields. More permissive than
422
+ * {@link validateSafeText} — allows international letters,
423
+ * combining marks, hyphens, apostrophes, and spaces — but still
424
+ * rejects HTML/SQL/NoSQL injection patterns and homoglyph forgeries.
425
+ *
426
+ * Suitable for first/last/full-name inputs in registration forms.
427
+ *
428
+ * @returns `true` when the name passes both the threat detectors and
429
+ * the name-shape regex.
430
+ */
431
+ function validateSafeName(input) {
432
+ const normalized = input.normalize("NFC");
433
+ if (!isSafeInput(normalized, { checkCommandInjection: false })) return false;
434
+ return /^[\p{L}\p{M}'\-\s.]+$/u.test(normalized);
435
+ }
436
+ /**
437
+ * Refinement for email fields. Combines:
438
+ *
439
+ * 1. RFC-style format check (length-bounded to ≤ 254 chars to
440
+ * prevent ReDoS).
441
+ * 2. XSS / SQL / NoSQL / homoglyph detectors — emails are extremely
442
+ * constrained and should never legitimately contain HTML or query
443
+ * operators.
444
+ *
445
+ * @returns `true` when both checks pass.
446
+ */
447
+ function validateSafeEmail(input) {
448
+ if (input.length > 254) return false;
449
+ if (!/^[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])?)*$/.test(input)) return false;
450
+ return detectThreatPatterns(input, {
451
+ checkXSS: true,
452
+ checkSQLInjection: true,
453
+ checkNoSQLInjection: true,
454
+ checkCommandInjection: false,
455
+ checkPathTraversal: false,
456
+ checkHomoglyphs: true
457
+ }).isSafe;
458
+ }
459
+ /**
460
+ * Map a {@link ThreatDetectionResult} into a user-facing error
461
+ * message string suitable for an HTTP 400 response or form
462
+ * validation error. Returns `""` when the result is safe (so
463
+ * `error || undefined` works).
464
+ *
465
+ * Uses only the **first** finding for the message — exposing every
466
+ * threat type to the user can leak information about the detection
467
+ * rules. For full diagnostics, log `result.threats` server-side
468
+ * rather than returning them.
469
+ */
470
+ function getThreatErrorMessage(result) {
471
+ if (result.isSafe) return "";
472
+ const threat = result.threats[0];
473
+ if (!threat) return THREAT_DETECTED_MESSAGE;
474
+ switch (threat.type) {
475
+ case "xss": return "Input contains potentially malicious script content";
476
+ case "sql_injection": return "Input contains potentially malicious database commands";
477
+ case "nosql_injection": return "Input contains potentially malicious query operators";
478
+ case "command_injection": return "Input contains potentially malicious system commands";
479
+ case "path_traversal": return "Input contains potentially malicious file path characters";
480
+ case "homoglyph": return "Input contains suspicious lookalike characters";
481
+ default: return assertNever(threat.type);
482
+ }
483
+ }
484
+ //#endregion
485
+ export { THREAT_DETECTED_MESSAGE, containsCommandInjection, containsHomoglyphs, containsNoSQLInjection, containsPathTraversal, containsSQLInjection, containsXSSPatterns, detectThreatPatterns, getThreatErrorMessage, isSafeInput, normalizeUnicode, sanitizeForDisplay, validateSafeEmail, validateSafeName, validateSafeText };
486
+
487
+ //# sourceMappingURL=validators.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validators.mjs","names":[],"sources":["../src/validators.ts"],"sourcesContent":["/**\n * Copyright 2026 ResQ Systems, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { assertNever } from \"@resq-systems/types\";\n\n// ============================================\n// Threat Pattern Definitions\n// ============================================\n\n/**\n * XSS attack patterns\n * Detects script injection, event handlers, and dangerous URIs\n */\nconst XSS_PATTERNS = [\n\t// Script tags (opening only — avoids ReDoS from greedy cross-tag matching)\n\t/<script\\b/gi,\n\t// Event handlers\n\t/\\bon\\w+\\s*=/gi,\n\t// JavaScript URIs\n\t/javascript\\s*:/gi,\n\t// Data URIs with script content\n\t/data\\s*:\\s*text\\/html/gi,\n\t/data\\s*:\\s*application\\/javascript/gi,\n\t// Expression evaluation\n\t/expression\\s*\\(/gi,\n\t// VBScript\n\t/vbscript\\s*:/gi,\n\t// Iframe injection\n\t/<iframe\\b/gi,\n\t// Object/embed injection\n\t/<object\\b/gi,\n\t/<embed\\b/gi,\n\t// Style-based attacks\n\t/<style\\b/gi,\n\t// Document manipulation\n\t/document\\s*\\.\\s*(cookie|domain|write|location)/gi,\n\t// Window manipulation\n\t/window\\s*\\.\\s*(location|open|eval)/gi,\n\t// Eval and Function constructor\n\t/\\beval\\s*\\(/gi,\n\t/\\bnew\\s+Function\\s*\\(/gi,\n\t// innerHTML manipulation\n\t/\\.innerHTML\\s*=/gi,\n\t// Prototype pollution\n\t/__proto__/gi,\n\t/constructor\\s*\\[/gi,\n];\n\n/**\n * SQL injection patterns\n * Detects common SQL attack vectors\n */\nconst SQL_INJECTION_PATTERNS = [\n\t// UNION-based injection\n\t/\\bUNION\\s+(ALL\\s+)?SELECT\\b/gi,\n\t// DROP/DELETE/TRUNCATE attacks\n\t/\\bDROP\\s+(TABLE|DATABASE|INDEX|VIEW)\\b/gi,\n\t/\\bDELETE\\s+FROM\\b/gi,\n\t/\\bTRUNCATE\\s+TABLE\\b/gi,\n\t// Comment-based attacks\n\t/--\\s*$/gm,\n\t/\\/\\*[\\s\\S]*?\\*\\//g,\n\t// Always-true conditions\n\t/'\\s*OR\\s+'[\\d\\w]+'\\s*=\\s*'[\\d\\w]+/gi,\n\t/'\\s*OR\\s+\\d+\\s*=\\s*\\d+/gi,\n\t/\"\\s*OR\\s+\"[\\d\\w]+\"\\s*=\\s*\"[\\d\\w]+/gi,\n\t/1\\s*=\\s*1/g,\n\t// Stacked queries\n\t/;\\s*(SELECT|INSERT|UPDATE|DELETE|DROP|EXEC|UNION)/gi,\n\t// Time-based blind injection\n\t/SLEEP\\s*\\(\\s*\\d+\\s*\\)/gi,\n\t/WAITFOR\\s+DELAY/gi,\n\t/BENCHMARK\\s*\\(/gi,\n\t// Information schema access\n\t/INFORMATION_SCHEMA/gi,\n\t// Hex encoding bypass\n\t/0x[0-9a-f]+/gi,\n\t// EXEC/EXECUTE\n\t/\\bEXEC(UTE)?\\s*\\(/gi,\n\t// xp_ procedures (SQL Server)\n\t/\\bxp_\\w+/gi,\n];\n\n/**\n * NoSQL injection patterns\n * Detects MongoDB and other NoSQL attack vectors\n */\nconst NOSQL_INJECTION_PATTERNS = [\n\t// MongoDB operators\n\t/\\$(?:gt|gte|lt|lte|ne|eq|in|nin|and|or|not|nor|exists|type|mod|regex|text|where|all|elemMatch|size|slice|expr|jsonSchema|meta)\\b/gi,\n\t// JavaScript execution in MongoDB\n\t/\\$where\\s*:/gi,\n\t/\\$function\\s*:/gi,\n\t// Operator injection\n\t/\\{\\s*\\$[a-z]+\\s*:/gi,\n\t// Array injection\n\t/\\[\\s*\\$[a-z]+\\s*\\]/gi,\n];\n\n/**\n * Path traversal patterns\n * Detects directory traversal attacks\n */\nconst PATH_TRAVERSAL_PATTERNS = [\n\t// Directory traversal\n\t/\\.\\.[/\\\\]/g,\n\t// URL-encoded traversal\n\t/%2e%2e[%2f%5c]/gi,\n\t/%252e%252e%252f/gi,\n\t// Double-encoded\n\t/\\.\\.%2f/gi,\n\t/\\.\\.%5c/gi,\n\t// Null byte injection\n\t/%00/g,\n\t// Common sensitive paths\n\t/\\/etc\\/passwd/gi,\n\t/\\/etc\\/shadow/gi,\n\t/\\/proc\\/self/gi,\n\t/C:\\\\Windows/gi,\n\t/C:\\\\System32/gi,\n];\n\n/**\n * Homoglyph patterns\n * Detects Unicode characters that look like ASCII but aren't\n * Used in phishing and IDN homograph attacks\n */\nconst HOMOGLYPH_MAP: Record<string, string[]> = {\n\ta: [\"а\", \"ɑ\", \"α\", \"а\"], // Cyrillic а, Latin alpha, Greek alpha\n\tc: [\"с\", \"ϲ\", \"ⅽ\"], // Cyrillic с, Greek lunate sigma\n\te: [\"е\", \"ε\", \"ė\"], // Cyrillic е, Greek epsilon\n\to: [\"о\", \"ο\", \"ᴏ\", \"०\"], // Cyrillic о, Greek omicron\n\tp: [\"р\", \"ρ\"], // Cyrillic р, Greek rho\n\ts: [\"ѕ\", \"ꜱ\"], // Cyrillic ѕ\n\tx: [\"х\", \"χ\"], // Cyrillic х, Greek chi\n\ty: [\"у\", \"γ\"], // Cyrillic у, Greek gamma\n\tB: [\"В\", \"Β\"], // Cyrillic В, Greek Beta\n\tH: [\"Н\", \"Η\"], // Cyrillic Н, Greek Eta\n\tK: [\"К\", \"Κ\"], // Cyrillic К, Greek Kappa\n\tM: [\"М\", \"Μ\"], // Cyrillic М, Greek Mu\n\tP: [\"Р\", \"Ρ\"], // Cyrillic Р, Greek Rho\n\tT: [\"Т\", \"Τ\"], // Cyrillic Т, Greek Tau\n};\n\n// ============================================\n// Detection Functions\n// ============================================\n\n/**\n * Outcome of {@link detectThreatPatterns}.\n *\n * `isSafe` is the boolean shortcut; `threats` is the full list of\n * findings (one per detector that fired). Use\n * {@link getThreatErrorMessage} to render a user-facing message for\n * the first finding.\n */\nexport interface ThreatDetectionResult {\n\t/** `true` when no detectors fired. Equivalent to `threats.length === 0`. */\n\tisSafe: boolean;\n\t/** All findings produced by enabled detectors, in detector order. */\n\tthreats: ThreatFinding[];\n}\n\n/**\n * A single detector hit. Detectors that fire return at most one\n * finding per call (one example is enough to reject the input).\n */\nexport interface ThreatFinding {\n\t/** Which detector matched. */\n\ttype: ThreatType;\n\t/** Human-readable description suitable for log lines (not for end users — use {@link getThreatErrorMessage} instead). */\n\tdescription: string;\n\t/** First 50 chars of the matching substring, for diagnostics. Truncated to prevent leaking large payloads in logs. */\n\tmatchedPattern?: string;\n}\n\n/**\n * The closed set of threat categories the validators recognize. Add\n * new categories here when adding a new detector.\n */\nexport type ThreatType =\n\t| \"xss\"\n\t| \"sql_injection\"\n\t| \"nosql_injection\"\n\t| \"command_injection\"\n\t| \"path_traversal\"\n\t| \"homoglyph\";\n\n/**\n * Detect XSS-style payloads (script tags, event handlers, dangerous\n * URI schemes, prototype pollution, …) in a UTF-8 input.\n *\n * Inputs longer than 100 000 characters are truncated before scanning\n * to bound regex evaluation cost and prevent ReDoS on crafted\n * payloads. Returns at most one finding — the regex catalog is\n * exhaustive enough that the first hit is sufficient for a\n * reject-or-sanitize decision.\n *\n * @param input - String to scan.\n * @returns Empty array when nothing matches, or a single\n * {@link ThreatFinding} of type `\"xss\"`.\n *\n * @example\n * ```ts\n * containsXSSPatterns(`<img src=x onerror=\"alert(1)\">`);\n * // → [{ type: \"xss\", description: \"...\", matchedPattern: \"onerror=\" }]\n * ```\n */\nexport function containsXSSPatterns(input: string): ThreatFinding[] {\n\tconst findings: ThreatFinding[] = [];\n\t// Limit input length to prevent ReDoS on crafted payloads\n\tconst bounded = input.length > 100_000 ? input.slice(0, 100_000) : input;\n\n\tfor (const pattern of XSS_PATTERNS) {\n\t\tconst match = bounded.match(pattern);\n\t\tif (match) {\n\t\t\tfindings.push({\n\t\t\t\ttype: \"xss\",\n\t\t\t\tdescription: \"Potential cross-site scripting (XSS) detected\",\n\t\t\t\tmatchedPattern: match[0].slice(0, 50),\n\t\t\t});\n\t\t\tbreak; // One finding per type is enough\n\t\t}\n\t}\n\n\treturn findings;\n}\n\n/**\n * Detect SQL-injection patterns (UNION SELECT, DROP TABLE,\n * comment-based bypasses, always-true tautologies, stacked queries)\n * in input.\n *\n * **Not a replacement for parameterised queries.** Use this as a\n * defense-in-depth signal in addition to a properly bound prepared\n * statement, never as the only barrier.\n *\n * @param input - String to scan. Truncated at 100 000 characters.\n * @returns Empty array, or one finding of type `\"sql_injection\"`.\n */\nexport function containsSQLInjection(input: string): ThreatFinding[] {\n\tconst findings: ThreatFinding[] = [];\n\tconst bounded = input.length > 100_000 ? input.slice(0, 100_000) : input;\n\n\tfor (const pattern of SQL_INJECTION_PATTERNS) {\n\t\tconst match = bounded.match(pattern);\n\t\tif (match) {\n\t\t\tfindings.push({\n\t\t\t\ttype: \"sql_injection\",\n\t\t\t\tdescription: \"Potential SQL injection detected\",\n\t\t\t\tmatchedPattern: match[0].slice(0, 50),\n\t\t\t});\n\t\t\tbreak;\n\t\t}\n\t}\n\n\treturn findings;\n}\n\n/**\n * Detect NoSQL-injection patterns — Mongo-style operator injection\n * (`$where`, `$ne`, `$regex`), JavaScript-in-query payloads, and\n * structural manipulators that can bypass auth filters in document\n * stores.\n *\n * @param input - String to scan.\n * @returns Empty array, or one finding of type `\"nosql_injection\"`.\n */\nexport function containsNoSQLInjection(input: string): ThreatFinding[] {\n\tconst findings: ThreatFinding[] = [];\n\n\tfor (const pattern of NOSQL_INJECTION_PATTERNS) {\n\t\tconst match = input.match(pattern);\n\t\tif (match) {\n\t\t\tfindings.push({\n\t\t\t\ttype: \"nosql_injection\",\n\t\t\t\tdescription: \"Potential NoSQL injection detected\",\n\t\t\t\tmatchedPattern: match[0].slice(0, 50),\n\t\t\t});\n\t\t\tbreak;\n\t\t}\n\t}\n\n\treturn findings;\n}\n\n/**\n * Detect shell command-injection patterns: command substitution\n * (`$(...)`, backticks), chained dangerous commands (`; rm`, `; curl`,\n * …) and shell-piped exec (`| sh`, `| bash`).\n *\n * **Off by default in {@link detectThreatPatterns}** — these patterns\n * occasionally fire on legitimate user content. Enable explicitly\n * (`checkCommandInjection: true`) only when input flows into a child\n * process or shell.\n *\n * @param input - String to scan. Truncated at 100 000 characters.\n * @returns Empty array, or one finding of type `\"command_injection\"`.\n */\nexport function containsCommandInjection(input: string): ThreatFinding[] {\n\tconst findings: ThreatFinding[] = [];\n\n\t// Only check for the most dangerous patterns, not all shell chars\n\tconst dangerousPatterns = [\n\t\t/\\$\\([^)]{1,200}\\)/g, // Command substitution (bounded)\n\t\t/`[^`]{1,200}`/g, // Backtick command substitution (bounded)\n\t\t/;\\s*(rm|del|cat|wget|curl|nc)\\b/gi, // Chained dangerous commands\n\t\t/\\|\\s*(sh|bash|cmd)\\b/gi, // Piped to shell\n\t];\n\n\tconst bounded = input.length > 100_000 ? input.slice(0, 100_000) : input;\n\n\tfor (const pattern of dangerousPatterns) {\n\t\tconst match = bounded.match(pattern);\n\t\tif (match) {\n\t\t\tfindings.push({\n\t\t\t\ttype: \"command_injection\",\n\t\t\t\tdescription: \"Potential command injection detected\",\n\t\t\t\tmatchedPattern: match[0].slice(0, 50),\n\t\t\t});\n\t\t\tbreak;\n\t\t}\n\t}\n\n\treturn findings;\n}\n\n/**\n * Detect path-traversal payloads — `../`, encoded dots, raw absolute\n * paths trying to escape a base directory. Pair with `path.resolve()`\n * + a `startsWith()` containment check on the canonicalised path\n * before reading or writing the file.\n *\n * @param input - String to scan.\n * @returns Empty array, or one finding of type `\"path_traversal\"`.\n */\nexport function containsPathTraversal(input: string): ThreatFinding[] {\n\tconst findings: ThreatFinding[] = [];\n\n\tfor (const pattern of PATH_TRAVERSAL_PATTERNS) {\n\t\tconst match = input.match(pattern);\n\t\tif (match) {\n\t\t\tfindings.push({\n\t\t\t\ttype: \"path_traversal\",\n\t\t\t\tdescription: \"Potential path traversal attack detected\",\n\t\t\t\tmatchedPattern: match[0].slice(0, 50),\n\t\t\t});\n\t\t\tbreak;\n\t\t}\n\t}\n\n\treturn findings;\n}\n\n/**\n * Detect lookalike Unicode characters (Cyrillic / Greek glyphs that\n * render identically to common ASCII letters). The classic phishing\n * trick is `paypaӏ.com` (`ӏ` instead of `l`); this detector catches\n * the building blocks.\n *\n * Use {@link normalizeUnicode} to *replace* homoglyphs with their\n * ASCII equivalents — this function only flags their presence.\n *\n * @param input - String to scan.\n * @returns Empty array, or one finding of type `\"homoglyph\"` (the\n * first matched lookalike).\n */\nexport function containsHomoglyphs(input: string): ThreatFinding[] {\n\tconst findings: ThreatFinding[] = [];\n\n\tfor (const [, homoglyphs] of Object.entries(HOMOGLYPH_MAP)) {\n\t\tfor (const homoglyph of homoglyphs) {\n\t\t\tif (input.includes(homoglyph)) {\n\t\t\t\tfindings.push({\n\t\t\t\t\ttype: \"homoglyph\",\n\t\t\t\t\tdescription: \"Suspicious lookalike Unicode character detected\",\n\t\t\t\t\tmatchedPattern: homoglyph,\n\t\t\t\t});\n\t\t\t\treturn findings; // One finding is enough\n\t\t\t}\n\t\t}\n\t}\n\n\treturn findings;\n}\n\n// ============================================\n// Main Validation Functions\n// ============================================\n\n/**\n * Per-detector toggles for {@link detectThreatPatterns}.\n *\n * Defaults: XSS, SQL, NoSQL, path-traversal, and homoglyph detectors\n * are **on**; command injection is **off** (false-positive prone).\n * Pass `false` to disable a detector or `true` to force-enable\n * `checkCommandInjection`.\n */\nexport interface ThreatDetectionConfig {\n\t/** Default `true`. */\n\tcheckXSS?: boolean;\n\t/** Default `true`. */\n\tcheckSQLInjection?: boolean;\n\t/** Default `true`. */\n\tcheckNoSQLInjection?: boolean;\n\t/** Default `false` — opt in only when input reaches a shell. */\n\tcheckCommandInjection?: boolean;\n\t/** Default `true`. */\n\tcheckPathTraversal?: boolean;\n\t/** Default `true`. */\n\tcheckHomoglyphs?: boolean;\n}\n\nconst DEFAULT_CONFIG: ThreatDetectionConfig = {\n\tcheckXSS: true,\n\tcheckSQLInjection: true,\n\tcheckNoSQLInjection: true,\n\tcheckCommandInjection: false, // Off by default, can cause false positives\n\tcheckPathTraversal: true,\n\tcheckHomoglyphs: true,\n};\n\n/**\n * Run every enabled detector against `input` and aggregate findings.\n *\n * Returns early-but-not-immediately: each individual detector still\n * runs to completion, but each detector returns at most one finding,\n * so the aggregate threats array is small (≤ 6 entries).\n *\n * Non-string inputs (`null`, `undefined`, numbers, …) are treated as\n * safe — wrap caller-side validation around this if you want to\n * reject non-strings.\n *\n * @param input - The candidate string.\n * @param config - Detector toggles. Defaults turn on everything\n * except command-injection.\n * @returns `{ isSafe, threats }`.\n *\n * @example\n * ```ts\n * const result = detectThreatPatterns(req.body.query);\n * if (!result.isSafe) return new Response(getThreatErrorMessage(result), { status: 400 });\n * ```\n */\nexport function detectThreatPatterns(\n\tinput: string,\n\tconfig: ThreatDetectionConfig = DEFAULT_CONFIG,\n): ThreatDetectionResult {\n\tif (!input || typeof input !== \"string\") {\n\t\treturn { isSafe: true, threats: [] };\n\t}\n\n\tconst threats: ThreatFinding[] = [];\n\n\tif (config.checkXSS !== false) {\n\t\tthreats.push(...containsXSSPatterns(input));\n\t}\n\n\tif (config.checkSQLInjection !== false) {\n\t\tthreats.push(...containsSQLInjection(input));\n\t}\n\n\tif (config.checkNoSQLInjection !== false) {\n\t\tthreats.push(...containsNoSQLInjection(input));\n\t}\n\n\tif (config.checkCommandInjection) {\n\t\tthreats.push(...containsCommandInjection(input));\n\t}\n\n\tif (config.checkPathTraversal !== false) {\n\t\tthreats.push(...containsPathTraversal(input));\n\t}\n\n\tif (config.checkHomoglyphs !== false) {\n\t\tthreats.push(...containsHomoglyphs(input));\n\t}\n\n\treturn {\n\t\tisSafe: threats.length === 0,\n\t\tthreats,\n\t};\n}\n\n/**\n * Boolean shortcut over {@link detectThreatPatterns} — discards the\n * findings list when you only need a yes/no decision.\n *\n * @param input - String to test.\n * @param config - Optional detector toggles.\n * @returns `true` when no detector fires.\n */\nexport function isSafeInput(input: string, config?: ThreatDetectionConfig): boolean {\n\treturn detectThreatPatterns(input, config).isSafe;\n}\n\n/**\n * HTML-entity escape `&`, `<`, `>`, `\"`, `'`, and `/` for safe\n * insertion into HTML text and attribute contexts.\n *\n * **Limited scope.** This is appropriate for plain text destined for\n * `textContent` or attribute values, not for unfiltered HTML\n * rendering. For rich-text use a vetted sanitizer (DOMPurify on the\n * client, sanitize-html or similar on the server).\n *\n * Returns `\"\"` for non-string or empty input.\n *\n * @param input - Untrusted string.\n * @returns Entity-escaped output safe to interpolate into HTML.\n */\nexport function sanitizeForDisplay(input: string): string {\n\tif (!input || typeof input !== \"string\") return \"\";\n\n\treturn input\n\t\t.replace(/&/g, \"&amp;\")\n\t\t.replace(/</g, \"&lt;\")\n\t\t.replace(/>/g, \"&gt;\")\n\t\t.replace(/\"/g, \"&quot;\")\n\t\t.replace(/'/g, \"&#x27;\")\n\t\t.replace(/\\//g, \"&#x2F;\");\n}\n\n/**\n * Canonicalise a string for safe equality checks against ASCII.\n *\n * Two-pass:\n * 1. Normalize to NFC (composed form) so combining-character\n * sequences don't compare differently from their pre-composed\n * counterparts.\n * 2. Replace known homoglyphs (Cyrillic `А`, Greek `Ε`, …) with their\n * ASCII equivalents (`A`, `E`, …).\n *\n * Use before storing user-controlled identifiers (usernames, domain\n * names) and before comparing them to a denylist or to each other.\n *\n * Returns `\"\"` for non-string or empty input.\n *\n * @param input - Raw string from an untrusted source.\n * @returns ASCII-normalized, NFC-composed string.\n */\nexport function normalizeUnicode(input: string): string {\n\tif (!input || typeof input !== \"string\") return \"\";\n\n\t// Normalize to NFC (composed form)\n\tlet normalized = input.normalize(\"NFC\");\n\n\t// Replace known homoglyphs with ASCII equivalents\n\tfor (const [ascii, homoglyphs] of Object.entries(HOMOGLYPH_MAP)) {\n\t\tfor (const homoglyph of homoglyphs) {\n\t\t\tnormalized = normalized.replace(new RegExp(homoglyph, \"g\"), ascii);\n\t\t}\n\t}\n\n\treturn normalized;\n}\n\n// ============================================\n// Zod Validation Helpers\n// ============================================\n\n/**\n * Generic user-facing fallback message. Render this verbatim when a\n * detector fires but you don't want to expose which one. Prefer\n * {@link getThreatErrorMessage} for category-specific messages.\n */\nexport const THREAT_DETECTED_MESSAGE = \"Input contains potentially unsafe content\";\n\n/**\n * Boolean refinement helper for use with `zod.string().refine(...)`,\n * `effect/Schema.filter(...)`, or any predicate-based validator.\n *\n * Equivalent to `isSafeInput(input)` with default config.\n */\nexport function validateSafeText(input: string): boolean {\n\treturn isSafeInput(input);\n}\n\n/**\n * Refinement for human name fields. More permissive than\n * {@link validateSafeText} — allows international letters,\n * combining marks, hyphens, apostrophes, and spaces — but still\n * rejects HTML/SQL/NoSQL injection patterns and homoglyph forgeries.\n *\n * Suitable for first/last/full-name inputs in registration forms.\n *\n * @returns `true` when the name passes both the threat detectors and\n * the name-shape regex.\n */\nexport function validateSafeName(input: string): boolean {\n\t// Normalize first to handle combining characters\n\tconst normalized = input.normalize(\"NFC\");\n\n\t// Names shouldn't contain HTML or script patterns\n\tif (!isSafeInput(normalized, { checkCommandInjection: false })) {\n\t\treturn false;\n\t}\n\n\t// Additional check: names should be primarily letters, spaces, hyphens, apostrophes\n\t// This allows international names while blocking obvious injection attempts\n\tconst namePattern = /^[\\p{L}\\p{M}'\\-\\s.]+$/u;\n\treturn namePattern.test(normalized);\n}\n\n/**\n * Refinement for email fields. Combines:\n *\n * 1. RFC-style format check (length-bounded to ≤ 254 chars to\n * prevent ReDoS).\n * 2. XSS / SQL / NoSQL / homoglyph detectors — emails are extremely\n * constrained and should never legitimately contain HTML or query\n * operators.\n *\n * @returns `true` when both checks pass.\n */\nexport function validateSafeEmail(input: string): boolean {\n\t// Standard format check — bounded length to prevent ReDoS\n\tif (input.length > 254) return false;\n\tconst emailPattern =\n\t\t/^[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])?)*$/;\n\tif (!emailPattern.test(input)) {\n\t\treturn false;\n\t}\n\n\t// Check for injection patterns in email\n\t// Emails shouldn't have HTML, SQL commands, etc.\n\tconst result = detectThreatPatterns(input, {\n\t\tcheckXSS: true,\n\t\tcheckSQLInjection: true,\n\t\tcheckNoSQLInjection: true,\n\t\tcheckCommandInjection: false,\n\t\tcheckPathTraversal: false,\n\t\tcheckHomoglyphs: true,\n\t});\n\n\treturn result.isSafe;\n}\n\n/**\n * Map a {@link ThreatDetectionResult} into a user-facing error\n * message string suitable for an HTTP 400 response or form\n * validation error. Returns `\"\"` when the result is safe (so\n * `error || undefined` works).\n *\n * Uses only the **first** finding for the message — exposing every\n * threat type to the user can leak information about the detection\n * rules. For full diagnostics, log `result.threats` server-side\n * rather than returning them.\n */\nexport function getThreatErrorMessage(result: ThreatDetectionResult): string {\n\tif (result.isSafe) return \"\";\n\n\tconst threat = result.threats[0];\n\tif (!threat) return THREAT_DETECTED_MESSAGE;\n\n\tswitch (threat.type) {\n\t\tcase \"xss\":\n\t\t\treturn \"Input contains potentially malicious script content\";\n\t\tcase \"sql_injection\":\n\t\t\treturn \"Input contains potentially malicious database commands\";\n\t\tcase \"nosql_injection\":\n\t\t\treturn \"Input contains potentially malicious query operators\";\n\t\tcase \"command_injection\":\n\t\t\treturn \"Input contains potentially malicious system commands\";\n\t\tcase \"path_traversal\":\n\t\t\treturn \"Input contains potentially malicious file path characters\";\n\t\tcase \"homoglyph\":\n\t\t\treturn \"Input contains suspicious lookalike characters\";\n\t\tdefault:\n\t\t\treturn assertNever(threat.type);\n\t}\n}\n"],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,86 @@
1
+ {
2
+ "name": "@resq-systems/security",
3
+ "version": "1.0.0",
4
+ "description": "Security utilities: encryption, input validation, schemas, and PII sanitization",
5
+ "license": "Apache-2.0",
6
+ "type": "module",
7
+ "exports": {
8
+ ".": {
9
+ "types": "./lib/index.d.mts",
10
+ "import": "./lib/index.mjs",
11
+ "default": "./lib/index.mjs"
12
+ },
13
+ "./sanitize": {
14
+ "types": "./lib/sanitize.d.mts",
15
+ "import": "./lib/sanitize.mjs",
16
+ "default": "./lib/sanitize.mjs"
17
+ },
18
+ "./validators": {
19
+ "types": "./lib/validators.d.mts",
20
+ "import": "./lib/validators.mjs",
21
+ "default": "./lib/validators.mjs"
22
+ },
23
+ "./crypto": {
24
+ "types": "./lib/crypto.d.mts",
25
+ "import": "./lib/crypto.mjs",
26
+ "default": "./lib/crypto.mjs"
27
+ },
28
+ "./package.json": "./package.json"
29
+ },
30
+ "main": "lib/index.mjs",
31
+ "types": "lib/index.d.mts",
32
+ "files": [
33
+ "lib",
34
+ "README.md"
35
+ ],
36
+ "scripts": {
37
+ "build": "tsdown",
38
+ "test": "vitest run"
39
+ },
40
+ "peerDependencies": {
41
+ "effect": ">=4.0.0-beta.78",
42
+ "jsdom": ">=22.0.0"
43
+ },
44
+ "peerDependenciesMeta": {
45
+ "effect": {
46
+ "optional": true
47
+ },
48
+ "jsdom": {
49
+ "optional": true
50
+ }
51
+ },
52
+ "devDependencies": {
53
+ "@total-typescript/ts-reset": "^0.6.1",
54
+ "@types/node": "^26.1.1",
55
+ "effect": "4.0.0-beta.97",
56
+ "jsdom": "^29.1.1",
57
+ "tsdown": "^0.22.4",
58
+ "typescript": "7.0.2",
59
+ "vitest": "4.1.10"
60
+ },
61
+ "publishConfig": {
62
+ "access": "public",
63
+ "provenance": true,
64
+ "registry": "https://registry.npmjs.org/"
65
+ },
66
+ "repository": {
67
+ "type": "git",
68
+ "url": "git+https://github.com/resq-software/npm.git",
69
+ "directory": "packages/security"
70
+ },
71
+ "keywords": [
72
+ "security",
73
+ "encryption",
74
+ "validation",
75
+ "sanitize",
76
+ "xss",
77
+ "pii"
78
+ ],
79
+ "engines": {
80
+ "node": ">=20.19.0"
81
+ },
82
+ "dependencies": {
83
+ "@resq-systems/types": "workspace:*",
84
+ "dompurify": "^3.4.11"
85
+ }
86
+ }