hi-secure 1.0.15 → 1.0.16

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 (99) hide show
  1. package/dist/adapters/ArgonAdapter.d.ts +1 -1
  2. package/dist/adapters/ArgonAdapter.d.ts.map +1 -1
  3. package/dist/adapters/ArgonAdapter.js +43 -5
  4. package/dist/adapters/ArgonAdapter.js.map +1 -1
  5. package/dist/adapters/BcryptAdapter.d.ts.map +1 -1
  6. package/dist/adapters/BcryptAdapter.js +43 -3
  7. package/dist/adapters/BcryptAdapter.js.map +1 -1
  8. package/dist/adapters/ExpressRLAdapter.d.ts.map +1 -1
  9. package/dist/adapters/ExpressRLAdapter.js +48 -6
  10. package/dist/adapters/ExpressRLAdapter.js.map +1 -1
  11. package/dist/adapters/ExpressValidatorAdapter.d.ts.map +1 -1
  12. package/dist/adapters/ExpressValidatorAdapter.js +50 -10
  13. package/dist/adapters/ExpressValidatorAdapter.js.map +1 -1
  14. package/dist/adapters/GoogleAdapter.d.ts.map +1 -1
  15. package/dist/adapters/GoogleAdapter.js +82 -16
  16. package/dist/adapters/GoogleAdapter.js.map +1 -1
  17. package/dist/adapters/JWTAdapter.d.ts.map +1 -1
  18. package/dist/adapters/JWTAdapter.js +104 -15
  19. package/dist/adapters/JWTAdapter.js.map +1 -1
  20. package/dist/adapters/RLFlexibleAdapter.d.ts.map +1 -1
  21. package/dist/adapters/RLFlexibleAdapter.js +87 -12
  22. package/dist/adapters/RLFlexibleAdapter.js.map +1 -1
  23. package/dist/adapters/SanitizeHtmlAdapter.d.ts.map +1 -1
  24. package/dist/adapters/SanitizeHtmlAdapter.js +81 -13
  25. package/dist/adapters/SanitizeHtmlAdapter.js.map +1 -1
  26. package/dist/adapters/XSSAdapter.d.ts +1 -1
  27. package/dist/adapters/XSSAdapter.d.ts.map +1 -1
  28. package/dist/adapters/XSSAdapter.js +137 -20
  29. package/dist/adapters/XSSAdapter.js.map +1 -1
  30. package/dist/adapters/ZodAdapter.d.ts +1 -1
  31. package/dist/adapters/ZodAdapter.d.ts.map +1 -1
  32. package/dist/adapters/ZodAdapter.js +13 -8
  33. package/dist/adapters/ZodAdapter.js.map +1 -1
  34. package/dist/core/HiSecure.d.ts +3 -4
  35. package/dist/core/HiSecure.d.ts.map +1 -1
  36. package/dist/core/HiSecure.js +108 -121
  37. package/dist/core/HiSecure.js.map +1 -1
  38. package/dist/index.d.ts +2 -0
  39. package/dist/index.d.ts.map +1 -1
  40. package/dist/index.js +8 -1
  41. package/dist/index.js.map +1 -1
  42. package/dist/logging/index.d.ts.map +1 -1
  43. package/dist/logging/index.js +2 -0
  44. package/dist/logging/index.js.map +1 -1
  45. package/dist/logging/morganSetup.d.ts.map +1 -1
  46. package/dist/logging/morganSetup.js +22 -1
  47. package/dist/logging/morganSetup.js.map +1 -1
  48. package/dist/logging/winstonSetup.d.ts.map +1 -1
  49. package/dist/logging/winstonSetup.js +61 -3
  50. package/dist/logging/winstonSetup.js.map +1 -1
  51. package/dist/managers/AuthManager.d.ts +2 -2
  52. package/dist/managers/AuthManager.d.ts.map +1 -1
  53. package/dist/managers/AuthManager.js +167 -31
  54. package/dist/managers/AuthManager.js.map +1 -1
  55. package/dist/managers/CorsManager.d.ts.map +1 -1
  56. package/dist/managers/CorsManager.js +46 -11
  57. package/dist/managers/CorsManager.js.map +1 -1
  58. package/dist/managers/HashManager.d.ts +1 -1
  59. package/dist/managers/HashManager.d.ts.map +1 -1
  60. package/dist/managers/HashManager.js +127 -17
  61. package/dist/managers/HashManager.js.map +1 -1
  62. package/dist/managers/JsonManager.d.ts +1 -1
  63. package/dist/managers/JsonManager.d.ts.map +1 -1
  64. package/dist/managers/JsonManager.js +99 -16
  65. package/dist/managers/JsonManager.js.map +1 -1
  66. package/dist/managers/RateLimitManager.d.ts +1 -1
  67. package/dist/managers/RateLimitManager.d.ts.map +1 -1
  68. package/dist/managers/RateLimitManager.js +46 -22
  69. package/dist/managers/RateLimitManager.js.map +1 -1
  70. package/dist/managers/SanitizerManager.d.ts.map +1 -1
  71. package/dist/managers/SanitizerManager.js +112 -15
  72. package/dist/managers/SanitizerManager.js.map +1 -1
  73. package/dist/managers/ValidatorManager.d.ts.map +1 -1
  74. package/dist/managers/ValidatorManager.js +90 -7
  75. package/dist/managers/ValidatorManager.js.map +1 -1
  76. package/package.json +2 -6
  77. package/readme.md +3 -6
  78. package/src/adapters/ArgonAdapter.ts +55 -6
  79. package/src/adapters/BcryptAdapter.ts +56 -8
  80. package/src/adapters/ExpressRLAdapter.ts +62 -9
  81. package/src/adapters/ExpressValidatorAdapter.ts +67 -11
  82. package/src/adapters/GoogleAdapter.ts +106 -21
  83. package/src/adapters/JWTAdapter.ts +129 -21
  84. package/src/adapters/RLFlexibleAdapter.ts +113 -16
  85. package/src/adapters/SanitizeHtmlAdapter.ts +111 -18
  86. package/src/adapters/XSSAdapter.ts +183 -39
  87. package/src/adapters/ZodAdapter.ts +56 -10
  88. package/src/core/HiSecure.ts +496 -162
  89. package/src/index.ts +4 -0
  90. package/src/logging/index.ts +6 -0
  91. package/src/logging/morganSetup.ts +36 -1
  92. package/src/logging/winstonSetup.ts +97 -8
  93. package/src/managers/AuthManager.ts +205 -34
  94. package/src/managers/CorsManager.ts +63 -16
  95. package/src/managers/HashManager.ts +156 -19
  96. package/src/managers/JsonManager.ts +119 -15
  97. package/src/managers/RateLimitManager.ts +174 -29
  98. package/src/managers/SanitizerManager.ts +150 -25
  99. package/src/managers/ValidatorManager.ts +115 -15
