forked from Talenttrust/Talenttrust-Backend
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathsanitize.ts
More file actions
100 lines (90 loc) · 3.57 KB
/
Copy pathsanitize.ts
File metadata and controls
100 lines (90 loc) · 3.57 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
import { Request, Response, NextFunction } from 'express';
import { FilterXSS } from 'xss';
/**
* Custom xss options to enforce strict sanitization
* Strip all HTML tags by allowing an empty list of whiteList.
*/
const xssOptions = {
whiteList: {}, // Empty means no tags are allowed
stripIgnoreTag: true, // Filter out all tags not in whiteList
stripIgnoreTagBody: ['script', 'style'], // Remove the tag AND its body
};
const customXss = new FilterXSS(xssOptions);
/**
* Keys that must never be copied onto a sanitized object, because assigning them
* can pollute `Object.prototype` and affect every object in the process.
*/
const PROTOTYPE_POLLUTION_KEYS = new Set(['__proto__', 'constructor', 'prototype']);
/**
* Recursively sanitize an object, array, or string using strict XSS filtering.
*
* The return type mirrors the input type (`T`), so callers - and Express's
* `query`/`params` typings - keep their expected shapes instead of collapsing to
* `any`. String values are trimmed and passed through the strict XSS filter;
* arrays and plain objects are sanitized recursively; everything else is
* returned unchanged.
*
* @typeParam T - The (inferred) type of the value being sanitized.
* @param obj The input object, array, or string to sanitize.
* @returns A new sanitized value of the same shape as the input.
*
* @remarks Security guarantees:
* - **Prototype-pollution safe:** keys `__proto__`, `constructor`, and
* `prototype` are skipped during recursion, so a crafted payload cannot reach
* `Object.prototype`. The result is also created with a `null` prototype.
* - **Non-mutating:** input objects/arrays are copied rather than mutated.
* - **Idempotent:** running the function twice yields the same result.
* - Special objects (`Date`, `Buffer`) are returned as-is to avoid corruption.
*/
const sanitizeObject = <T>(obj: T): T => {
if (typeof obj === 'string') {
return customXss.process(obj.trim()) as unknown as T;
}
if (Array.isArray(obj)) {
return obj.map((item) => sanitizeObject(item)) as unknown as T;
}
if (obj !== null && typeof obj === 'object') {
// Avoid sanitizing special objects like Date or Buffer
if (obj instanceof Date || Buffer.isBuffer(obj)) {
return obj;
}
const sanitizedObj: { [key: string]: unknown } = Object.create(null);
for (const [key, value] of Object.entries(obj)) {
// Drop prototype-pollution keys so they never survive sanitization.
if (PROTOTYPE_POLLUTION_KEYS.has(key)) {
continue;
}
sanitizedObj[key] = sanitizeObject(value);
}
return sanitizedObj as unknown as T;
}
return obj;
};
/**
* Express middleware that strips XSS payloads from incoming request data.
*
* Sanitizes `req.body`, `req.query`, and `req.params` in place. Because
* {@link sanitizeObject} preserves the input type, `req.query` and `req.params`
* keep their declared `ParsedQs` / params shapes instead of degrading to `any`,
* so downstream handlers continue to see the expected string/array structures.
*
* The middleware is idempotent (safe to run more than once) and guards against
* prototype-pollution keys during sanitization.
*
* @param req Express request object
* @param res Express response object
* @param next Express next function
*/
export const sanitize = (req: Request, _res: Response, next: NextFunction): void => {
if (req.body) {
req.body = sanitizeObject(req.body);
}
if (req.query) {
req.query = sanitizeObject(req.query);
}
if (req.params) {
req.params = sanitizeObject(req.params);
}
next();
};
export default sanitize;