@sun-asterisk/sunlint 1.3.18 → 1.3.19

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 (34) hide show
  1. package/config/rules/enhanced-rules-registry.json +77 -18
  2. package/core/cli-program.js +2 -1
  3. package/core/github-annotate-service.js +89 -0
  4. package/core/output-service.js +25 -0
  5. package/core/summary-report-service.js +30 -30
  6. package/package.json +3 -2
  7. package/rules/common/C014_dependency_injection/symbol-based-analyzer.js +392 -280
  8. package/rules/common/C017_constructor_logic/analyzer.js +137 -503
  9. package/rules/common/C017_constructor_logic/config.json +50 -0
  10. package/rules/common/C017_constructor_logic/symbol-based-analyzer.js +463 -0
  11. package/rules/security/S006_no_plaintext_recovery_codes/symbol-based-analyzer.js +463 -21
  12. package/rules/security/S011_secure_guid_generation/README.md +255 -0
  13. package/rules/security/S011_secure_guid_generation/analyzer.js +135 -0
  14. package/rules/security/S011_secure_guid_generation/config.json +56 -0
  15. package/rules/security/S011_secure_guid_generation/symbol-based-analyzer.js +609 -0
  16. package/rules/security/S028_file_upload_size_limits/README.md +537 -0
  17. package/rules/security/S028_file_upload_size_limits/analyzer.js +202 -0
  18. package/rules/security/S028_file_upload_size_limits/config.json +186 -0
  19. package/rules/security/S028_file_upload_size_limits/symbol-based-analyzer.js +530 -0
  20. package/rules/security/S041_session_token_invalidation/README.md +303 -0
  21. package/rules/security/S041_session_token_invalidation/analyzer.js +242 -0
  22. package/rules/security/S041_session_token_invalidation/config.json +175 -0
  23. package/rules/security/S041_session_token_invalidation/regex-based-analyzer.js +411 -0
  24. package/rules/security/S041_session_token_invalidation/symbol-based-analyzer.js +674 -0
  25. package/rules/security/S044_re_authentication_required/README.md +136 -0
  26. package/rules/security/S044_re_authentication_required/analyzer.js +242 -0
  27. package/rules/security/S044_re_authentication_required/config.json +161 -0
  28. package/rules/security/S044_re_authentication_required/regex-based-analyzer.js +329 -0
  29. package/rules/security/S044_re_authentication_required/symbol-based-analyzer.js +537 -0
  30. package/rules/security/S045_brute_force_protection/README.md +345 -0
  31. package/rules/security/S045_brute_force_protection/analyzer.js +336 -0
  32. package/rules/security/S045_brute_force_protection/config.json +139 -0
  33. package/rules/security/S045_brute_force_protection/symbol-based-analyzer.js +646 -0
  34. package/rules/common/C017_constructor_logic/semantic-analyzer.js +0 -340
