@eggjs/security 4.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.
Files changed (183) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +569 -0
  3. package/README.zh-CN.md +441 -0
  4. package/dist/commonjs/agent.d.ts +6 -0
  5. package/dist/commonjs/agent.js +14 -0
  6. package/dist/commonjs/app/extend/agent.d.ts +5 -0
  7. package/dist/commonjs/app/extend/agent.js +11 -0
  8. package/dist/commonjs/app/extend/application.d.ts +16 -0
  9. package/dist/commonjs/app/extend/application.js +35 -0
  10. package/dist/commonjs/app/extend/context.d.ts +68 -0
  11. package/dist/commonjs/app/extend/context.js +283 -0
  12. package/dist/commonjs/app/extend/helper.d.ts +12 -0
  13. package/dist/commonjs/app/extend/helper.js +10 -0
  14. package/dist/commonjs/app/extend/response.d.ts +41 -0
  15. package/dist/commonjs/app/extend/response.js +85 -0
  16. package/dist/commonjs/app/middleware/securities.d.ts +4 -0
  17. package/dist/commonjs/app/middleware/securities.js +55 -0
  18. package/dist/commonjs/app.d.ts +6 -0
  19. package/dist/commonjs/app.js +29 -0
  20. package/dist/commonjs/config/config.default.d.ts +871 -0
  21. package/dist/commonjs/config/config.default.js +357 -0
  22. package/dist/commonjs/config/config.local.d.ts +5 -0
  23. package/dist/commonjs/config/config.local.js +10 -0
  24. package/dist/commonjs/index.d.ts +1 -0
  25. package/dist/commonjs/index.js +14 -0
  26. package/dist/commonjs/lib/extend/safe_curl.d.ts +16 -0
  27. package/dist/commonjs/lib/extend/safe_curl.js +28 -0
  28. package/dist/commonjs/lib/helper/cliFilter.d.ts +4 -0
  29. package/dist/commonjs/lib/helper/cliFilter.js +20 -0
  30. package/dist/commonjs/lib/helper/escape.d.ts +2 -0
  31. package/dist/commonjs/lib/helper/escape.js +8 -0
  32. package/dist/commonjs/lib/helper/escapeShellArg.d.ts +1 -0
  33. package/dist/commonjs/lib/helper/escapeShellArg.js +8 -0
  34. package/dist/commonjs/lib/helper/escapeShellCmd.d.ts +1 -0
  35. package/dist/commonjs/lib/helper/escapeShellCmd.js +17 -0
  36. package/dist/commonjs/lib/helper/index.d.ts +21 -0
  37. package/dist/commonjs/lib/helper/index.js +26 -0
  38. package/dist/commonjs/lib/helper/shtml.d.ts +2 -0
  39. package/dist/commonjs/lib/helper/shtml.js +76 -0
  40. package/dist/commonjs/lib/helper/sjs.d.ts +4 -0
  41. package/dist/commonjs/lib/helper/sjs.js +52 -0
  42. package/dist/commonjs/lib/helper/sjson.d.ts +1 -0
  43. package/dist/commonjs/lib/helper/sjson.js +45 -0
  44. package/dist/commonjs/lib/helper/spath.d.ts +5 -0
  45. package/dist/commonjs/lib/helper/spath.js +28 -0
  46. package/dist/commonjs/lib/helper/surl.d.ts +2 -0
  47. package/dist/commonjs/lib/helper/surl.js +33 -0
  48. package/dist/commonjs/lib/middlewares/csp.d.ts +4 -0
  49. package/dist/commonjs/lib/middlewares/csp.js +68 -0
  50. package/dist/commonjs/lib/middlewares/csrf.d.ts +4 -0
  51. package/dist/commonjs/lib/middlewares/csrf.js +42 -0
  52. package/dist/commonjs/lib/middlewares/dta.d.ts +3 -0
  53. package/dist/commonjs/lib/middlewares/dta.js +14 -0
  54. package/dist/commonjs/lib/middlewares/hsts.d.ts +4 -0
  55. package/dist/commonjs/lib/middlewares/hsts.js +23 -0
  56. package/dist/commonjs/lib/middlewares/index.d.ts +13 -0
  57. package/dist/commonjs/lib/middlewares/index.js +28 -0
  58. package/dist/commonjs/lib/middlewares/methodnoallow.d.ts +3 -0
  59. package/dist/commonjs/lib/middlewares/methodnoallow.js +22 -0
  60. package/dist/commonjs/lib/middlewares/noopen.d.ts +4 -0
  61. package/dist/commonjs/lib/middlewares/noopen.js +17 -0
  62. package/dist/commonjs/lib/middlewares/nosniff.d.ts +4 -0
  63. package/dist/commonjs/lib/middlewares/nosniff.js +30 -0
  64. package/dist/commonjs/lib/middlewares/referrerPolicy.d.ts +4 -0
  65. package/dist/commonjs/lib/middlewares/referrerPolicy.js +36 -0
  66. package/dist/commonjs/lib/middlewares/xframe.d.ts +4 -0
  67. package/dist/commonjs/lib/middlewares/xframe.js +19 -0
  68. package/dist/commonjs/lib/middlewares/xssProtection.d.ts +4 -0
  69. package/dist/commonjs/lib/middlewares/xssProtection.js +16 -0
  70. package/dist/commonjs/lib/utils.d.ts +19 -0
  71. package/dist/commonjs/lib/utils.js +206 -0
  72. package/dist/commonjs/package.json +3 -0
  73. package/dist/commonjs/types.d.ts +10 -0
  74. package/dist/commonjs/types.js +5 -0
  75. package/dist/esm/agent.d.ts +6 -0
  76. package/dist/esm/agent.js +11 -0
  77. package/dist/esm/app/extend/agent.d.ts +5 -0
  78. package/dist/esm/app/extend/agent.js +8 -0
  79. package/dist/esm/app/extend/application.d.ts +16 -0
  80. package/dist/esm/app/extend/application.js +32 -0
  81. package/dist/esm/app/extend/context.d.ts +68 -0
  82. package/dist/esm/app/extend/context.js +244 -0
  83. package/dist/esm/app/extend/helper.d.ts +12 -0
  84. package/dist/esm/app/extend/helper.js +5 -0
  85. package/dist/esm/app/extend/response.d.ts +41 -0
  86. package/dist/esm/app/extend/response.js +82 -0
  87. package/dist/esm/app/middleware/securities.d.ts +4 -0
  88. package/dist/esm/app/middleware/securities.js +50 -0
  89. package/dist/esm/app.d.ts +6 -0
  90. package/dist/esm/app.js +26 -0
  91. package/dist/esm/config/config.default.d.ts +871 -0
  92. package/dist/esm/config/config.default.js +351 -0
  93. package/dist/esm/config/config.local.d.ts +5 -0
  94. package/dist/esm/config/config.local.js +8 -0
  95. package/dist/esm/index.d.ts +1 -0
  96. package/dist/esm/index.js +12 -0
  97. package/dist/esm/lib/extend/safe_curl.d.ts +16 -0
  98. package/dist/esm/lib/extend/safe_curl.js +25 -0
  99. package/dist/esm/lib/helper/cliFilter.d.ts +4 -0
  100. package/dist/esm/lib/helper/cliFilter.js +17 -0
  101. package/dist/esm/lib/helper/escape.d.ts +2 -0
  102. package/dist/esm/lib/helper/escape.js +3 -0
  103. package/dist/esm/lib/helper/escapeShellArg.d.ts +1 -0
  104. package/dist/esm/lib/helper/escapeShellArg.js +5 -0
  105. package/dist/esm/lib/helper/escapeShellCmd.d.ts +1 -0
  106. package/dist/esm/lib/helper/escapeShellCmd.js +14 -0
  107. package/dist/esm/lib/helper/index.d.ts +21 -0
  108. package/dist/esm/lib/helper/index.js +21 -0
  109. package/dist/esm/lib/helper/shtml.d.ts +2 -0
  110. package/dist/esm/lib/helper/shtml.js +70 -0
  111. package/dist/esm/lib/helper/sjs.d.ts +4 -0
  112. package/dist/esm/lib/helper/sjs.js +49 -0
  113. package/dist/esm/lib/helper/sjson.d.ts +1 -0
  114. package/dist/esm/lib/helper/sjson.js +39 -0
  115. package/dist/esm/lib/helper/spath.d.ts +5 -0
  116. package/dist/esm/lib/helper/spath.js +25 -0
  117. package/dist/esm/lib/helper/surl.d.ts +2 -0
  118. package/dist/esm/lib/helper/surl.js +30 -0
  119. package/dist/esm/lib/middlewares/csp.d.ts +4 -0
  120. package/dist/esm/lib/middlewares/csp.js +63 -0
  121. package/dist/esm/lib/middlewares/csrf.d.ts +4 -0
  122. package/dist/esm/lib/middlewares/csrf.js +37 -0
  123. package/dist/esm/lib/middlewares/dta.d.ts +3 -0
  124. package/dist/esm/lib/middlewares/dta.js +12 -0
  125. package/dist/esm/lib/middlewares/hsts.d.ts +4 -0
  126. package/dist/esm/lib/middlewares/hsts.js +21 -0
  127. package/dist/esm/lib/middlewares/index.d.ts +13 -0
  128. package/dist/esm/lib/middlewares/index.js +23 -0
  129. package/dist/esm/lib/middlewares/methodnoallow.d.ts +3 -0
  130. package/dist/esm/lib/middlewares/methodnoallow.js +20 -0
  131. package/dist/esm/lib/middlewares/noopen.d.ts +4 -0
  132. package/dist/esm/lib/middlewares/noopen.js +15 -0
  133. package/dist/esm/lib/middlewares/nosniff.d.ts +4 -0
  134. package/dist/esm/lib/middlewares/nosniff.js +28 -0
  135. package/dist/esm/lib/middlewares/referrerPolicy.d.ts +4 -0
  136. package/dist/esm/lib/middlewares/referrerPolicy.js +34 -0
  137. package/dist/esm/lib/middlewares/xframe.d.ts +4 -0
  138. package/dist/esm/lib/middlewares/xframe.js +17 -0
  139. package/dist/esm/lib/middlewares/xssProtection.d.ts +4 -0
  140. package/dist/esm/lib/middlewares/xssProtection.js +14 -0
  141. package/dist/esm/lib/utils.d.ts +19 -0
  142. package/dist/esm/lib/utils.js +194 -0
  143. package/dist/esm/package.json +3 -0
  144. package/dist/esm/types.d.ts +10 -0
  145. package/dist/esm/types.js +3 -0
  146. package/dist/package.json +4 -0
  147. package/package.json +116 -0
  148. package/src/agent.ts +14 -0
  149. package/src/app/extend/agent.ts +14 -0
  150. package/src/app/extend/application.ts +51 -0
  151. package/src/app/extend/context.ts +282 -0
  152. package/src/app/extend/helper.ts +5 -0
  153. package/src/app/extend/response.ts +95 -0
  154. package/src/app/middleware/securities.ts +63 -0
  155. package/src/app.ts +31 -0
  156. package/src/config/config.default.ts +379 -0
  157. package/src/config/config.local.ts +9 -0
  158. package/src/index.ts +12 -0
  159. package/src/lib/extend/safe_curl.ts +35 -0
  160. package/src/lib/helper/cliFilter.ts +20 -0
  161. package/src/lib/helper/escape.ts +3 -0
  162. package/src/lib/helper/escapeShellArg.ts +4 -0
  163. package/src/lib/helper/escapeShellCmd.ts +16 -0
  164. package/src/lib/helper/index.ts +21 -0
  165. package/src/lib/helper/shtml.ts +77 -0
  166. package/src/lib/helper/sjs.ts +57 -0
  167. package/src/lib/helper/sjson.ts +35 -0
  168. package/src/lib/helper/spath.ts +27 -0
  169. package/src/lib/helper/surl.ts +35 -0
  170. package/src/lib/middlewares/csp.ts +70 -0
  171. package/src/lib/middlewares/csrf.ts +44 -0
  172. package/src/lib/middlewares/dta.ts +13 -0
  173. package/src/lib/middlewares/hsts.ts +24 -0
  174. package/src/lib/middlewares/index.ts +23 -0
  175. package/src/lib/middlewares/methodnoallow.ts +23 -0
  176. package/src/lib/middlewares/noopen.ts +18 -0
  177. package/src/lib/middlewares/nosniff.ts +32 -0
  178. package/src/lib/middlewares/referrerPolicy.ts +39 -0
  179. package/src/lib/middlewares/xframe.ts +20 -0
  180. package/src/lib/middlewares/xssProtection.ts +17 -0
  181. package/src/lib/utils.ts +208 -0
  182. package/src/types.ts +16 -0
  183. package/src/typings/index.d.ts +4 -0
