chrome-cdp-cli 1.5.0 → 1.6.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,337 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.SecurityManager = void 0;
7
+ const express_rate_limit_1 = __importDefault(require("express-rate-limit"));
8
+ const logger_1 = require("../../utils/logger");
9
+ class SecurityManager {
10
+ constructor(config) {
11
+ this.logger = (0, logger_1.createLogger)({ component: 'SecurityManager' });
12
+ this.config = {
13
+ enableRateLimit: true,
14
+ rateLimitWindowMs: 60 * 1000,
15
+ rateLimitMaxRequests: 100,
16
+ enableRequestValidation: true,
17
+ enableInputSanitization: true,
18
+ allowedHosts: ['localhost', '127.0.0.1'],
19
+ maxRequestBodySize: '10mb',
20
+ enableSecurityHeaders: true,
21
+ ...config
22
+ };
23
+ this.setupRateLimiters();
24
+ this.logger.info('Security manager initialized', { config: this.config });
25
+ }
26
+ setupRateLimiters() {
27
+ this.rateLimiter = (0, express_rate_limit_1.default)({
28
+ windowMs: this.config.rateLimitWindowMs,
29
+ max: this.config.rateLimitMaxRequests,
30
+ message: {
31
+ success: false,
32
+ error: 'Too many requests, please try again later',
33
+ timestamp: Date.now()
34
+ },
35
+ standardHeaders: true,
36
+ legacyHeaders: false,
37
+ skip: (req) => {
38
+ return req.path === '/api/health' || req.path === '/api/status';
39
+ },
40
+ handler: (req, res) => {
41
+ this.logger.logSecurityEvent('rate_limit_exceeded', 'Rate limit exceeded', {
42
+ ip: req.ip,
43
+ path: req.path,
44
+ method: req.method,
45
+ userAgent: req.get('user-agent')
46
+ });
47
+ res.status(429).json({
48
+ success: false,
49
+ error: 'Too many requests, please try again later',
50
+ timestamp: Date.now()
51
+ });
52
+ }
53
+ });
54
+ this.strictRateLimiter = (0, express_rate_limit_1.default)({
55
+ windowMs: this.config.rateLimitWindowMs,
56
+ max: Math.floor(this.config.rateLimitMaxRequests / 4),
57
+ message: {
58
+ success: false,
59
+ error: 'Too many requests for this operation, please try again later',
60
+ timestamp: Date.now()
61
+ },
62
+ standardHeaders: true,
63
+ legacyHeaders: false,
64
+ handler: (req, res) => {
65
+ this.logger.logSecurityEvent('strict_rate_limit_exceeded', 'Strict rate limit exceeded', {
66
+ ip: req.ip,
67
+ path: req.path,
68
+ method: req.method,
69
+ userAgent: req.get('user-agent')
70
+ });
71
+ res.status(429).json({
72
+ success: false,
73
+ error: 'Too many requests for this operation, please try again later',
74
+ timestamp: Date.now()
75
+ });
76
+ }
77
+ });
78
+ }
79
+ getRateLimiter() {
80
+ return this.config.enableRateLimit ? this.rateLimiter : this.noOpMiddleware;
81
+ }
82
+ getStrictRateLimiter() {
83
+ return this.config.enableRateLimit ? this.strictRateLimiter : this.noOpMiddleware;
84
+ }
85
+ getSecurityHeadersMiddleware() {
86
+ return (_req, res, next) => {
87
+ if (!this.config.enableSecurityHeaders) {
88
+ return next();
89
+ }
90
+ res.setHeader('X-Content-Type-Options', 'nosniff');
91
+ res.setHeader('X-Frame-Options', 'DENY');
92
+ res.setHeader('X-XSS-Protection', '1; mode=block');
93
+ res.setHeader('Referrer-Policy', 'strict-origin-when-cross-origin');
94
+ res.setHeader('Content-Security-Policy', "default-src 'self'");
95
+ res.removeHeader('X-Powered-By');
96
+ next();
97
+ };
98
+ }
99
+ getRequestValidationMiddleware() {
100
+ return (req, res, next) => {
101
+ if (!this.config.enableRequestValidation) {
102
+ return next();
103
+ }
104
+ try {
105
+ const allowedMethods = ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'];
106
+ if (!allowedMethods.includes(req.method)) {
107
+ this.logger.logSecurityEvent('invalid_method', 'Invalid HTTP method', {
108
+ method: req.method,
109
+ ip: req.ip,
110
+ path: req.path
111
+ });
112
+ return res.status(405).json({
113
+ success: false,
114
+ error: 'Method not allowed',
115
+ timestamp: Date.now()
116
+ });
117
+ }
118
+ if (['POST', 'PUT'].includes(req.method)) {
119
+ const contentType = req.get('content-type');
120
+ if (!contentType || !contentType.includes('application/json')) {
121
+ this.logger.logSecurityEvent('invalid_content_type', 'Invalid content type', {
122
+ contentType,
123
+ method: req.method,
124
+ ip: req.ip,
125
+ path: req.path
126
+ });
127
+ return res.status(400).json({
128
+ success: false,
129
+ error: 'Content-Type must be application/json',
130
+ timestamp: Date.now()
131
+ });
132
+ }
133
+ }
134
+ const contentLength = req.get('content-length');
135
+ if (contentLength) {
136
+ const maxSize = this.parseSize(this.config.maxRequestBodySize);
137
+ if (parseInt(contentLength) > maxSize) {
138
+ this.logger.logSecurityEvent('request_too_large', 'Request body too large', {
139
+ contentLength: parseInt(contentLength),
140
+ maxSize,
141
+ ip: req.ip,
142
+ path: req.path
143
+ });
144
+ return res.status(413).json({
145
+ success: false,
146
+ error: 'Request body too large',
147
+ timestamp: Date.now()
148
+ });
149
+ }
150
+ }
151
+ const userAgent = req.get('user-agent');
152
+ if (!userAgent || this.isSuspiciousUserAgent(userAgent)) {
153
+ this.logger.logSecurityEvent('suspicious_user_agent', 'Suspicious user agent', {
154
+ userAgent,
155
+ ip: req.ip,
156
+ path: req.path
157
+ });
158
+ }
159
+ next();
160
+ }
161
+ catch (error) {
162
+ this.logger.error('Request validation error:', error);
163
+ res.status(500).json({
164
+ success: false,
165
+ error: 'Request validation failed',
166
+ timestamp: Date.now()
167
+ });
168
+ }
169
+ };
170
+ }
171
+ getInputSanitizationMiddleware() {
172
+ return (req, res, next) => {
173
+ if (!this.config.enableInputSanitization) {
174
+ return next();
175
+ }
176
+ try {
177
+ if (req.body && typeof req.body === 'object') {
178
+ req.body = this.sanitizeObject(req.body);
179
+ }
180
+ if (req.query && typeof req.query === 'object') {
181
+ req.query = this.sanitizeObject(req.query);
182
+ }
183
+ if (req.params && typeof req.params === 'object') {
184
+ req.params = this.sanitizeObject(req.params);
185
+ }
186
+ next();
187
+ }
188
+ catch (error) {
189
+ this.logger.error('Input sanitization error:', error);
190
+ res.status(500).json({
191
+ success: false,
192
+ error: 'Input sanitization failed',
193
+ timestamp: Date.now()
194
+ });
195
+ }
196
+ };
197
+ }
198
+ getHostValidationMiddleware() {
199
+ return (req, res, next) => {
200
+ if (req.path === '/api/connect' && req.method === 'POST') {
201
+ const { host } = req.body;
202
+ if (host && !this.isAllowedHost(host)) {
203
+ this.logger.logSecurityEvent('unauthorized_host', 'Unauthorized host connection attempt', {
204
+ requestedHost: host,
205
+ clientIP: req.ip,
206
+ allowedHosts: this.config.allowedHosts
207
+ });
208
+ res.status(403).json({
209
+ success: false,
210
+ error: 'Connection to this host is not allowed for security reasons',
211
+ timestamp: Date.now()
212
+ });
213
+ return;
214
+ }
215
+ }
216
+ next();
217
+ };
218
+ }
219
+ getSecurityLoggingMiddleware() {
220
+ return (req, res, next) => {
221
+ const startTime = Date.now();
222
+ this.logger.debug('Security: Request received', {
223
+ method: req.method,
224
+ path: req.path,
225
+ ip: req.ip,
226
+ userAgent: req.get('user-agent'),
227
+ contentType: req.get('content-type'),
228
+ contentLength: req.get('content-length')
229
+ });
230
+ res.on('finish', () => {
231
+ const duration = Date.now() - startTime;
232
+ if (res.statusCode >= 400) {
233
+ this.logger.logSecurityEvent('error_response', 'Error response sent', {
234
+ method: req.method,
235
+ path: req.path,
236
+ statusCode: res.statusCode,
237
+ duration,
238
+ ip: req.ip,
239
+ userAgent: req.get('user-agent')
240
+ });
241
+ }
242
+ });
243
+ next();
244
+ };
245
+ }
246
+ isAllowedHost(host) {
247
+ if (host === 'localhost' || host === '127.0.0.1') {
248
+ return true;
249
+ }
250
+ if (this.config.allowedHosts.includes(host)) {
251
+ return true;
252
+ }
253
+ if (host.startsWith('192.168.') ||
254
+ host.startsWith('10.') ||
255
+ host.match(/^172\.(1[6-9]|2[0-9]|3[0-1])\./)) {
256
+ return true;
257
+ }
258
+ return false;
259
+ }
260
+ isSuspiciousUserAgent(userAgent) {
261
+ const suspiciousPatterns = [
262
+ /bot/i,
263
+ /crawler/i,
264
+ /spider/i,
265
+ /scraper/i,
266
+ /curl/i,
267
+ /wget/i,
268
+ /python/i,
269
+ /^$/
270
+ ];
271
+ return suspiciousPatterns.some(pattern => pattern.test(userAgent));
272
+ }
273
+ sanitizeObject(obj) {
274
+ if (obj === null || obj === undefined) {
275
+ return obj;
276
+ }
277
+ if (typeof obj === 'string') {
278
+ return this.sanitizeString(obj);
279
+ }
280
+ if (typeof obj === 'number' || typeof obj === 'boolean') {
281
+ return obj;
282
+ }
283
+ if (Array.isArray(obj)) {
284
+ return obj.map(item => this.sanitizeObject(item));
285
+ }
286
+ if (typeof obj === 'object') {
287
+ const sanitized = {};
288
+ for (const [key, value] of Object.entries(obj)) {
289
+ const sanitizedKey = this.sanitizeString(key);
290
+ sanitized[sanitizedKey] = this.sanitizeObject(value);
291
+ }
292
+ return sanitized;
293
+ }
294
+ return obj;
295
+ }
296
+ sanitizeString(str) {
297
+ if (typeof str !== 'string') {
298
+ return str;
299
+ }
300
+ let sanitized = str.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, '');
301
+ if (sanitized.length > 10000) {
302
+ sanitized = sanitized.substring(0, 10000);
303
+ this.logger.warn('String truncated during sanitization', {
304
+ originalLength: str.length,
305
+ truncatedLength: sanitized.length
306
+ });
307
+ }
308
+ return sanitized;
309
+ }
310
+ parseSize(sizeStr) {
311
+ const match = sizeStr.match(/^(\d+(?:\.\d+)?)\s*(b|kb|mb|gb)$/i);
312
+ if (!match) {
313
+ return 10 * 1024 * 1024;
314
+ }
315
+ const value = parseFloat(match[1]);
316
+ const unit = match[2].toLowerCase();
317
+ switch (unit) {
318
+ case 'b': return value;
319
+ case 'kb': return value * 1024;
320
+ case 'mb': return value * 1024 * 1024;
321
+ case 'gb': return value * 1024 * 1024 * 1024;
322
+ default: return 10 * 1024 * 1024;
323
+ }
324
+ }
325
+ noOpMiddleware(_req, _res, next) {
326
+ next();
327
+ }
328
+ updateConfig(newConfig) {
329
+ this.config = { ...this.config, ...newConfig };
330
+ this.setupRateLimiters();
331
+ this.logger.info('Security configuration updated', { config: this.config });
332
+ }
333
+ getConfig() {
334
+ return { ...this.config };
335
+ }
336
+ }
337
+ exports.SecurityManager = SecurityManager;