@@ -0,0 +1,537 @@
1
+ # S028 - Limit Upload File Size and Number of Files Per User
2
+
3
+ ## 📋 Tổng quan
4
+
5
+ **Rule ID:** S028
6
+ **Mức độ:** Medium (⚠️)
7
+ **Danh mục:** Security
8
+ **OWASP:** A04:2021 - Insecure Design
9
+ **CWE:** CWE-400 - Uncontrolled Resource Consumption
10
+
11
+ ### Mục đích
12
+
13
+ Ngăn chặn các cuộc tấn công **Denial of Service (DoS)** và lạm dụng tài nguyên bằng cách đảm bảo rằng mọi file upload endpoint đều có giới hạn:
14
+
15
+ - **Kích thước file** (file size limit)
16
+ - **Số lượng file** (file count limit)
17
+ - **Tổng kích thước request** (request size limit)
18
+
19
+ ### Tại sao quan trọng?
20
+
21
+ 1. **Ngăn chặn DoS attacks**: Attacker có thể upload file cực lớn (vài GB) để làm cạn kiệt:
22
+
23
+ - Disk space
24
+ - Memory
25
+ - Network bandwidth
26
+ - Processing power
27
+
28
+ 2. **Ngăn storage abuse**: User có thể upload hàng nghìn file nhỏ để chiếm dụng storage
29
+
30
+ 3. **Bảo vệ service availability**: Đảm bảo server không bị quá tải bởi upload requests
31
+
32
+ ---
33
+
34
+ ## 🎯 Phạm vi áp dụng
35
+
36
+ ### Framework hỗ trợ:
37
+
38
+ - ✅ **Node.js**: multer, express
39
+ - ✅ **NestJS**: FileInterceptor, FilesInterceptor
40
+ - ✅ **Java/Spring Boot**: multipart configuration
41
+ - ⚠️ **Python/Flask**: (Future support)
42
+ - ⚠️ **PHP**: (Future support)
43
+
44
+ ### File types:
45
+
46
+ - `.ts`, `.js`, `.tsx`, `.jsx` (TypeScript/JavaScript)
47
+ - `.java` (Java - properties files)
48
+
49
+ ---
50
+
51
+ ## 🚨 Các vi phạm phát hiện
52
+
53
+ ### 1. Multer không có limits (HIGH)
54
+
55
+ ```javascript
56
+ // ❌ VIOLATION: Multer thiếu giới hạn hoàn toàn
57
+ const upload = multer({ dest: "uploads/" });
58
+
59
+ // ❌ VIOLATION: Multer thiếu limits object
60
+ const upload = multer({
61
+ storage: diskStorage({ destination: "./uploads" }),
62
+ });
63
+
64
+ // ❌ VIOLATION: Multer có limits nhưng thiếu fileSize
65
+ const upload = multer({
66
+ limits: {
67
+ files: 5, // Chỉ giới hạn số lượng, không giới hạn size
68
+ },
69
+ });
70
+ ```
71
+
72
+ **Severity:** ERROR
73
+ **Message:** "Multer configuration missing size limits - add limits.fileSize and limits.files"
74
+
75
+ ---
76
+
77
+ ### 2. File size limit quá cao (HIGH/MEDIUM)
78
+
79
+ ```javascript
80
+ // ❌ HIGH RISK: File size > 50MB
81
+ const upload = multer({
82
+ limits: {
83
+ fileSize: 100 * 1024 * 1024, // 100MB - QUÁ CAO!
84
+ },
85
+ });
86
+
87
+ // ⚠️ MEDIUM RISK: File size > 20MB
88
+ const upload = multer({
89
+ limits: {
90
+ fileSize: 30 * 1024 * 1024, // 30MB - CAO HƠN KHUYẾN NGHỊ
91
+ },
92
+ });
93
+ ```
94
+
95
+ **Severity:** ERROR (> 50MB), WARNING (> 20MB)
96
+ **Message:** "File size limit too high (100MB) - recommend ≤ 10MB"
97
+
98
+ ---
99
+
100
+ ### 3. FileInterceptor thiếu limits (HIGH)
101
+
102
+ ```typescript
103
+ // ❌ VIOLATION: FileInterceptor không có limits
104
+ @Post('/upload')
105
+ @UseInterceptors(FileInterceptor('file'))
106
+ uploadFile(@UploadedFile() file: Express.Multer.File) {
107
+ return this.fileService.save(file);
108
+ }
109
+
110
+ // ❌ VIOLATION: FilesInterceptor không có limits
111
+ @Post('/gallery')
112
+ @UseInterceptors(FilesInterceptor('images'))
113
+ uploadGallery(@UploadedFiles() files: Array<Express.Multer.File>) {
114
+ return this.galleryService.save(files);
115
+ }
116
+ ```
117
+
118
+ **Severity:** ERROR
119
+ **Message:** "FileInterceptor missing 'limits' configuration - add { limits: { fileSize: 10485760 } }"
120
+
121
+ ---
122
+
123
+ ### 4. Express middleware thiếu body limit (MEDIUM)
124
+
125
+ ```javascript
126
+ // ⚠️ WARNING: express.json() không giới hạn
127
+ app.use(express.json());
128
+
129
+ // ⚠️ WARNING: express.urlencoded() không giới hạn
130
+ app.use(express.urlencoded({ extended: true }));
131
+ ```
132
+
133
+ **Severity:** WARNING
134
+ **Message:** "express.json() missing body size limit - add { limit: '10mb' }"
135
+
136
+ ---
137
+
138
+ ## ✅ Cách sửa đúng
139
+
140
+ ### 1. Multer với limits hợp lý
141
+
142
+ ```javascript
143
+ // ✅ GOOD: Multer với limits đầy đủ
144
+ const upload = multer({
145
+ dest: "uploads/",
146
+ limits: {
147
+ fileSize: 10 * 1024 * 1024, // 10MB
148
+ files: 5, // Tối đa 5 files
149
+ },
150
+ });
151
+
152
+ // ✅ GOOD: Multer với fileFilter validation
153
+ const upload = multer({
154
+ storage: diskStorage({
155
+ destination: "./uploads",
156
+ filename: (req, file, cb) => {
157
+ const uniqueName = `${Date.now()}-${file.originalname}`;
158
+ cb(null, uniqueName);
159
+ },
160
+ }),
161
+ limits: {
162
+ fileSize: 5 * 1024 * 1024, // 5MB per file
163
+ files: 3, // Max 3 files
164
+ },
165
+ fileFilter: (req, file, cb) => {
166
+ // Additional validation
167
+ const allowedTypes = ["image/jpeg", "image/png"];
168
+ if (allowedTypes.includes(file.mimetype)) {
169
+ cb(null, true);
170
+ } else {
171
+ cb(new Error("Invalid file type"), false);
172
+ }
173
+ },
174
+ });
175
+ ```
176
+
177
+ ---
178
+
179
+ ### 2. NestJS FileInterceptor với limits
180
+
181
+ ```typescript
182
+ // ✅ GOOD: FileInterceptor với limits
183
+ @Post('/avatar')
184
+ @UseInterceptors(FileInterceptor('file', {
185
+ limits: {
186
+ fileSize: 5 * 1024 * 1024 // 5MB
187
+ }
188
+ }))
189
+ uploadAvatar(@UploadedFile() file: Express.Multer.File) {
190
+ return this.userService.updateAvatar(file);
191
+ }
192
+
193
+ // ✅ GOOD: FilesInterceptor với limits
194
+ @Post('/documents')
195
+ @UseInterceptors(FilesInterceptor('documents', 10, {
196
+ limits: {
197
+ fileSize: 10 * 1024 * 1024, // 10MB per file
198
+ files: 10 // Max 10 files (also in decorator)
199
+ }
200
+ }))
201
+ uploadDocuments(@UploadedFiles() files: Array<Express.Multer.File>) {
202
+ return this.documentService.save(files);
203
+ }
204
+
205
+ // ✅ GOOD: FileFieldsInterceptor với limits
206
+ @Post('/profile')
207
+ @UseInterceptors(FileFieldsInterceptor([
208
+ { name: 'avatar', maxCount: 1 },
209
+ { name: 'documents', maxCount: 5 }
210
+ ], {
211
+ limits: {
212
+ fileSize: 5 * 1024 * 1024
213
+ }
214
+ }))
215
+ updateProfile(
216
+ @UploadedFiles() files: { avatar?: Express.Multer.File[], documents?: Express.Multer.File[] }
217
+ ) {
218
+ return this.profileService.update(files);
219
+ }
220
+ ```
221
+
222
+ ---
223
+
224
+ ### 3. Express middleware với body limit
225
+
226
+ ```javascript
227
+ // ✅ GOOD: express.json với limit
228
+ app.use(express.json({ limit: "10mb" }));
229
+
230
+ // ✅ GOOD: express.urlencoded với limit
231
+ app.use(
232
+ express.urlencoded({
233
+ limit: "10mb",
234
+ extended: true,
235
+ })
236
+ );
237
+
238
+ // ✅ GOOD: Custom limit cho specific routes
239
+ const uploadRouter = express.Router();
240
+ uploadRouter.use(express.json({ limit: "5mb" }));
241
+
242
+ uploadRouter.post("/images", upload.single("image"), (req, res) => {
243
+ // Handle upload
244
+ });
245
+ ```
246
+
247
+ ---
248
+
249
+ ### 4. Spring Boot configuration
250
+
251
+ ```properties
252
+ # ✅ GOOD: application.properties
253
+ spring.servlet.multipart.max-file-size=10MB
254
+ spring.servlet.multipart.max-request-size=20MB
255
+ spring.servlet.multipart.enabled=true
256
+ ```
257
+
258
+ ```yaml
259
+ # ✅ GOOD: application.yml
260
+ spring:
261
+ servlet:
262
+ multipart:
263
+ max-file-size: 10MB
264
+ max-request-size: 20MB
265
+ enabled: true
266
+ ```
267
+
268
+ ---
269
+
270
+ ## 📊 Ngưỡng khuyến nghị
271
+
272
+ | Loại | Khuyến nghị | Medium Risk | High Risk |
273
+ | ---------------- | ----------- | ----------- | ---------- |
274
+ | **File size** | ≤ 10MB | > 20MB | > 50MB |
275
+ | **File count** | ≤ 10 files | > 20 files | > 50 files |
276
+ | **Request size** | ≤ 20MB | > 40MB | > 100MB |
277
+
278
+ ### Giải thích:
279
+
280
+ - **10MB per file**: Đủ cho hầu hết use cases (images, documents, PDFs)
281
+ - **10 files**: Ngăn bulk upload abuse
282
+ - **20MB total request**: Cho phép multiple files nhưng không quá lớn
283
+
284
+ ### Các trường hợp đặc biệt:
285
+
286
+ ```javascript
287
+ // Video uploads - có thể cao hơn nhưng cần validation
288
+ const videoUpload = multer({
289
+ limits: {
290
+ fileSize: 50 * 1024 * 1024, // 50MB for video
291
+ files: 1, // Only 1 video
292
+ },
293
+ fileFilter: (req, file, cb) => {
294
+ if (file.mimetype.startsWith("video/")) {
295
+ cb(null, true);
296
+ } else {
297
+ cb(new Error("Only video files allowed"), false);
298
+ }
299
+ },
300
+ });
301
+
302
+ // Archive files - cần kiểm tra content
303
+ const archiveUpload = multer({
304
+ limits: {
305
+ fileSize: 20 * 1024 * 1024, // 20MB for zip/tar
306
+ },
307
+ fileFilter: (req, file, cb) => {
308
+ const allowedTypes = ["application/zip", "application/x-tar"];
309
+ if (allowedTypes.includes(file.mimetype)) {
310
+ cb(null, true);
311
+ } else {
312
+ cb(new Error("Only archive files allowed"), false);
313
+ }
314
+ },
315
+ });
316
+ ```
317
+
318
+ ---
319
+
320
+ ## 🔍 Chi tiết kỹ thuật
321
+
322
+ ### Pattern detection:
323
+
324
+ 1. **Multer initialization:**
325
+
326
+ ```javascript
327
+ multer({ limits: { fileSize, files } });
328
+ ```
329
+
330
+ 2. **FileInterceptor decorator:**
331
+
332
+ ```typescript
333
+ @UseInterceptors(FileInterceptor('field', { limits }))
334
+ ```
335
+
336
+ 3. **Express middleware:**
337
+ ```javascript
338
+ express.json({ limit: "10mb" });
339
+ ```
340
+
341
+ ### AST nodes checked:
342
+
343
+ - `SyntaxKind.CallExpression` - multer(), express.json()
344
+ - `SyntaxKind.Decorator` - @UseInterceptors()
345
+ - Object literals - configuration objects
346
+
347
+ ### Deduplication:
348
+
349
+ Rule sử dụng `reportedLines` Set để tránh báo duplicate errors trên cùng một dòng.
350
+
351
+ ---
352
+
353
+ ## 🎯 Best Practices
354
+
355
+ ### 1. Layered defense
356
+
357
+ ```typescript
358
+ // ✅ Multiple layers of protection
359
+ @Post('/upload')
360
+ @UseGuards(AuthGuard) // 1. Authentication
361
+ @UseInterceptors(FileInterceptor('file', {
362
+ limits: {
363
+ fileSize: 10 * 1024 * 1024 // 2. Size limit
364
+ },
365
+ fileFilter: (req, file, cb) => {
366
+ // 3. Type validation
367
+ const allowedTypes = ['image/jpeg', 'image/png'];
368
+ if (!allowedTypes.includes(file.mimetype)) {
369
+ return cb(new Error('Invalid file type'), false);
370
+ }
371
+ cb(null, true);
372
+ }
373
+ }))
374
+ async uploadFile(
375
+ @UploadedFile() file: Express.Multer.File,
376
+ @Req() req
377
+ ) {
378
+ // 4. Additional server-side validation
379
+ if (!file) {
380
+ throw new BadRequestException('No file uploaded');
381
+ }
382
+
383
+ // 5. Magic number validation
384
+ const isValid = await this.fileService.validateFileContent(file);
385
+ if (!isValid) {
386
+ await this.fileService.deleteFile(file.path);
387
+ throw new BadRequestException('Invalid file content');
388
+ }
389
+
390
+ // 6. User quota check
391
+ const userStorage = await this.userService.getStorageUsage(req.user.id);
392
+ if (userStorage + file.size > USER_STORAGE_QUOTA) {
393
+ await this.fileService.deleteFile(file.path);
394
+ throw new BadRequestException('Storage quota exceeded');
395
+ }
396
+
397
+ return this.fileService.save(file, req.user.id);
398
+ }
399
+ ```
400
+
401
+ ---
402
+
403
+ ### 2. Logging and monitoring
404
+
405
+ ```typescript
406
+ // ✅ Log upload attempts and rejections
407
+ @Post('/upload')
408
+ @UseInterceptors(FileInterceptor('file', {
409
+ limits: { fileSize: 10 * 1024 * 1024 }
410
+ }))
411
+ async uploadFile(@UploadedFile() file, @Req() req) {
412
+ try {
413
+ this.logger.log({
414
+ event: 'file_upload_attempt',
415
+ userId: req.user.id,
416
+ filename: file.originalname,
417
+ size: file.size,
418
+ mimetype: file.mimetype,
419
+ ip: req.ip
420
+ });
421
+
422
+ const result = await this.fileService.save(file);
423
+
424
+ this.logger.log({
425
+ event: 'file_upload_success',
426
+ userId: req.user.id,
427
+ fileId: result.id
428
+ });
429
+
430
+ return result;
431
+ } catch (error) {
432
+ this.logger.error({
433
+ event: 'file_upload_failed',
434
+ userId: req.user.id,
435
+ filename: file?.originalname,
436
+ size: file?.size,
437
+ error: error.message,
438
+ ip: req.ip
439
+ });
440
+ throw error;
441
+ }
442
+ }
443
+ ```
444
+
445
+ ---
446
+
447
+ ### 3. Rate limiting
448
+
449
+ ```typescript
450
+ // ✅ Combine with rate limiting
451
+ import { Throttle } from "@nestjs/throttler";
452
+
453
+ @Controller("upload")
454
+ export class UploadController {
455
+ @Post("/avatar")
456
+ @Throttle(5, 60) // Max 5 uploads per 60 seconds
457
+ @UseInterceptors(
458
+ FileInterceptor("file", {
459
+ limits: { fileSize: 5 * 1024 * 1024 },
460
+ })
461
+ )
462
+ uploadAvatar(@UploadedFile() file) {
463
+ return this.userService.updateAvatar(file);
464
+ }
465
+ }
466
+ ```
467
+
468
+ ---
469
+
470
+ ## 🔗 Liên quan
471
+
472
+ ### Rules liên quan:
473
+
474
+ - **S025** - Server-side Validation: Validate file type, content
475
+ - **S055** - Content-Type Validation: Validate Content-Type header
476
+
477
+ ### OWASP Top 10:
478
+
479
+ - **A04:2021** - Insecure Design
480
+ - **A05:2021** - Security Misconfiguration
481
+
482
+ ### CWE:
483
+
484
+ - **CWE-400** - Uncontrolled Resource Consumption
485
+ - **CWE-770** - Allocation of Resources Without Limits or Throttling
486
+
487
+ ---
488
+
489
+ ## 📚 Tài liệu tham khảo
490
+
491
+ 1. **OWASP File Upload Cheat Sheet**
492
+ https://cheatsheetseries.owasp.org/cheatsheets/File_Upload_Cheat_Sheet.html
493
+
494
+ 2. **Multer Documentation**
495
+ https://github.com/expressjs/multer
496
+
497
+ 3. **NestJS File Upload**
498
+ https://docs.nestjs.com/techniques/file-upload
499
+
500
+ 4. **CWE-400: Uncontrolled Resource Consumption**
501
+ https://cwe.mitre.org/data/definitions/400.html
502
+
503
+ ---
504
+
505
+ ## 🚀 Testing
506
+
507
+ ### Test với SunLint:
508
+
509
+ ```bash
510
+ # Test rule trên một file
511
+ node cli.js --rule=S028 --input=src/upload/upload.controller.ts --engine=heuristic
512
+
513
+ # Test trên toàn bộ project
514
+ node cli.js --rule=S028 --input=src --engine=heuristic
515
+
516
+ # Test với verbose output
517
+ SUNLINT_DEBUG=1 node cli.js --rule=S028 --input=src --engine=heuristic
518
+ ```
519
+
520
+ ### Expected violations:
521
+
522
+ - ❌ Multer thiếu limits
523
+ - ❌ FileInterceptor thiếu limits
524
+ - ⚠️ Express middleware thiếu limit
525
+ - ❌ File size > 50MB (high risk)
526
+ - ⚠️ File size > 20MB (medium risk)
527
+
528
+ ---
529
+
530
+ ## ✨ Version History
531
+
532
+ - **v1.0** (2025-01-23): Initial release
533
+ - Support Node.js/NestJS file upload detection
534
+ - Multer configuration checking
535
+ - FileInterceptor validation
536
+ - Express middleware checking
537
+ - Threshold validation (10MB/20MB/50MB)