@@ -0,0 +1,379 @@
1
+ import z from 'zod';
2
+ import { Context } from '@eggjs/core';
3
+
4
+ const CSRFSupportRequestItem = z.object({
5
+ path: z.instanceof(RegExp),
6
+ methods: z.array(z.string()),
7
+ });
8
+ export type CSRFSupportRequestItem = z.infer<typeof CSRFSupportRequestItem>;
9
+
10
+ export const LookupAddress = z.object({
11
+ address: z.string(),
12
+ family: z.number(),
13
+ });
14
+ export type LookupAddress = z.infer<typeof LookupAddress>;
15
+
16
+ const LookupAddressAndStringArray = z.union([ z.string(), LookupAddress ]).array();
17
+ const SSRFCheckAddressFunction = z.function()
18
+ .args(z.union([ z.string(), LookupAddress, LookupAddressAndStringArray ]), z.union([ z.number(), z.string() ]), z.string())
19
+ .returns(z.boolean());
20
+ /**
21
+ * SSRF check address function
22
+ * `(address, family, hostname) => boolean`
23
+ */
24
+ export type SSRFCheckAddressFunction = z.infer<typeof SSRFCheckAddressFunction>;
25
+
26
+ export const SecurityMiddlewareName = z.enum([
27
+ 'csrf',
28
+ 'hsts',
29
+ 'methodnoallow',
30
+ 'noopen',
31
+ 'nosniff',
32
+ 'csp',
33
+ 'xssProtection',
34
+ 'xframe',
35
+ 'dta',
36
+ ]);
37
+ export type SecurityMiddlewareName = z.infer<typeof SecurityMiddlewareName>;
38
+
39
+ /**
40
+ * (ctx) => boolean
41
+ */
42
+ const IgnoreOrMatchHandler = z.function().args(z.instanceof(Context)).returns(z.boolean());
43
+ export type IgnoreOrMatchHandler = z.infer<typeof IgnoreOrMatchHandler>;
44
+
45
+ const IgnoreOrMatch = z.union([
46
+ z.string(), z.instanceof(RegExp), IgnoreOrMatchHandler,
47
+ ]);
48
+ export type IgnoreOrMatch = z.infer<typeof IgnoreOrMatch>;
49
+
50
+ const IgnoreOrMatchOption = z.union([ IgnoreOrMatch, IgnoreOrMatch.array() ]).optional();
51
+ export type IgnoreOrMatchOption = z.infer<typeof IgnoreOrMatchOption>;
52
+
53
+ /**
54
+ * security options
55
+ * @member Config#security
56
+ */
57
+ export const SecurityConfig = z.object({
58
+ /**
59
+ * domain white list
60
+ *
61
+ * Default to `[]`
62
+ */
63
+ domainWhiteList: z.array(z.string()).default([]),
64
+ /**
65
+ * protocol white list
66
+ *
67
+ * Default to `[]`
68
+ */
69
+ protocolWhiteList: z.array(z.string()).default([]),
70
+ /**
71
+ * default open security middleware
72
+ *
73
+ * Default to `'csrf,hsts,methodnoallow,noopen,nosniff,csp,xssProtection,xframe,dta'`
74
+ */
75
+ defaultMiddleware: z.union([ z.string(), z.array(SecurityMiddlewareName) ])
76
+ .default(SecurityMiddlewareName.options),
77
+ /**
78
+ * whether defend csrf attack
79
+ */
80
+ csrf: z.preprocess(val => {
81
+ // transform old config, `csrf: false` to `csrf: { enable: false }`
82
+ if (typeof val === 'boolean') {
83
+ return { enable: val };
84
+ }
85
+ return val;
86
+ }, z.object({
87
+ match: IgnoreOrMatchOption,
88
+ ignore: IgnoreOrMatchOption,
89
+ /**
90
+ * Default to `true`
91
+ */
92
+ enable: z.boolean().default(true),
93
+ /**
94
+ * csrf token detect source type
95
+ *
96
+ * Default to `'ctoken'`
97
+ */
98
+ type: z.enum([ 'ctoken', 'referer', 'all', 'any' ]).default('ctoken'),
99
+ /**
100
+ * ignore json request
101
+ *
102
+ * Default to `false`
103
+ *
104
+ * @deprecated is not safe now, don't use it
105
+ */
106
+ ignoreJSON: z.boolean().default(false),
107
+ /**
108
+ * csrf token cookie name
109
+ *
110
+ * Default to `'csrfToken'`
111
+ */
112
+ cookieName: z.union([ z.string(), z.array(z.string()) ]).default('csrfToken'),
113
+ /**
114
+ * csrf token session name
115
+ *
116
+ * Default to `'csrfToken'`
117
+ */
118
+ sessionName: z.string().default('csrfToken'),
119
+ /**
120
+ * csrf token request header name
121
+ *
122
+ * Default to `'x-csrf-token'`
123
+ */
124
+ headerName: z.string().default('x-csrf-token'),
125
+ /**
126
+ * csrf token request body field name
127
+ *
128
+ * Default to `'_csrf'`
129
+ */
130
+ bodyName: z.union([ z.string(), z.array(z.string()) ]).default('_csrf'),
131
+ /**
132
+ * csrf token request query field name
133
+ *
134
+ * Default to `'_csrf'`
135
+ */
136
+ queryName: z.union([ z.string(), z.array(z.string()) ]).default('_csrf'),
137
+ /**
138
+ * rotate csrf token when it is invalid
139
+ *
140
+ * Default to `false`
141
+ */
142
+ rotateWhenInvalid: z.boolean().default(false),
143
+ /**
144
+ * These config works when using `'ctoken'` type
145
+ *
146
+ * Default to `false`
147
+ */
148
+ useSession: z.boolean().default(false),
149
+ /**
150
+ * csrf token cookie domain setting,
151
+ * can be `(ctx) => string` or `string`
152
+ *
153
+ * Default to `undefined`, auto set the cookie domain in the safe way
154
+ */
155
+ cookieDomain: z.union([
156
+ z.string(),
157
+ z.function()
158
+ .args(z.instanceof(Context))
159
+ .returns(z.string()),
160
+ ]).optional(),
161
+ /**
162
+ * csrf token check requests config
163
+ */
164
+ supportedRequests: z.array(CSRFSupportRequestItem)
165
+ .default([
166
+ { path: /^\//, methods: [ 'POST', 'PATCH', 'DELETE', 'PUT', 'CONNECT' ] },
167
+ ]),
168
+ /**
169
+ * referer or origin header white list.
170
+ * It only works when using `'referer'` type
171
+ *
172
+ * Default to `[]`
173
+ */
174
+ refererWhiteList: z.array(z.string()).default([]),
175
+ /**
176
+ * csrf token cookie options
177
+ *
178
+ * Default to `{
179
+ * signed: false,
180
+ * httpOnly: false,
181
+ * overwrite: true,
182
+ * }`
183
+ */
184
+ cookieOptions: z.object({
185
+ signed: z.boolean(),
186
+ httpOnly: z.boolean(),
187
+ overwrite: z.boolean(),
188
+ }).default({
189
+ signed: false,
190
+ httpOnly: false,
191
+ overwrite: true,
192
+ }),
193
+ }).default({})),
194
+ /**
195
+ * whether enable X-Frame-Options response header
196
+ */
197
+ xframe: z.object({
198
+ match: IgnoreOrMatchOption,
199
+ ignore: IgnoreOrMatchOption,
200
+ /**
201
+ * Default to `true`
202
+ */
203
+ enable: z.boolean().default(true),
204
+ /**
205
+ * X-Frame-Options value, can be `'DENY'`, `'SAMEORIGIN'`, `'ALLOW-FROM https://example.com'`
206
+ *
207
+ * Default to `'SAMEORIGIN'`
208
+ */
209
+ value: z.string().default('SAMEORIGIN'),
210
+ }).default({}),
211
+ /**
212
+ * whether enable Strict-Transport-Security response header
213
+ */
214
+ hsts: z.object({
215
+ match: IgnoreOrMatchOption,
216
+ ignore: IgnoreOrMatchOption,
217
+ /**
218
+ * Default to `false`
219
+ */
220
+ enable: z.boolean().default(false),
221
+ /**
222
+ * Max age of Strict-Transport-Security in seconds
223
+ *
224
+ * Default to `365 * 24 * 3600`
225
+ */
226
+ maxAge: z.number().default(365 * 24 * 3600),
227
+ /**
228
+ * Whether include sub domains
229
+ *
230
+ * Default to `false`
231
+ */
232
+ includeSubdomains: z.boolean().default(false),
233
+ }).default({}),
234
+ /**
235
+ * whether enable Http Method filter
236
+ */
237
+ methodnoallow: z.object({
238
+ match: IgnoreOrMatchOption,
239
+ ignore: IgnoreOrMatchOption,
240
+ /**
241
+ * Default to `true`
242
+ */
243
+ enable: z.boolean().default(true),
244
+ }).default({}),
245
+ /**
246
+ * whether enable IE automatically download open
247
+ */
248
+ noopen: z.object({
249
+ match: IgnoreOrMatchOption,
250
+ ignore: IgnoreOrMatchOption,
251
+ /**
252
+ * Default to `true`
253
+ */
254
+ enable: z.boolean().default(true),
255
+ }).default({}),
256
+ /**
257
+ * whether enable IE8 automatically detect mime
258
+ */
259
+ nosniff: z.object({
260
+ match: IgnoreOrMatchOption,
261
+ ignore: IgnoreOrMatchOption,
262
+ /**
263
+ * Default to `true`
264
+ */
265
+ enable: z.boolean().default(true),
266
+ }).default({}),
267
+ /**
268
+ * whether enable IE8 XSS Filter
269
+ */
270
+ xssProtection: z.object({
271
+ match: IgnoreOrMatchOption,
272
+ ignore: IgnoreOrMatchOption,
273
+ /**
274
+ * Default to `true`
275
+ */
276
+ enable: z.boolean().default(true),
277
+ /**
278
+ * X-XSS-Protection response header value
279
+ *
280
+ * Default to `'1; mode=block'`
281
+ */
282
+ value: z.coerce.string().default('1; mode=block'),
283
+ }).default({}),
284
+ /**
285
+ * content security policy config
286
+ */
287
+ csp: z.object({
288
+ match: IgnoreOrMatchOption,
289
+ ignore: IgnoreOrMatchOption,
290
+ /**
291
+ * Default to `false`
292
+ */
293
+ enable: z.boolean().default(false),
294
+ // https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP#csp_overview
295
+ policy: z.record(z.union([ z.string(), z.array(z.string()), z.boolean() ])).default({}),
296
+ /**
297
+ * whether enable report only mode
298
+ * Default to `undefined`
299
+ */
300
+ reportOnly: z.boolean().optional(),
301
+ /**
302
+ * whether support IE
303
+ * Default to `undefined`
304
+ */
305
+ supportIE: z.boolean().optional(),
306
+ }).default({}),
307
+ /**
308
+ * whether enable referrer policy
309
+ * @see https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referrer-Policy
310
+ */
311
+ referrerPolicy: z.object({
312
+ match: IgnoreOrMatchOption,
313
+ ignore: IgnoreOrMatchOption,
314
+ /**
315
+ * Default to `false`
316
+ */
317
+ enable: z.boolean().default(false),
318
+ /**
319
+ * referrer policy value
320
+ *
321
+ * Default to `'no-referrer-when-downgrade'`
322
+ */
323
+ value: z.string().default('no-referrer-when-downgrade'),
324
+ }).default({}),
325
+ /**
326
+ * whether enable auto avoid directory traversal attack
327
+ */
328
+ dta: z.object({
329
+ match: IgnoreOrMatchOption,
330
+ ignore: IgnoreOrMatchOption,
331
+ /**
332
+ * Default to `true`
333
+ */
334
+ enable: z.boolean().default(true),
335
+ }).default({}),
336
+ ssrf: z.object({
337
+ ipBlackList: z.array(z.string()).optional(),
338
+ ipExceptionList: z.array(z.string()).optional(),
339
+ hostnameExceptionList: z.array(z.string()).optional(),
340
+ checkAddress: SSRFCheckAddressFunction.optional(),
341
+ }).default({}),
342
+ match: z.union([ IgnoreOrMatch, IgnoreOrMatch.array() ]).optional(),
343
+ ignore: z.union([ IgnoreOrMatch, IgnoreOrMatch.array() ]).optional(),
344
+ __protocolWhiteListSet: z.set(z.string()).optional().readonly(),
345
+ });
346
+ export type SecurityConfig = z.infer<typeof SecurityConfig>;
347
+
348
+ const SecurityHelperOnTagAttrHandler = z.function()
349
+ .args(z.string(), z.string(), z.string(), z.boolean())
350
+ .returns(z.union([ z.string(), z.void() ]));
351
+
352
+ /**
353
+ * (tag: string, name: string, value: string, isWhiteAttr: boolean) => string | void
354
+ */
355
+ export type SecurityHelperOnTagAttrHandler = z.infer<typeof SecurityHelperOnTagAttrHandler>;
356
+
357
+ export const SecurityHelperConfig = z.object({
358
+ shtml: z.object({
359
+ /**
360
+ * tag attribute white list
361
+ */
362
+ whiteList: z.record(z.array(z.string())).optional(),
363
+ /**
364
+ * domain white list
365
+ * @deprecated use `config.security.domainWhiteList` instead
366
+ */
367
+ domainWhiteList: z.array(z.string()).optional(),
368
+ /**
369
+ * tag attribute handler
370
+ */
371
+ onTagAttr: SecurityHelperOnTagAttrHandler.optional(),
372
+ }).default({}),
373
+ });
374
+ export type SecurityHelperConfig = z.infer<typeof SecurityHelperConfig>;
375
+
376
+ export default {
377
+ security: SecurityConfig.parse({}),
378
+ helper: SecurityHelperConfig.parse({}),
379
+ };
@@ -0,0 +1,9 @@
1
+ import { SecurityConfig } from '../types.js';
2
+
3
+ export default {
4
+ security: {
5
+ hsts: {
6
+ enable: false,
7
+ },
8
+ } as SecurityConfig,
9
+ };
package/src/index.ts ADDED
@@ -0,0 +1,12 @@
1
+ import './types.js';
2
+
3
+ // module.exports = require('./app/middleware/securities');
4
+ // module.exports.csp = require('./lib/middlewares/csp');
5
+ // module.exports.csrf = require('./lib/middlewares/csrf');
6
+ // module.exports.methodNoAllow = require('./lib/middlewares/methodnoallow');
7
+ // module.exports.noopen = require('./lib/middlewares/noopen');
8
+ // module.exports.nosniff = require('./lib/middlewares/nosniff');
9
+ // module.exports.xssProtection = require('./lib/middlewares/xssProtection');
10
+ // module.exports.xframe = require('./lib/middlewares/xframe');
11
+ // module.exports.safeRedirect = require('./lib/safe_redirect');
12
+ // module.exports.utils = require('./lib/utils');
@@ -0,0 +1,35 @@
1
+ import { EggCore } from '@eggjs/core';
2
+ import type { SSRFCheckAddressFunction } from '../../types.js';
3
+
4
+ const SSRF_HTTPCLIENT = Symbol('SSRF_HTTPCLIENT');
5
+
6
+ type HttpClient = EggCore['HttpClient'];
7
+ type HttpClientParameters = Parameters<HttpClient['prototype']['request']>;
8
+ export type HttpClientRequestURL = HttpClientParameters[0];
9
+ export type HttpClientOptions = HttpClientParameters[1] & { checkAddress?: SSRFCheckAddressFunction };
10
+ export type HttpClientResponse<T = any> = Awaited<ReturnType<HttpClient['prototype']['request']>> & { data: T };
11
+
12
+ /**
13
+ * safe curl with ssrf protection
14
+ */
15
+ export async function safeCurlForApplication<T = any>(app: EggCore, url: HttpClientRequestURL, options: HttpClientOptions = {}) {
16
+ const ssrfConfig = app.config.security.ssrf;
17
+ if (ssrfConfig?.checkAddress) {
18
+ options.checkAddress = ssrfConfig.checkAddress;
19
+ } else {
20
+ app.logger.warn('[@eggjs/security] please configure `config.security.ssrf` first');
21
+ }
22
+
23
+ if (ssrfConfig?.checkAddress) {
24
+ let httpClient = app[SSRF_HTTPCLIENT] as ReturnType<EggCore['createHttpClient']>;
25
+ // use the new httpClient init with checkAddress
26
+ if (!httpClient) {
27
+ httpClient = app[SSRF_HTTPCLIENT] = app.createHttpClient({
28
+ checkAddress: ssrfConfig.checkAddress,
29
+ });
30
+ }
31
+ return await httpClient.request<T>(url, options);
32
+ }
33
+
34
+ return await app.curl<T>(url, options);
35
+ }
@@ -0,0 +1,20 @@
1
+ /**
2
+ * remote command execution
3
+ */
4
+
5
+ const BASIC_ALPHABETS = new Set('abcdefghijklmnopqrstuvwxyz1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ.-_'.split(''));
6
+
7
+ export default function cliFilter(text: string) {
8
+ const str = '' + text;
9
+ let res = '';
10
+ let ascii;
11
+
12
+ for (let index = 0; index < str.length; index++) {
13
+ ascii = str[index];
14
+ if (BASIC_ALPHABETS.has(ascii)) {
15
+ res += ascii;
16
+ }
17
+ }
18
+
19
+ return res;
20
+ }
@@ -0,0 +1,3 @@
1
+ import escapeHTML from 'escape-html';
2
+
3
+ export default escapeHTML;
@@ -0,0 +1,4 @@
1
+ export default function escapeShellArg(text: string) {
2
+ const str = '' + text;
3
+ return '\'' + str.replace(/\\/g, '\\\\').replace(/\'/g, '\\\'') + '\'';
4
+ }
@@ -0,0 +1,16 @@
1
+ const BASIC_ALPHABETS = new Set('#&;`|*?~<>^()[]{}$;\'",\x0A\xFF'.split(''));
2
+
3
+ export default function escapeShellCmd(text: string) {
4
+ const str = '' + text;
5
+ let res = '';
6
+ let ascii;
7
+
8
+ for (let index = 0; index < str.length; index++) {
9
+ ascii = str[index];
10
+ if (!BASIC_ALPHABETS.has(ascii)) {
11
+ res += ascii;
12
+ }
13
+ }
14
+
15
+ return res;
16
+ }
@@ -0,0 +1,21 @@
1
+ import cliFilter from './cliFilter.js';
2
+ import escape from './escape.js';
3
+ import escapeShellArg from './escapeShellArg.js';
4
+ import escapeShellCmd from './escapeShellCmd.js';
5
+ import shtml from './shtml.js';
6
+ import sjs from './sjs.js';
7
+ import sjson from './sjson.js';
8
+ import spath from './spath.js';
9
+ import surl from './surl.js';
10
+
11
+ export default {
12
+ cliFilter,
13
+ escape,
14
+ escapeShellArg,
15
+ escapeShellCmd,
16
+ shtml,
17
+ sjs,
18
+ sjson,
19
+ spath,
20
+ surl,
21
+ };
@@ -0,0 +1,77 @@
1
+ import type { BaseContextClass } from '@eggjs/core';
2
+ import xss from 'xss';
3
+ import { isSafeDomain, getFromUrl } from '../utils.js';
4
+ import type { SecurityHelperOnTagAttrHandler } from '../../types.js';
5
+
6
+ const BUILD_IN_ON_TAG_ATTR = Symbol('buildInOnTagAttr');
7
+
8
+ // default rule: https://github.com/leizongmin/js-xss/blob/master/lib/default.js
9
+ // add domain filter based on xss module
10
+ // custom options http://jsxss.com/zh/options.html
11
+ // eg: support a tag,filter attributes except for title : whiteList: {a: ['title']}
12
+ export default function shtml(this: BaseContextClass, val: string) {
13
+ if (typeof val !== 'string') {
14
+ return val;
15
+ }
16
+
17
+ const securityOptions = this.ctx.securityOptions;
18
+ let buildInOnTagAttrHandler: SecurityHelperOnTagAttrHandler | undefined;
19
+ const shtmlConfig = {
20
+ ...this.app.config.helper.shtml,
21
+ ...securityOptions.shtml,
22
+ [BUILD_IN_ON_TAG_ATTR]: buildInOnTagAttrHandler,
23
+ };
24
+ const domainWhiteList = this.app.config.security.domainWhiteList;
25
+ const app = this.app;
26
+ // filter href and src attribute if not in domain white list
27
+ if (!shtmlConfig[BUILD_IN_ON_TAG_ATTR]) {
28
+ shtmlConfig[BUILD_IN_ON_TAG_ATTR] = (_tag, name, value, isWhiteAttr) => {
29
+ if (isWhiteAttr && (name === 'href' || name === 'src')) {
30
+ if (!value) {
31
+ return;
32
+ }
33
+
34
+ value = String(value);
35
+ if (value[0] === '/' || value[0] === '#') {
36
+ return;
37
+ }
38
+
39
+ const hostname = getFromUrl(value, 'hostname');
40
+ if (!hostname) {
41
+ return;
42
+ }
43
+
44
+ // If we don't have our hostname in the app.security.domainWhiteList,
45
+ // Just check for `shtmlConfig.domainWhiteList` and `ctx.whiteList`.
46
+ if (!isSafeDomain(hostname, domainWhiteList)) {
47
+ // Check for `shtmlConfig.domainWhiteList` first (duplicated now)
48
+ if (shtmlConfig.domainWhiteList && shtmlConfig.domainWhiteList.length > 0) {
49
+ app.deprecate('[@eggjs/security/lib/helper/shtml] `config.helper.shtml.domainWhiteList` has been deprecate. Please use `config.security.domainWhiteList` instead.');
50
+ if (!isSafeDomain(hostname, shtmlConfig.domainWhiteList)) {
51
+ return '';
52
+ }
53
+ } else {
54
+ return '';
55
+ }
56
+ }
57
+ }
58
+ };
59
+
60
+ // avoid overriding user configuration 'onTagAttr'
61
+ if (shtmlConfig.onTagAttr) {
62
+ const customOnTagAttrHandler = shtmlConfig.onTagAttr;
63
+ shtmlConfig.onTagAttr = function(tag, name, value, isWhiteAttr) {
64
+ const result = customOnTagAttrHandler.apply(this, [ tag, name, value, isWhiteAttr ]);
65
+ if (result !== undefined) {
66
+ return result;
67
+ }
68
+ // fallback to build-in handler
69
+ return shtmlConfig[BUILD_IN_ON_TAG_ATTR]!.apply(this, [ tag, name, value, isWhiteAttr ]);
70
+ };
71
+ } else {
72
+ shtmlConfig.onTagAttr = shtmlConfig[BUILD_IN_ON_TAG_ATTR];
73
+ }
74
+ }
75
+
76
+ return xss(val, shtmlConfig);
77
+ }
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Escape JavaScript to \xHH format
3
+ */
4
+
5
+ // escape \x00-\x7f
6
+ // except 0-9,A-Z,a-z(\x2f-\x3a \x40-\x5b \x60-\x7b)
7
+
8
+ // eslint-disable-next-line
9
+ const MATCH_VULNERABLE_REGEXP = /[\x00-\x2f\x3a-\x40\x5b-\x60\x7b-\x7f]/;
10
+ // eslint-enable-next-line
11
+
12
+ const BASIC_ALPHABETS = new Set('abcdefghijklmnopqrstuvwxyz1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split(''));
13
+
14
+ const map: Record<string, string> = {
15
+ '\t': '\\t',
16
+ '\n': '\\n',
17
+ '\r': '\\r',
18
+ };
19
+
20
+ export default function escapeJavaScript(text: string) {
21
+ const str = '' + text;
22
+ const match = MATCH_VULNERABLE_REGEXP.exec(str);
23
+
24
+ if (!match) {
25
+ return str;
26
+ }
27
+
28
+ let res = '';
29
+ let index = 0;
30
+ let lastIndex = 0;
31
+ let ascii;
32
+
33
+ for (index = match.index; index < str.length; index++) {
34
+ ascii = str[index];
35
+ if (BASIC_ALPHABETS.has(ascii)) {
36
+ continue;
37
+ } else {
38
+ if (map[ascii] === undefined) {
39
+ const code = ascii.charCodeAt(0);
40
+ if (code > 127) {
41
+ continue;
42
+ } else {
43
+ map[ascii] = '\\x' + code.toString(16);
44
+ }
45
+ }
46
+ }
47
+
48
+ if (lastIndex !== index) {
49
+ res += str.substring(lastIndex, index);
50
+ }
51
+
52
+ lastIndex = index + 1;
53
+ res += map[ascii];
54
+ }
55
+
56
+ return lastIndex !== index ? res + str.substring(lastIndex, index) : res;
57
+ }
@@ -0,0 +1,35 @@
1
+ import sjs from './sjs.js';
2
+
3
+ /**
4
+ * escape json
5
+ * for output json in script
6
+ */
7
+
8
+ function sanitizeKey(obj: any) {
9
+ if (typeof obj !== 'object') return obj;
10
+ if (Array.isArray(obj)) return obj;
11
+ if (obj === null) return null;
12
+ if (typeof obj === 'boolean') return obj;
13
+ if (typeof obj === 'number') return obj;
14
+ if (Buffer.isBuffer(obj)) return obj.toString();
15
+
16
+ for (const k in obj) {
17
+ const escapedK = sjs(k);
18
+ if (escapedK !== k) {
19
+ obj[escapedK] = sanitizeKey(obj[k]);
20
+ obj[k] = undefined;
21
+ } else {
22
+ obj[k] = sanitizeKey(obj[k]);
23
+ }
24
+ }
25
+ return obj;
26
+ }
27
+
28
+ export default function jsonEscape(obj: any) {
29
+ return JSON.stringify(sanitizeKey(obj), (_k, v) => {
30
+ if (typeof v === 'string') {
31
+ return sjs(v);
32
+ }
33
+ return v;
34
+ });
35
+ }