@arcis/node 1.4.2 → 1.4.4
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/README.md +11 -3
- package/dist/cli/arcis.d.ts +23 -0
- package/dist/cli/arcis.d.ts.map +1 -0
- package/dist/cli/arcis.js +312 -0
- package/dist/cli/arcis.js.map +1 -0
- package/dist/cli/arcis.mjs +309 -0
- package/dist/cli/arcis.mjs.map +1 -0
- package/dist/core/constants.d.ts +2 -2
- package/dist/core/constants.d.ts.map +1 -1
- package/dist/core/index.js +4 -1
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.mjs +4 -1
- package/dist/core/index.mjs.map +1 -1
- package/dist/core/types.d.ts +17 -0
- package/dist/core/types.d.ts.map +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +658 -161
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +655 -162
- package/dist/index.mjs.map +1 -1
- package/dist/logging/index.js.map +1 -1
- package/dist/logging/index.mjs.map +1 -1
- package/dist/middleware/bot-detection.d.ts.map +1 -1
- package/dist/middleware/cookies.d.ts.map +1 -1
- package/dist/middleware/csrf.d.ts +10 -0
- package/dist/middleware/csrf.d.ts.map +1 -1
- package/dist/middleware/hpp.d.ts.map +1 -1
- package/dist/middleware/index.d.ts +2 -0
- package/dist/middleware/index.d.ts.map +1 -1
- package/dist/middleware/index.js +833 -12
- package/dist/middleware/index.js.map +1 -1
- package/dist/middleware/index.mjs +832 -13
- package/dist/middleware/index.mjs.map +1 -1
- package/dist/middleware/main.d.ts.map +1 -1
- package/dist/middleware/rate-limit.d.ts.map +1 -1
- package/dist/middleware/signup-protection.d.ts +65 -0
- package/dist/middleware/signup-protection.d.ts.map +1 -0
- package/dist/middleware/telemetry.d.ts +36 -0
- package/dist/middleware/telemetry.d.ts.map +1 -0
- package/dist/sanitizers/index.d.ts +2 -1
- package/dist/sanitizers/index.d.ts.map +1 -1
- package/dist/sanitizers/index.js +238 -152
- package/dist/sanitizers/index.js.map +1 -1
- package/dist/sanitizers/index.mjs +238 -153
- package/dist/sanitizers/index.mjs.map +1 -1
- package/dist/sanitizers/pii.d.ts.map +1 -1
- package/dist/sanitizers/sanitize.d.ts +13 -0
- package/dist/sanitizers/sanitize.d.ts.map +1 -1
- package/dist/sanitizers/ssti.d.ts.map +1 -1
- package/dist/sanitizers/xxe.d.ts.map +1 -1
- package/dist/stores/index.js.map +1 -1
- package/dist/stores/index.mjs.map +1 -1
- package/dist/telemetry/client.d.ts +63 -0
- package/dist/telemetry/client.d.ts.map +1 -0
- package/dist/telemetry/index.d.ts +3 -0
- package/dist/telemetry/index.d.ts.map +1 -0
- package/dist/telemetry/types.d.ts +71 -0
- package/dist/telemetry/types.d.ts.map +1 -0
- package/dist/validation/index.js +3 -0
- package/dist/validation/index.js.map +1 -1
- package/dist/validation/index.mjs +3 -0
- package/dist/validation/index.mjs.map +1 -1
- package/package.json +10 -1
|
@@ -33,13 +33,19 @@ var XSS_PATTERNS = [
|
|
|
33
33
|
/** base href hijacking — redirects all relative URLs to attacker domain */
|
|
34
34
|
/<base[\s>]/gi,
|
|
35
35
|
/** link tag injection — stylesheet or preload CSRF attacks */
|
|
36
|
-
/<link[\s>]/gi
|
|
36
|
+
/<link[\s>]/gi,
|
|
37
|
+
/** style tag — CSS expression() / behavior: / IE-era attacks. Mirrors
|
|
38
|
+
* Python's xss-style-tag from packages/core/patterns.json. */
|
|
39
|
+
/<style[\s>]/gi
|
|
37
40
|
];
|
|
38
41
|
var XSS_REMOVE_PATTERNS = [
|
|
39
42
|
/** Full script blocks (content + tags) */
|
|
40
43
|
/<script[^>]*>[\s\S]*?<\/script>/gi,
|
|
41
44
|
/** Standalone/unclosed script tags */
|
|
42
45
|
/<script[^>]*>/gi,
|
|
46
|
+
/** style — CSS expression() and behavior: attacks (IE-era but still relevant) */
|
|
47
|
+
/<style[^>]*>[\s\S]*?<\/style>/gi,
|
|
48
|
+
/<style[^>]*/gi,
|
|
43
49
|
/** iframe — full block and partial/unclosed */
|
|
44
50
|
/<iframe[^>]*>[\s\S]*?<\/iframe>/gi,
|
|
45
51
|
/<iframe[^>]*/gi,
|
|
@@ -423,6 +429,168 @@ function detectCommandInjection(input) {
|
|
|
423
429
|
return false;
|
|
424
430
|
}
|
|
425
431
|
|
|
432
|
+
// src/sanitizers/ssti.ts
|
|
433
|
+
var SSTI_DETECT_PATTERNS = [
|
|
434
|
+
/** Jinja2 / Twig / Nunjucks: {{ ... }} */
|
|
435
|
+
/\{\{.*?\}\}/g,
|
|
436
|
+
/** Freemarker / Thymeleaf / Spring EL: ${ ... } */
|
|
437
|
+
/\$\{.*?\}/g,
|
|
438
|
+
/** ERB / EJS: <%= ... %> or <% ... %> */
|
|
439
|
+
/<%[=\-]?.*?%>/gs,
|
|
440
|
+
/** Pug / Jade / Slim: #{ ... } */
|
|
441
|
+
/#\{.*?\}/g,
|
|
442
|
+
/** Python dunder sandbox escape */
|
|
443
|
+
/__(?:class|mro|subclasses|globals|builtins|import)__/gi,
|
|
444
|
+
/** Jinja2 config leak: {{config.X}} or {{config['X']}} */
|
|
445
|
+
/\{\{\s*config[.\[]/gi,
|
|
446
|
+
/** Jinja2 built-in objects */
|
|
447
|
+
/\{\{\s*(?:self|request|lipsum|cycler|joiner|namespace|range)\b/gi
|
|
448
|
+
];
|
|
449
|
+
var SSTI_REMOVE_PATTERNS = [
|
|
450
|
+
/** Jinja2 / Twig: {{ ... }} — always strip (not valid in any JS context) */
|
|
451
|
+
/\{\{.*?\}\}/g,
|
|
452
|
+
/**
|
|
453
|
+
* Freemarker / Spring EL: ${...} — strip when expression contains operators,
|
|
454
|
+
* method calls, or Python dunder patterns (sandbox escape).
|
|
455
|
+
* Bare ${name} and ${user.name} are left intact (JS template literal syntax).
|
|
456
|
+
*/
|
|
457
|
+
/\$\{[^}]*__\w+__[^}]*\}/g,
|
|
458
|
+
/\$\{[^}]*[?!()*+\-/][^}]*\}/g,
|
|
459
|
+
/** ERB / EJS: <%= ... %> */
|
|
460
|
+
/<%[=\-]?.*?%>/gs,
|
|
461
|
+
/**
|
|
462
|
+
* Pug / Jade: #{...} — same narrowing as ${ above, plus dunder detection.
|
|
463
|
+
* #{name} output expressions are left intact.
|
|
464
|
+
*/
|
|
465
|
+
/#\{[^}]*__\w+__[^}]*\}/g,
|
|
466
|
+
/#\{[^}]*[?!()*+\-/][^}]*\}/g,
|
|
467
|
+
/** Python dunder sandbox escape — always strip */
|
|
468
|
+
/__(?:class|mro|subclasses|globals|builtins|import)__/gi
|
|
469
|
+
];
|
|
470
|
+
function sanitizeSsti(input, collectThreats = false) {
|
|
471
|
+
if (typeof input !== "string") {
|
|
472
|
+
return collectThreats ? { value: String(input), wasSanitized: false, threats: [] } : String(input);
|
|
473
|
+
}
|
|
474
|
+
const threats = [];
|
|
475
|
+
let value = input;
|
|
476
|
+
let wasSanitized = false;
|
|
477
|
+
for (const pattern of SSTI_REMOVE_PATTERNS) {
|
|
478
|
+
pattern.lastIndex = 0;
|
|
479
|
+
if (pattern.test(value)) {
|
|
480
|
+
pattern.lastIndex = 0;
|
|
481
|
+
if (collectThreats) {
|
|
482
|
+
const matches = value.match(pattern);
|
|
483
|
+
if (matches) {
|
|
484
|
+
for (const match of matches) {
|
|
485
|
+
threats.push({
|
|
486
|
+
type: "ssti",
|
|
487
|
+
pattern: pattern.source,
|
|
488
|
+
original: match
|
|
489
|
+
});
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
value = value.replace(pattern, "");
|
|
494
|
+
wasSanitized = true;
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
if (collectThreats) {
|
|
498
|
+
return { value, wasSanitized, threats };
|
|
499
|
+
}
|
|
500
|
+
return value;
|
|
501
|
+
}
|
|
502
|
+
function detectSsti(input) {
|
|
503
|
+
if (typeof input !== "string") return false;
|
|
504
|
+
for (const pattern of SSTI_DETECT_PATTERNS) {
|
|
505
|
+
pattern.lastIndex = 0;
|
|
506
|
+
if (pattern.test(input)) {
|
|
507
|
+
return true;
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
return false;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
// src/sanitizers/xxe.ts
|
|
514
|
+
var MAX_XXE_INPUT_BYTES = 1e6;
|
|
515
|
+
var MAX_ENTITY_REFERENCES = 64;
|
|
516
|
+
var XXE_DETECT_PATTERNS = [
|
|
517
|
+
/** DOCTYPE declaration */
|
|
518
|
+
/<!DOCTYPE\b/gi,
|
|
519
|
+
/** ENTITY declaration */
|
|
520
|
+
/<!ENTITY\b/gi,
|
|
521
|
+
/** SYSTEM keyword with URI */
|
|
522
|
+
/\bSYSTEM\s+["']/gi,
|
|
523
|
+
/** PUBLIC keyword with URI */
|
|
524
|
+
/\bPUBLIC\s+["']/gi,
|
|
525
|
+
/** Parameter entity reference (%entity;) */
|
|
526
|
+
/%\s*\w+\s*;/g,
|
|
527
|
+
/** CDATA section (often used to smuggle payloads) */
|
|
528
|
+
/<!\[CDATA\[/gi
|
|
529
|
+
];
|
|
530
|
+
var XXE_REMOVE_PATTERNS = [
|
|
531
|
+
/** Full DOCTYPE block with optional internal subset: <!DOCTYPE ... [...]> */
|
|
532
|
+
/<!DOCTYPE\s[^[>]*(?:\[[^\]]*\]\s*)?>|<!DOCTYPE\s[^>]*>/gi,
|
|
533
|
+
/** Full ENTITY declaration: <!ENTITY ... > */
|
|
534
|
+
/<!ENTITY[^>]*>/gi,
|
|
535
|
+
/** CDATA sections: <![CDATA[ ... ]]> */
|
|
536
|
+
/<!\[CDATA\[[\s\S]*?\]\]>/gi
|
|
537
|
+
];
|
|
538
|
+
function sanitizeXxe(input, collectThreats = false) {
|
|
539
|
+
if (typeof input !== "string") {
|
|
540
|
+
return collectThreats ? { value: String(input), wasSanitized: false, threats: [] } : String(input);
|
|
541
|
+
}
|
|
542
|
+
const threats = [];
|
|
543
|
+
let value = input;
|
|
544
|
+
let wasSanitized = false;
|
|
545
|
+
if (value.length > MAX_XXE_INPUT_BYTES) {
|
|
546
|
+
if (collectThreats) {
|
|
547
|
+
threats.push({ type: "xxe", pattern: "oversize_input", original: `length=${value.length}` });
|
|
548
|
+
}
|
|
549
|
+
return collectThreats ? { value: "", wasSanitized: true, threats } : "";
|
|
550
|
+
}
|
|
551
|
+
const entityRefs = value.match(/&\w+;/g);
|
|
552
|
+
if (entityRefs && entityRefs.length > MAX_ENTITY_REFERENCES) {
|
|
553
|
+
if (collectThreats) {
|
|
554
|
+
threats.push({ type: "xxe", pattern: "entity_expansion", original: `count=${entityRefs.length}` });
|
|
555
|
+
}
|
|
556
|
+
return collectThreats ? { value: "", wasSanitized: true, threats } : "";
|
|
557
|
+
}
|
|
558
|
+
for (const pattern of XXE_REMOVE_PATTERNS) {
|
|
559
|
+
pattern.lastIndex = 0;
|
|
560
|
+
if (pattern.test(value)) {
|
|
561
|
+
pattern.lastIndex = 0;
|
|
562
|
+
if (collectThreats) {
|
|
563
|
+
const matches = value.match(pattern);
|
|
564
|
+
if (matches) {
|
|
565
|
+
for (const match of matches) {
|
|
566
|
+
threats.push({
|
|
567
|
+
type: "xxe",
|
|
568
|
+
pattern: pattern.source,
|
|
569
|
+
original: match
|
|
570
|
+
});
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
value = value.replace(pattern, "");
|
|
575
|
+
wasSanitized = true;
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
if (collectThreats) {
|
|
579
|
+
return { value, wasSanitized, threats };
|
|
580
|
+
}
|
|
581
|
+
return value;
|
|
582
|
+
}
|
|
583
|
+
function detectXxe(input) {
|
|
584
|
+
if (typeof input !== "string") return false;
|
|
585
|
+
for (const pattern of XXE_DETECT_PATTERNS) {
|
|
586
|
+
pattern.lastIndex = 0;
|
|
587
|
+
if (pattern.test(input)) {
|
|
588
|
+
return true;
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
return false;
|
|
592
|
+
}
|
|
593
|
+
|
|
426
594
|
// src/sanitizers/sanitize.ts
|
|
427
595
|
function sanitizeString(value, options = {}) {
|
|
428
596
|
if (typeof value !== "string") return value;
|
|
@@ -492,9 +660,73 @@ function sanitizeObjectDepth(obj, options, depth) {
|
|
|
492
660
|
}
|
|
493
661
|
return result;
|
|
494
662
|
}
|
|
663
|
+
function scanThreats(data, depth = 0) {
|
|
664
|
+
if (depth > INPUT.MAX_RECURSION_DEPTH) return null;
|
|
665
|
+
if (data && typeof data === "object" && !Array.isArray(data)) {
|
|
666
|
+
for (const key of Object.keys(data)) {
|
|
667
|
+
const lower = key.toLowerCase();
|
|
668
|
+
if (DANGEROUS_PROTO_KEYS.has(lower)) {
|
|
669
|
+
return { vector: "prototype", rule: "prototype/match", matchedPattern: key };
|
|
670
|
+
}
|
|
671
|
+
if (NOSQL_DANGEROUS_KEYS.has(key)) {
|
|
672
|
+
return { vector: "nosql", rule: "nosql/match", matchedPattern: key };
|
|
673
|
+
}
|
|
674
|
+
const inner = scanThreats(data[key], depth + 1);
|
|
675
|
+
if (inner) return inner;
|
|
676
|
+
}
|
|
677
|
+
return null;
|
|
678
|
+
}
|
|
679
|
+
if (Array.isArray(data)) {
|
|
680
|
+
for (const item of data) {
|
|
681
|
+
const inner = scanThreats(item, depth + 1);
|
|
682
|
+
if (inner) return inner;
|
|
683
|
+
}
|
|
684
|
+
return null;
|
|
685
|
+
}
|
|
686
|
+
if (typeof data !== "string") return null;
|
|
687
|
+
const sample = data.slice(0, 80);
|
|
688
|
+
if (detectXss(data)) {
|
|
689
|
+
return { vector: "xss", rule: "xss/match", matchedPattern: sample };
|
|
690
|
+
}
|
|
691
|
+
if (detectSsti(data)) {
|
|
692
|
+
return { vector: "ssti", rule: "ssti/match", matchedPattern: sample };
|
|
693
|
+
}
|
|
694
|
+
if (detectXxe(data)) {
|
|
695
|
+
return { vector: "xxe", rule: "xxe/match", matchedPattern: sample };
|
|
696
|
+
}
|
|
697
|
+
if (detectSql(data)) {
|
|
698
|
+
return { vector: "sql", rule: "sql/match", matchedPattern: sample };
|
|
699
|
+
}
|
|
700
|
+
if (detectPathTraversal(data)) {
|
|
701
|
+
return { vector: "path", rule: "path/match", matchedPattern: sample };
|
|
702
|
+
}
|
|
703
|
+
if (detectCommandInjection(data)) {
|
|
704
|
+
return { vector: "command", rule: "command/match", matchedPattern: sample };
|
|
705
|
+
}
|
|
706
|
+
return null;
|
|
707
|
+
}
|
|
495
708
|
function createSanitizer(options = {}) {
|
|
496
|
-
return (req,
|
|
709
|
+
return (req, res, next) => {
|
|
497
710
|
try {
|
|
711
|
+
if (options.block) {
|
|
712
|
+
const hit = scanThreats(req.body) || scanThreats(req.query) || scanThreats(req.params) || scanThreats(req.path);
|
|
713
|
+
if (hit) {
|
|
714
|
+
req.__arcis = {
|
|
715
|
+
vector: hit.vector,
|
|
716
|
+
rule: hit.rule,
|
|
717
|
+
severity: "high",
|
|
718
|
+
matchedPattern: hit.matchedPattern,
|
|
719
|
+
reason: `${hit.vector} pattern detected in request`,
|
|
720
|
+
decision: "deny"
|
|
721
|
+
};
|
|
722
|
+
res.status(403).json({
|
|
723
|
+
error: "Request blocked for security reasons",
|
|
724
|
+
code: "SECURITY_THREAT",
|
|
725
|
+
vector: hit.vector
|
|
726
|
+
});
|
|
727
|
+
return;
|
|
728
|
+
}
|
|
729
|
+
}
|
|
498
730
|
if (req.body && typeof req.body === "object") {
|
|
499
731
|
req.body = sanitizeObject(req.body, options);
|
|
500
732
|
}
|
|
@@ -567,158 +799,11 @@ function getDangerousProtoKeys() {
|
|
|
567
799
|
return Array.from(DANGEROUS_PROTO_KEYS);
|
|
568
800
|
}
|
|
569
801
|
|
|
570
|
-
// src/sanitizers/ssti.ts
|
|
571
|
-
var SSTI_DETECT_PATTERNS = [
|
|
572
|
-
/** Jinja2 / Twig / Nunjucks: {{ ... }} */
|
|
573
|
-
/\{\{.*?\}\}/g,
|
|
574
|
-
/** Freemarker / Thymeleaf / Spring EL: ${ ... } */
|
|
575
|
-
/\$\{.*?\}/g,
|
|
576
|
-
/** ERB / EJS: <%= ... %> or <% ... %> */
|
|
577
|
-
/<%[=\-]?.*?%>/gs,
|
|
578
|
-
/** Pug / Jade / Slim: #{ ... } */
|
|
579
|
-
/#\{.*?\}/g,
|
|
580
|
-
/** Python dunder sandbox escape */
|
|
581
|
-
/__(?:class|mro|subclasses|globals|builtins|import)__/gi,
|
|
582
|
-
/** Jinja2 config leak: {{config.X}} or {{config['X']}} */
|
|
583
|
-
/\{\{\s*config[.\[]/gi,
|
|
584
|
-
/** Jinja2 built-in objects */
|
|
585
|
-
/\{\{\s*(?:self|request|lipsum|cycler|joiner|namespace|range)\b/gi
|
|
586
|
-
];
|
|
587
|
-
var SSTI_REMOVE_PATTERNS = [
|
|
588
|
-
/** Jinja2 / Twig: {{ ... }} — always strip (not valid in any JS context) */
|
|
589
|
-
/\{\{.*?\}\}/g,
|
|
590
|
-
/**
|
|
591
|
-
* Freemarker / Spring EL: ${...} — only strip when the expression contains
|
|
592
|
-
* operators (?!*+-/), method calls (), or known-dangerous prefixes.
|
|
593
|
-
* Bare ${name} and ${user.name} are left intact (JS template literal syntax).
|
|
594
|
-
*/
|
|
595
|
-
/\$\{[^}]*[?!()*+\-/][^}]*\}/g,
|
|
596
|
-
/** ERB / EJS: <%= ... %> */
|
|
597
|
-
/<%[=\-]?.*?%>/gs,
|
|
598
|
-
/**
|
|
599
|
-
* Pug / Jade: #{...} — same narrowing as ${ above.
|
|
600
|
-
* #{name} output expressions are left intact.
|
|
601
|
-
*/
|
|
602
|
-
/#\{[^}]*[?!()*+\-/][^}]*\}/g,
|
|
603
|
-
/** Python dunder sandbox escape — always strip */
|
|
604
|
-
/__(?:class|mro|subclasses|globals|builtins|import)__/gi
|
|
605
|
-
];
|
|
606
|
-
function sanitizeSsti(input, collectThreats = false) {
|
|
607
|
-
if (typeof input !== "string") {
|
|
608
|
-
return collectThreats ? { value: String(input), wasSanitized: false, threats: [] } : String(input);
|
|
609
|
-
}
|
|
610
|
-
const threats = [];
|
|
611
|
-
let value = input;
|
|
612
|
-
let wasSanitized = false;
|
|
613
|
-
for (const pattern of SSTI_REMOVE_PATTERNS) {
|
|
614
|
-
pattern.lastIndex = 0;
|
|
615
|
-
if (pattern.test(value)) {
|
|
616
|
-
pattern.lastIndex = 0;
|
|
617
|
-
if (collectThreats) {
|
|
618
|
-
const matches = value.match(pattern);
|
|
619
|
-
if (matches) {
|
|
620
|
-
for (const match of matches) {
|
|
621
|
-
threats.push({
|
|
622
|
-
type: "ssti",
|
|
623
|
-
pattern: pattern.source,
|
|
624
|
-
original: match
|
|
625
|
-
});
|
|
626
|
-
}
|
|
627
|
-
}
|
|
628
|
-
}
|
|
629
|
-
value = value.replace(pattern, "");
|
|
630
|
-
wasSanitized = true;
|
|
631
|
-
}
|
|
632
|
-
}
|
|
633
|
-
if (collectThreats) {
|
|
634
|
-
return { value, wasSanitized, threats };
|
|
635
|
-
}
|
|
636
|
-
return value;
|
|
637
|
-
}
|
|
638
|
-
function detectSsti(input) {
|
|
639
|
-
if (typeof input !== "string") return false;
|
|
640
|
-
for (const pattern of SSTI_DETECT_PATTERNS) {
|
|
641
|
-
pattern.lastIndex = 0;
|
|
642
|
-
if (pattern.test(input)) {
|
|
643
|
-
return true;
|
|
644
|
-
}
|
|
645
|
-
}
|
|
646
|
-
return false;
|
|
647
|
-
}
|
|
648
|
-
|
|
649
|
-
// src/sanitizers/xxe.ts
|
|
650
|
-
var XXE_DETECT_PATTERNS = [
|
|
651
|
-
/** DOCTYPE declaration */
|
|
652
|
-
/<!DOCTYPE\b/gi,
|
|
653
|
-
/** ENTITY declaration */
|
|
654
|
-
/<!ENTITY\b/gi,
|
|
655
|
-
/** SYSTEM keyword with URI */
|
|
656
|
-
/\bSYSTEM\s+["']/gi,
|
|
657
|
-
/** PUBLIC keyword with URI */
|
|
658
|
-
/\bPUBLIC\s+["']/gi,
|
|
659
|
-
/** Parameter entity reference (%entity;) */
|
|
660
|
-
/%\s*\w+\s*;/g,
|
|
661
|
-
/** CDATA section (often used to smuggle payloads) */
|
|
662
|
-
/<!\[CDATA\[/gi
|
|
663
|
-
];
|
|
664
|
-
var XXE_REMOVE_PATTERNS = [
|
|
665
|
-
/** Full DOCTYPE block with optional internal subset: <!DOCTYPE ... [...]> */
|
|
666
|
-
/<!DOCTYPE\s[^[>]*(?:\[[^\]]*\]\s*)?>|<!DOCTYPE\s[^>]*>/gi,
|
|
667
|
-
/** Full ENTITY declaration: <!ENTITY ... > */
|
|
668
|
-
/<!ENTITY[^>]*>/gi,
|
|
669
|
-
/** CDATA sections: <![CDATA[ ... ]]> */
|
|
670
|
-
/<!\[CDATA\[[\s\S]*?\]\]>/gi
|
|
671
|
-
];
|
|
672
|
-
function sanitizeXxe(input, collectThreats = false) {
|
|
673
|
-
if (typeof input !== "string") {
|
|
674
|
-
return collectThreats ? { value: String(input), wasSanitized: false, threats: [] } : String(input);
|
|
675
|
-
}
|
|
676
|
-
const threats = [];
|
|
677
|
-
let value = input;
|
|
678
|
-
let wasSanitized = false;
|
|
679
|
-
for (const pattern of XXE_REMOVE_PATTERNS) {
|
|
680
|
-
pattern.lastIndex = 0;
|
|
681
|
-
if (pattern.test(value)) {
|
|
682
|
-
pattern.lastIndex = 0;
|
|
683
|
-
if (collectThreats) {
|
|
684
|
-
const matches = value.match(pattern);
|
|
685
|
-
if (matches) {
|
|
686
|
-
for (const match of matches) {
|
|
687
|
-
threats.push({
|
|
688
|
-
type: "xxe",
|
|
689
|
-
pattern: pattern.source,
|
|
690
|
-
original: match
|
|
691
|
-
});
|
|
692
|
-
}
|
|
693
|
-
}
|
|
694
|
-
}
|
|
695
|
-
value = value.replace(pattern, "");
|
|
696
|
-
wasSanitized = true;
|
|
697
|
-
}
|
|
698
|
-
}
|
|
699
|
-
if (collectThreats) {
|
|
700
|
-
return { value, wasSanitized, threats };
|
|
701
|
-
}
|
|
702
|
-
return value;
|
|
703
|
-
}
|
|
704
|
-
function detectXxe(input) {
|
|
705
|
-
if (typeof input !== "string") return false;
|
|
706
|
-
for (const pattern of XXE_DETECT_PATTERNS) {
|
|
707
|
-
pattern.lastIndex = 0;
|
|
708
|
-
if (pattern.test(input)) {
|
|
709
|
-
return true;
|
|
710
|
-
}
|
|
711
|
-
}
|
|
712
|
-
return false;
|
|
713
|
-
}
|
|
714
|
-
|
|
715
802
|
// src/sanitizers/jsonp.ts
|
|
716
|
-
var SAFE_CALLBACK_PATTERN = /^[a-zA-Z_$][a-zA-Z0-9_$.
|
|
803
|
+
var SAFE_CALLBACK_PATTERN = /^[a-zA-Z_$][a-zA-Z0-9_$.]*$/;
|
|
717
804
|
var DANGEROUS_CALLBACK_PATTERNS = [
|
|
718
|
-
|
|
805
|
+
/\.\./
|
|
719
806
|
// prototype chain traversal
|
|
720
|
-
/\[\s*\]/
|
|
721
|
-
// empty bracket access
|
|
722
807
|
];
|
|
723
808
|
function sanitizeJsonpCallback(callback, maxLength = 128) {
|
|
724
809
|
if (typeof callback !== "string" || callback.length === 0) {
|
|
@@ -803,7 +888,7 @@ function detectHeaderInjection(input) {
|
|
|
803
888
|
|
|
804
889
|
// src/sanitizers/pii.ts
|
|
805
890
|
var EMAIL_RE = /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z]{2,})+/g;
|
|
806
|
-
var PHONE_RE = /(?:\+?1[-.\s]?)?\(?[2-9]\d{2}\)?[-.\s]?\d{3}[-.\s]?\d{4}/g;
|
|
891
|
+
var PHONE_RE = /(?<!\d)(?:\+?1[-.\s]?)?\(?[2-9]\d{2}\)?[-.\s]?\d{3}[-.\s]?\d{4}(?!\d)/g;
|
|
807
892
|
var CREDIT_CARD_RE = /\b(?:\d[ -]*?){13,19}\b/g;
|
|
808
893
|
var SSN_RE = /\b\d{3}[-\s]\d{2}[-\s]\d{4}\b/g;
|
|
809
894
|
var IPV4_RE = /\b(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\b/g;
|
|
@@ -1021,6 +1106,6 @@ function detectLdapInjection(input) {
|
|
|
1021
1106
|
return LDAP_DETECT_PATTERN.test(input) || LDAP_INJECTION_PATTERN.test(input);
|
|
1022
1107
|
}
|
|
1023
1108
|
|
|
1024
|
-
export { createSanitizer, detectCommandInjection, detectHeaderInjection, detectJsonpInjection, detectLdapInjection, detectNoSqlInjection, detectPathTraversal, detectPii, detectPrototypePollution, detectSql, detectSsti, detectXss, detectXxe, encodeForAttribute, encodeForCss, encodeForHtml, encodeForJs, encodeForUrl, encodeHtmlEntities, getDangerousOperators, getDangerousProtoKeys, isDangerousNoSqlKey, isDangerousProtoKey, isPlainObject, redactObjectPii, redactPii, sanitizeCommand, sanitizeHeaderValue, sanitizeHeaders, sanitizeJsonpCallback, sanitizeLdapDn, sanitizeLdapFilter, sanitizeObject, sanitizePath, sanitizeSql, sanitizeSsti, sanitizeString, sanitizeXss, sanitizeXxe, scanObjectPii, scanPii };
|
|
1109
|
+
export { createSanitizer, detectCommandInjection, detectHeaderInjection, detectJsonpInjection, detectLdapInjection, detectNoSqlInjection, detectPathTraversal, detectPii, detectPrototypePollution, detectSql, detectSsti, detectXss, detectXxe, encodeForAttribute, encodeForCss, encodeForHtml, encodeForJs, encodeForUrl, encodeHtmlEntities, getDangerousOperators, getDangerousProtoKeys, isDangerousNoSqlKey, isDangerousProtoKey, isPlainObject, redactObjectPii, redactPii, sanitizeCommand, sanitizeHeaderValue, sanitizeHeaders, sanitizeJsonpCallback, sanitizeLdapDn, sanitizeLdapFilter, sanitizeObject, sanitizePath, sanitizeSql, sanitizeSsti, sanitizeString, sanitizeXss, sanitizeXxe, scanObjectPii, scanPii, scanThreats };
|
|
1025
1110
|
//# sourceMappingURL=index.mjs.map
|
|
1026
1111
|
//# sourceMappingURL=index.mjs.map
|