@@ -1,10 +1,92 @@
1
+ // import { RateLimiterMemory, RateLimiterRes } from "rate-limiter-flexible";
2
+ // import { logger } from "../logging/index.js";
3
+ // import { AdapterError } from "../core/errors/AdapterError.js";
4
+
5
+ // export interface RLOptions {
6
+ // points?: number;
7
+ // duration?: number;
8
+ // message?: any;
9
+ // blockDuration?: number;
10
+ // }
11
+
12
+ // export class RLFlexibleAdapter {
13
+ // getMiddleware(options: RLOptions = {}) {
14
+ // try {
15
+ // const defaultOptions = {
16
+ // points: 100,
17
+ // duration: 60,
18
+ // message: "Too many requests, slow down.",
19
+ // blockDuration: 0
20
+ // };
21
+
22
+ // const finalOptions = { ...defaultOptions, ...options };
23
+
24
+ // const limiter = new RateLimiterMemory({
25
+ // points: finalOptions.points,
26
+ // duration: finalOptions.duration,
27
+ // blockDuration: finalOptions.blockDuration
28
+ // });
29
+
30
+ // return async (req: any, res: any, next: any) => {
31
+
32
+ // const ip = this.extractIP(req);
33
+
34
+ // try {
35
+ // await limiter.consume(ip);
36
+ // next();
37
+ // } catch (err: any) {
38
+ // const rlErr = err as RateLimiterRes;
39
+
40
+ // logger.warn("RLFlexibleAdapter: rate limit exceeded", {
41
+ // ip,
42
+ // path: req.path,
43
+ // method: req.method,
44
+ // retryAfter: rlErr.msBeforeNext
45
+ // });
46
+
47
+ // res.setHeader('Retry-After', Math.ceil(rlErr.msBeforeNext / 1000));
48
+
49
+ // return res.status(429).json({
50
+ // success: false,
51
+ // error: "RATE_LIMIT_EXCEEDED",
52
+ // retryAfter: Math.ceil(rlErr.msBeforeNext / 1000),
53
+ // message: finalOptions.message
54
+ // });
55
+ // }
56
+ // };
57
+
58
+ // } catch (err: any) {
59
+ // logger.error("RLFlexibleAdapter: failed to initialize limiter", {
60
+ // error: err?.message || err
61
+ // });
62
+ // throw new AdapterError("RateLimiterFlexible creation failed.");
63
+ // }
64
+ // }
65
+
66
+ // private extractIP(req: any): string {
67
+ // return (
68
+ // req.headers['x-real-ip'] ||
69
+ // req.headers['x-forwarded-for']?.split(',')[0]?.trim() ||
70
+ // req.ip ||
71
+ // req.connection?.remoteAddress ||
72
+ // req.socket?.remoteAddress ||
73
+ // 'unknown'
74
+ // );
75
+ // }
76
+ // }
77
+
78
+
79
+
80
+
81
+
82
+
1
83
  import { RateLimiterMemory, RateLimiterRes } from "rate-limiter-flexible";
2
- import { logger } from "../logging/index.js";
3
- import { AdapterError } from "../core/errors/AdapterError.js";
84
+ import { logger } from "../logging";
85
+ import { AdapterError } from "../core/errors/AdapterError";
4
86
 
5
87
  export interface RLOptions {
6
88
  points?: number;
7
- duration?: number;
89
+ duration?: number;
8
90
  message?: any;
9
91
  blockDuration?: number;
10
92
  }
@@ -27,8 +109,16 @@ export class RLFlexibleAdapter {
27
109
  blockDuration: finalOptions.blockDuration
28
110
  });
29
111
 
112
+
113
+ logger.info("Rate limiter initialized", {
114
+ adapter: "rate-limiter-flexible",
115
+ operation: "init",
116
+ points: finalOptions.points,
117
+ duration: finalOptions.duration,
118
+ blockDuration: finalOptions.blockDuration
119
+ });
120
+
30
121
  return async (req: any, res: any, next: any) => {
31
-
32
122
  const ip = this.extractIP(req);
33
123
 
34
124
  try {
@@ -37,15 +127,20 @@ export class RLFlexibleAdapter {
37
127
  } catch (err: any) {
38
128
  const rlErr = err as RateLimiterRes;
39
129
 
40
- logger.warn("RLFlexibleAdapter: rate limit exceeded", {
130
+ logger.warn("Rate limit exceeded", {
131
+ adapter: "rate-limiter-flexible",
132
+ operation: "consume",
41
133
  ip,
42
- path: req.path,
43
134
  method: req.method,
44
- retryAfter: rlErr.msBeforeNext
135
+ path: req.path,
136
+ retryAfterMs: rlErr.msBeforeNext
45
137
  });
46
138
 
47
- res.setHeader('Retry-After', Math.ceil(rlErr.msBeforeNext / 1000));
48
-
139
+ res.setHeader(
140
+ "Retry-After",
141
+ Math.ceil(rlErr.msBeforeNext / 1000)
142
+ );
143
+
49
144
  return res.status(429).json({
50
145
  success: false,
51
146
  error: "RATE_LIMIT_EXCEEDED",
@@ -54,23 +149,25 @@ export class RLFlexibleAdapter {
54
149
  });
55
150
  }
56
151
  };
57
-
58
152
  } catch (err: any) {
59
- logger.error("RLFlexibleAdapter: failed to initialize limiter", {
60
- error: err?.message || err
153
+ logger.error("Rate limiter initialization failed", {
154
+ adapter: "rate-limiter-flexible",
155
+ operation: "init",
156
+ reason: err?.message
61
157
  });
158
+
62
159
  throw new AdapterError("RateLimiterFlexible creation failed.");
63
160
  }
64
161
  }
65
162
 
66
163
  private extractIP(req: any): string {
67
164
  return (
68
- req.headers['x-real-ip'] ||
69
- req.headers['x-forwarded-for']?.split(',')[0]?.trim() ||
165
+ req.headers["x-real-ip"] ||
166
+ req.headers["x-forwarded-for"]?.split(",")[0]?.trim() ||
70
167
  req.ip ||
71
168
  req.connection?.remoteAddress ||
72
169
  req.socket?.remoteAddress ||
73
- 'unknown'
170
+ "unknown"
74
171
  );
75
172
  }
76
- }
173
+ }
@@ -1,6 +1,89 @@
1
+ // import sanitizeHtml from "sanitize-html";
2
+ // import { AdapterError } from "../core/errors/AdapterError.js";
3
+ // import { logger } from "../logging/index.js";
4
+
5
+ // export class SanitizeHtmlAdapter {
6
+ // private globalOptions: sanitizeHtml.IOptions;
7
+
8
+ // constructor(options: sanitizeHtml.IOptions = {}) {
9
+ // this.globalOptions = options;
10
+ // }
11
+
12
+ // sanitize(input: string, dynamicOptions?: any): string {
13
+ // try {
14
+ // const opts = { ...this.globalOptions, ...(dynamicOptions || {}) };
15
+
16
+ // const clean = sanitizeHtml(input, opts);
17
+ // return typeof clean === "string" ? clean : String(clean);
18
+
19
+ // } catch (err: any) {
20
+ // logger.error("sanitize-html failed", {
21
+ // error: err?.message || err,
22
+ // preview: typeof input === "string" ? input.slice(0, 100) : undefined
23
+ // });
24
+
25
+ // throw new AdapterError("sanitize-html adapter failed.");
26
+ // }
27
+ // }
28
+
29
+ // // Deep Sanitization - Recursively
30
+ // private deepSanitize(obj: any, dynamicOptions?: any, visited = new WeakSet()): any {
31
+
32
+ // if (obj && typeof obj === "object") {
33
+ // if (visited.has(obj)) {
34
+ // return obj;
35
+ // }
36
+ // visited.add(obj);
37
+ // }
38
+
39
+ // if (typeof obj === "string") {
40
+ // return this.sanitize(obj, dynamicOptions);
41
+ // }
42
+
43
+ // if (Array.isArray(obj)) {
44
+ // return obj.map((item) => this.deepSanitize(item, dynamicOptions, visited));
45
+ // }
46
+
47
+ // if (obj && typeof obj === "object") {
48
+ // const result: any = {};
49
+ // for (const key of Object.keys(obj)) {
50
+ // result[key] = this.deepSanitize(obj[key], dynamicOptions, visited);
51
+ // }
52
+ // return result;
53
+ // }
54
+
55
+ // return obj;
56
+ // }
57
+
58
+ // middleware(dynamicOptions?: any) {
59
+ // return (req: any, _res: any, next: any) => {
60
+ // try {
61
+ // if (req.body) {
62
+ // req.body = this.deepSanitize(req.body, dynamicOptions);
63
+
64
+ // logger.debug("sanitize-html applied", {
65
+ // keys: Object.keys(req.body)
66
+ // });
67
+ // }
68
+ // next();
69
+
70
+ // } catch (err: any) {
71
+ // logger.error("sanitize-html middleware failed", {
72
+ // error: err?.message || err
73
+ // });
74
+ // next(err);
75
+ // }
76
+ // };
77
+ // }
78
+ // }
79
+
80
+
81
+
82
+
83
+
1
84
  import sanitizeHtml from "sanitize-html";
2
- import { AdapterError } from "../core/errors/AdapterError.js";
3
- import { logger } from "../logging/index.js";
85
+ import { AdapterError } from "../core/errors/AdapterError";
86
+ import { logger } from "../logging";
4
87
 
5
88
  export class SanitizeHtmlAdapter {
6
89
  private globalOptions: sanitizeHtml.IOptions;
@@ -12,27 +95,25 @@ export class SanitizeHtmlAdapter {
12
95
  sanitize(input: string, dynamicOptions?: any): string {
13
96
  try {
14
97
  const opts = { ...this.globalOptions, ...(dynamicOptions || {}) };
15
-
16
98
  const clean = sanitizeHtml(input, opts);
99
+
17
100
  return typeof clean === "string" ? clean : String(clean);
18
101
 
19
102
  } catch (err: any) {
20
- logger.error("sanitize-html failed", {
21
- error: err?.message || err,
22
- preview: typeof input === "string" ? input.slice(0, 100) : undefined
103
+ logger.error("HTML sanitization failed", {
104
+ adapter: "sanitize-html",
105
+ operation: "sanitize",
106
+ reason: err?.message
23
107
  });
24
108
 
25
109
  throw new AdapterError("sanitize-html adapter failed.");
26
110
  }
27
111
  }
28
112
 
29
- // Deep Sanitization - Recursively
113
+ // Deep Sanitization - recursively
30
114
  private deepSanitize(obj: any, dynamicOptions?: any, visited = new WeakSet()): any {
31
-
32
115
  if (obj && typeof obj === "object") {
33
- if (visited.has(obj)) {
34
- return obj;
35
- }
116
+ if (visited.has(obj)) return obj;
36
117
  visited.add(obj);
37
118
  }
38
119
 
@@ -41,13 +122,19 @@ export class SanitizeHtmlAdapter {
41
122
  }
42
123
 
43
124
  if (Array.isArray(obj)) {
44
- return obj.map((item) => this.deepSanitize(item, dynamicOptions, visited));
125
+ return obj.map(item =>
126
+ this.deepSanitize(item, dynamicOptions, visited)
127
+ );
45
128
  }
46
129
 
47
130
  if (obj && typeof obj === "object") {
48
131
  const result: any = {};
49
132
  for (const key of Object.keys(obj)) {
50
- result[key] = this.deepSanitize(obj[key], dynamicOptions, visited);
133
+ result[key] = this.deepSanitize(
134
+ obj[key],
135
+ dynamicOptions,
136
+ visited
137
+ );
51
138
  }
52
139
  return result;
53
140
  }
@@ -61,18 +148,24 @@ export class SanitizeHtmlAdapter {
61
148
  if (req.body) {
62
149
  req.body = this.deepSanitize(req.body, dynamicOptions);
63
150
 
64
- logger.debug("sanitize-html applied", {
151
+ // visible + safe info log
152
+ logger.info("HTML sanitization applied", {
153
+ adapter: "sanitize-html",
154
+ operation: "middleware",
65
155
  keys: Object.keys(req.body)
66
156
  });
67
157
  }
68
- next();
69
158
 
159
+ next();
70
160
  } catch (err: any) {
71
- logger.error("sanitize-html middleware failed", {
72
- error: err?.message || err
161
+ logger.error("HTML sanitization middleware failed", {
162
+ adapter: "sanitize-html",
163
+ operation: "middleware",
164
+ reason: err?.message
73
165
  });
166
+
74
167
  next(err);
75
168
  }
76
169
  };
77
170
  }
78
- }
171
+ }
@@ -1,6 +1,155 @@
1
- import { FilterXSS, getDefaultWhiteList, whiteList } from 'xss';
2
- import { AdapterError } from "../core/errors/AdapterError.js";
3
- import { logger } from "../logging/index.js";
1
+ // import { FilterXSS, getDefaultWhiteList, whiteList } from 'xss';
2
+ // import { AdapterError } from "../core/errors/AdapterError.js";
3
+ // import { logger } from "../logging/index.js";
4
+
5
+ // export interface XSSOptions {
6
+ // whiteList?: typeof whiteList;
7
+ // stripIgnoreTag?: boolean;
8
+ // stripIgnoreTagBody?: string[];
9
+ // allowCommentTag?: boolean;
10
+ // css?: boolean | { [key: string]: boolean };
11
+ // onTag?: (tag: string, html: string, options: any) => string;
12
+ // onTagAttr?: (tag: string, name: string, value: string, isWhiteAttr: boolean) => string;
13
+ // onIgnoreTag?: (tag: string, html: string, options: any) => string;
14
+ // [key: string]: any;
15
+ // }
16
+
17
+ // export class XSSAdapter {
18
+ // private globalOptions: XSSOptions;
19
+ // private defaultFilter: FilterXSS;
20
+
21
+ // constructor(options: XSSOptions = {}) {
22
+ // this.globalOptions = options;
23
+
24
+ // // Default safe configuration
25
+ // const defaultOptions: XSSOptions = {
26
+ // whiteList: getDefaultWhiteList(),
27
+ // stripIgnoreTag: true,
28
+ // stripIgnoreTagBody: ['script', 'style', 'iframe', 'object', 'embed'],
29
+ // allowCommentTag: false,
30
+ // css: false,
31
+ // onTag: (tag, html, options) => {
32
+
33
+ // if (tag === 'a') {
34
+ // return html.replace(/<a /i, '<a target="_blank" rel="noopener noreferrer" ');
35
+ // }
36
+ // return html;
37
+ // }
38
+ // };
39
+
40
+ // const finalOptions = { ...defaultOptions, ...options };
41
+ // this.defaultFilter = new FilterXSS(finalOptions);
42
+ // }
43
+
44
+
45
+ // sanitize(input: string, dynamicOptions?: XSSOptions): string {
46
+ // try {
47
+ // if (typeof input !== "string") {
48
+ // return input as any;
49
+ // }
50
+
51
+
52
+ // if (!dynamicOptions || Object.keys(dynamicOptions).length === 0) {
53
+ // return this.defaultFilter.process(input);
54
+ // }
55
+
56
+
57
+ // const mergedOptions = { ...this.globalOptions, ...dynamicOptions };
58
+ // const customFilter = new FilterXSS(mergedOptions);
59
+
60
+ // return customFilter.process(input);
61
+
62
+ // } catch (err: any) {
63
+ // logger.error("XSS sanitizer failed", {
64
+ // error: err?.message,
65
+ // preview: input?.slice?.(0, 80)
66
+ // });
67
+ // throw new AdapterError("XSS sanitizer failed.");
68
+ // }
69
+ // }
70
+
71
+
72
+ // middleware(dynamicOptions?: XSSOptions) {
73
+ // return (req: any, _res: any, next: any) => {
74
+ // try {
75
+ // if (req.body && typeof req.body === "object") {
76
+ // const originalBody = req.body;
77
+ // const sanitizedBody: any = Array.isArray(originalBody) ? [] : {};
78
+
79
+ // for (const key of Object.keys(originalBody)) {
80
+ // const val = originalBody[key];
81
+
82
+ // if (typeof val === "string") {
83
+ // sanitizedBody[key] = this.sanitize(val, dynamicOptions);
84
+ // } else if (Array.isArray(val)) {
85
+ // sanitizedBody[key] = val.map((v) =>
86
+ // typeof v === "string"
87
+ // ? this.sanitize(v, dynamicOptions)
88
+ // : v
89
+ // );
90
+ // } else if (val && typeof val === "object") {
91
+
92
+ // sanitizedBody[key] = this.deepSanitize(val, dynamicOptions);
93
+ // } else {
94
+ // sanitizedBody[key] = val;
95
+ // }
96
+ // }
97
+
98
+
99
+ // req.sanitizedBody = sanitizedBody;
100
+
101
+ // logger.debug("XSS sanitizer applied", {
102
+ // originalKeys: Object.keys(originalBody),
103
+ // sanitizedKeys: Object.keys(sanitizedBody)
104
+ // });
105
+ // }
106
+
107
+ // next();
108
+ // } catch (err: any) {
109
+ // logger.error("XSS middleware failed", {
110
+ // error: err?.message || err
111
+ // });
112
+ // next(err);
113
+ // }
114
+ // };
115
+ // }
116
+
117
+
118
+ // private deepSanitize(obj: any, options?: XSSOptions, visited = new WeakSet()): any {
119
+
120
+ // if (obj && typeof obj === "object") {
121
+ // if (visited.has(obj)) {
122
+ // return obj;
123
+ // }
124
+ // visited.add(obj);
125
+ // }
126
+
127
+ // if (typeof obj === "string") {
128
+ // return this.sanitize(obj, options);
129
+ // }
130
+
131
+ // if (Array.isArray(obj)) {
132
+ // return obj.map(item => this.deepSanitize(item, options, visited));
133
+ // }
134
+
135
+ // if (obj && typeof obj === "object") {
136
+ // const result: any = {};
137
+ // for (const key of Object.keys(obj)) {
138
+ // result[key] = this.deepSanitize(obj[key], options, visited);
139
+ // }
140
+ // return result;
141
+ // }
142
+
143
+ // return obj;
144
+ // }
145
+ // }
146
+
147
+
148
+
149
+
150
+ import { FilterXSS, getDefaultWhiteList, whiteList } from "xss";
151
+ import { AdapterError } from "../core/errors/AdapterError";
152
+ import { logger } from "../logging";
4
153
 
5
154
  export interface XSSOptions {
6
155
  whiteList?: typeof whiteList;
@@ -20,18 +169,19 @@ export class XSSAdapter {
20
169
 
21
170
  constructor(options: XSSOptions = {}) {
22
171
  this.globalOptions = options;
23
-
24
- // Default safe configuration
172
+
25
173
  const defaultOptions: XSSOptions = {
26
174
  whiteList: getDefaultWhiteList(),
27
- stripIgnoreTag: true,
28
- stripIgnoreTagBody: ['script', 'style', 'iframe', 'object', 'embed'],
175
+ stripIgnoreTag: true,
176
+ stripIgnoreTagBody: ["script", "style", "iframe", "object", "embed"],
29
177
  allowCommentTag: false,
30
- css: false,
31
- onTag: (tag, html, options) => {
32
-
33
- if (tag === 'a') {
34
- return html.replace(/<a /i, '<a target="_blank" rel="noopener noreferrer" ');
178
+ css: false,
179
+ onTag: (tag, html) => {
180
+ if (tag === "a") {
181
+ return html.replace(
182
+ /<a /i,
183
+ '<a target="_blank" rel="noopener noreferrer" '
184
+ );
35
185
  }
36
186
  return html;
37
187
  }
@@ -41,86 +191,80 @@ export class XSSAdapter {
41
191
  this.defaultFilter = new FilterXSS(finalOptions);
42
192
  }
43
193
 
44
-
45
194
  sanitize(input: string, dynamicOptions?: XSSOptions): string {
46
195
  try {
47
- if (typeof input !== "string") {
48
- return input as any;
49
- }
196
+ if (typeof input !== "string") return input as any;
50
197
 
51
-
52
198
  if (!dynamicOptions || Object.keys(dynamicOptions).length === 0) {
53
199
  return this.defaultFilter.process(input);
54
200
  }
55
201
 
56
-
57
202
  const mergedOptions = { ...this.globalOptions, ...dynamicOptions };
58
203
  const customFilter = new FilterXSS(mergedOptions);
59
-
204
+
60
205
  return customFilter.process(input);
61
206
 
62
207
  } catch (err: any) {
63
- logger.error("XSS sanitizer failed", {
64
- error: err?.message,
65
- preview: input?.slice?.(0, 80)
208
+ logger.error("XSS sanitization failed", {
209
+ adapter: "xss",
210
+ operation: "sanitize",
211
+ reason: err?.message
66
212
  });
213
+
67
214
  throw new AdapterError("XSS sanitizer failed.");
68
215
  }
69
216
  }
70
217
 
71
-
72
218
  middleware(dynamicOptions?: XSSOptions) {
73
219
  return (req: any, _res: any, next: any) => {
74
220
  try {
75
221
  if (req.body && typeof req.body === "object") {
76
222
  const originalBody = req.body;
77
223
  const sanitizedBody: any = Array.isArray(originalBody) ? [] : {};
78
-
224
+
79
225
  for (const key of Object.keys(originalBody)) {
80
226
  const val = originalBody[key];
81
227
 
82
228
  if (typeof val === "string") {
83
229
  sanitizedBody[key] = this.sanitize(val, dynamicOptions);
84
230
  } else if (Array.isArray(val)) {
85
- sanitizedBody[key] = val.map((v) =>
231
+ sanitizedBody[key] = val.map(v =>
86
232
  typeof v === "string"
87
233
  ? this.sanitize(v, dynamicOptions)
88
234
  : v
89
235
  );
90
236
  } else if (val && typeof val === "object") {
91
-
92
237
  sanitizedBody[key] = this.deepSanitize(val, dynamicOptions);
93
238
  } else {
94
239
  sanitizedBody[key] = val;
95
240
  }
96
241
  }
97
-
98
-
242
+
99
243
  req.sanitizedBody = sanitizedBody;
100
-
101
- logger.debug("XSS sanitizer applied", {
102
- originalKeys: Object.keys(originalBody),
103
- sanitizedKeys: Object.keys(sanitizedBody)
244
+
245
+ // visible + safe info log
246
+ logger.info("XSS sanitization applied", {
247
+ adapter: "xss",
248
+ operation: "middleware",
249
+ keys: Object.keys(sanitizedBody)
104
250
  });
105
251
  }
106
252
 
107
253
  next();
108
254
  } catch (err: any) {
109
255
  logger.error("XSS middleware failed", {
110
- error: err?.message || err
256
+ adapter: "xss",
257
+ operation: "middleware",
258
+ reason: err?.message
111
259
  });
112
260
  next(err);
113
261
  }
114
262
  };
115
263
  }
116
264
 
117
-
118
265
  private deepSanitize(obj: any, options?: XSSOptions, visited = new WeakSet()): any {
119
-
120
266
  if (obj && typeof obj === "object") {
121
- if (visited.has(obj)) {
122
- return obj;
123
- }
267
+ if (visited.has(obj)) return obj;
124
268
  visited.add(obj);
125
269
  }
126
270
 
@@ -142,4 +286,4 @@ export class XSSAdapter {
142
286
 
143
287
  return obj;
144
288
  }
145
- }
289
+ }