@sun-asterisk/sunlint 1.3.47 → 1.3.49

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 (185) hide show
  1. package/config/rules/rules-registry-generated.json +1717 -282
  2. package/core/architecture-integration.js +57 -15
  3. package/core/cli-action-handler.js +51 -36
  4. package/core/config-manager.js +6 -0
  5. package/core/config-merger.js +33 -0
  6. package/core/config-validator.js +37 -2
  7. package/core/file-targeting-service.js +148 -15
  8. package/core/init-command.js +118 -70
  9. package/core/output-service.js +12 -3
  10. package/core/project-detector.js +517 -0
  11. package/core/scoring-service.js +12 -6
  12. package/core/summary-report-service.js +9 -4
  13. package/core/tui-select.js +245 -0
  14. package/engines/arch-detect/rules/layered/l001-presentation-layer.js +7 -15
  15. package/engines/arch-detect/rules/layered/l002-business-layer.js +7 -15
  16. package/engines/arch-detect/rules/layered/l003-data-layer.js +7 -15
  17. package/engines/arch-detect/rules/layered/l004-model-layer.js +7 -15
  18. package/engines/arch-detect/rules/layered/l005-layer-separation.js +22 -2
  19. package/engines/arch-detect/rules/layered/l006-dependency-direction.js +8 -5
  20. package/engines/arch-detect/rules/modular/m005-no-deep-imports.js +67 -29
  21. package/engines/arch-detect/rules/presentation/pr001-view-layer.js +16 -9
  22. package/engines/arch-detect/rules/presentation/pr006-router-layer.js +33 -8
  23. package/engines/arch-detect/rules/presentation/pr007-interactor-layer.js +35 -6
  24. package/engines/arch-detect/rules/project-scanner/ps003-framework-detection.js +56 -10
  25. package/engines/impact/cli.js +54 -39
  26. package/engines/impact/config/default-config.js +105 -5
  27. package/engines/impact/core/impact-analyzer.js +12 -15
  28. package/engines/impact/core/utils/gitignore-parser.js +123 -0
  29. package/engines/impact/core/utils/method-call-graph.js +272 -87
  30. package/origin-rules/dart-en.md +1 -1
  31. package/origin-rules/go-en.md +231 -0
  32. package/origin-rules/php-en.md +107 -0
  33. package/origin-rules/python-en.md +113 -0
  34. package/origin-rules/ruby-en.md +607 -0
  35. package/package.json +1 -1
  36. package/scripts/copy-arch-detect.js +5 -1
  37. package/scripts/copy-impact-analyzer.js +5 -1
  38. package/scripts/generate-rules-registry.js +30 -14
  39. package/skill-assets/sunlint-code-quality/SKILL.md +3 -2
  40. package/skill-assets/sunlint-code-quality/rules/dart/C006-verb-noun-functions.md +45 -0
  41. package/skill-assets/sunlint-code-quality/rules/dart/C013-no-dead-code.md +53 -0
  42. package/skill-assets/sunlint-code-quality/rules/dart/C014-dependency-injection.md +92 -0
  43. package/skill-assets/sunlint-code-quality/rules/dart/C017-no-constructor-logic.md +62 -0
  44. package/skill-assets/sunlint-code-quality/rules/dart/C018-generic-errors.md +57 -0
  45. package/skill-assets/sunlint-code-quality/rules/dart/C019-error-log-level.md +50 -0
  46. package/skill-assets/sunlint-code-quality/rules/dart/C020-no-unused-imports.md +46 -0
  47. package/skill-assets/sunlint-code-quality/rules/dart/C022-no-unused-variables.md +50 -0
  48. package/skill-assets/sunlint-code-quality/rules/dart/C023-no-duplicate-names.md +56 -0
  49. package/skill-assets/sunlint-code-quality/rules/dart/C024-centralize-constants.md +75 -0
  50. package/skill-assets/sunlint-code-quality/rules/dart/C029-catch-log-root-cause.md +53 -0
  51. package/skill-assets/sunlint-code-quality/rules/dart/C030-custom-error-classes.md +86 -0
  52. package/skill-assets/sunlint-code-quality/rules/dart/C033-separate-data-access.md +90 -0
  53. package/skill-assets/sunlint-code-quality/rules/dart/C035-error-context-logging.md +62 -0
  54. package/skill-assets/sunlint-code-quality/rules/dart/C041-no-hardcoded-secrets.md +75 -0
  55. package/skill-assets/sunlint-code-quality/rules/dart/C042-boolean-naming.md +73 -0
  56. package/skill-assets/sunlint-code-quality/rules/dart/C052-widget-parsing.md +84 -0
  57. package/skill-assets/sunlint-code-quality/rules/dart/C060-superclass-logic.md +91 -0
  58. package/skill-assets/sunlint-code-quality/rules/dart/C067-no-hardcoded-config.md +108 -0
  59. package/skill-assets/sunlint-code-quality/rules/go/G001-explicit-error-handling.md +53 -0
  60. package/skill-assets/sunlint-code-quality/rules/go/G002-context-first-argument.md +44 -0
  61. package/skill-assets/sunlint-code-quality/rules/go/G003-receiver-consistency.md +38 -0
  62. package/skill-assets/sunlint-code-quality/rules/go/G004-avoid-panic.md +49 -0
  63. package/skill-assets/sunlint-code-quality/rules/go/G005-goroutine-leak-prevention.md +49 -0
  64. package/skill-assets/sunlint-code-quality/rules/go/G006-interface-consumer-definition.md +45 -0
  65. package/skill-assets/sunlint-code-quality/rules/go/GN001-gin-binding-validation.md +57 -0
  66. package/skill-assets/sunlint-code-quality/rules/go/GN002-gin-error-response.md +48 -0
  67. package/skill-assets/sunlint-code-quality/rules/go/GN003-graceful-shutdown.md +57 -0
  68. package/skill-assets/sunlint-code-quality/rules/go/GN004-gin-route-logical-grouping.md +54 -0
  69. package/skill-assets/sunlint-code-quality/rules/go-gin/AGENTS.md +149 -0
  70. package/skill-assets/sunlint-code-quality/rules/go-gin/GN001-abort-after-response.md +75 -0
  71. package/skill-assets/sunlint-code-quality/rules/go-gin/GN002-request-context.md +64 -0
  72. package/skill-assets/sunlint-code-quality/rules/go-gin/GN003-bind-error-handling.md +70 -0
  73. package/skill-assets/sunlint-code-quality/rules/go-gin/GN004-dependency-injection.md +78 -0
  74. package/skill-assets/sunlint-code-quality/rules/go-gin/GN005-route-groups-middleware.md +71 -0
  75. package/skill-assets/sunlint-code-quality/rules/go-gin/GN006-http-status-codes.md +91 -0
  76. package/skill-assets/sunlint-code-quality/rules/go-gin/GN007-release-mode.md +64 -0
  77. package/skill-assets/sunlint-code-quality/rules/go-gin/GN008-struct-validation-tags.md +90 -0
  78. package/skill-assets/sunlint-code-quality/rules/go-gin/GN009-recovery-middleware.md +68 -0
  79. package/skill-assets/sunlint-code-quality/rules/go-gin/GN010-context-scope.md +68 -0
  80. package/skill-assets/sunlint-code-quality/rules/go-gin/GN011-middleware-concerns.md +92 -0
  81. package/skill-assets/sunlint-code-quality/rules/go-gin/GN012-no-log-sensitive.md +84 -0
  82. package/skill-assets/sunlint-code-quality/rules/java/J001-try-with-resources.md +86 -0
  83. package/skill-assets/sunlint-code-quality/rules/java/J002-equals-and-hashcode.md +88 -0
  84. package/skill-assets/sunlint-code-quality/rules/java/J003-string-comparison.md +72 -0
  85. package/skill-assets/sunlint-code-quality/rules/java/J004-use-java-time.md +91 -0
  86. package/skill-assets/sunlint-code-quality/rules/java/J005-no-print-stack-trace.md +80 -0
  87. package/skill-assets/sunlint-code-quality/rules/java/J006-no-system-println.md +89 -0
  88. package/skill-assets/sunlint-code-quality/rules/java/J007-proper-logger.md +91 -0
  89. package/skill-assets/sunlint-code-quality/rules/java/J008-thread-safe-singleton.md +119 -0
  90. package/skill-assets/sunlint-code-quality/rules/java/J009-utility-class-constructor.md +82 -0
  91. package/skill-assets/sunlint-code-quality/rules/java/J010-preserve-stack-trace.md +119 -0
  92. package/skill-assets/sunlint-code-quality/rules/java/J011-null-safe-compare.md +88 -0
  93. package/skill-assets/sunlint-code-quality/rules/java/J012-use-enum-collections.md +104 -0
  94. package/skill-assets/sunlint-code-quality/rules/java/J013-return-empty-not-null.md +102 -0
  95. package/skill-assets/sunlint-code-quality/rules/java/J014-hardcoded-crypto-key.md +108 -0
  96. package/skill-assets/sunlint-code-quality/rules/java/J015-optional-instead-of-null.md +109 -0
  97. package/skill-assets/sunlint-code-quality/rules/php-laravel/AGENTS.md +124 -0
  98. package/skill-assets/sunlint-code-quality/rules/php-laravel/LV001-form-request-validation.md +64 -0
  99. package/skill-assets/sunlint-code-quality/rules/php-laravel/LV002-eager-load-no-n-plus-1.md +58 -0
  100. package/skill-assets/sunlint-code-quality/rules/php-laravel/LV003-config-not-env.md +54 -0
  101. package/skill-assets/sunlint-code-quality/rules/php-laravel/LV004-fillable-mass-assignment.md +51 -0
  102. package/skill-assets/sunlint-code-quality/rules/php-laravel/LV005-policies-gates-authorization.md +71 -0
  103. package/skill-assets/sunlint-code-quality/rules/php-laravel/LV006-queue-heavy-tasks.md +68 -0
  104. package/skill-assets/sunlint-code-quality/rules/php-laravel/LV007-hash-passwords.md +51 -0
  105. package/skill-assets/sunlint-code-quality/rules/php-laravel/LV008-route-model-binding.md +67 -0
  106. package/skill-assets/sunlint-code-quality/rules/php-laravel/LV009-api-resources.md +72 -0
  107. package/skill-assets/sunlint-code-quality/rules/php-laravel/LV010-chunk-large-datasets.md +58 -0
  108. package/skill-assets/sunlint-code-quality/rules/php-laravel/LV011-db-transactions.md +73 -0
  109. package/skill-assets/sunlint-code-quality/rules/php-laravel/LV012-service-layer.md +78 -0
  110. package/skill-assets/sunlint-code-quality/rules/php-laravel/LV013-testing-factories.md +75 -0
  111. package/skill-assets/sunlint-code-quality/rules/php-laravel/LV014-service-container.md +61 -0
  112. package/skill-assets/sunlint-code-quality/rules/python/P001-mutable-default-argument.md +55 -0
  113. package/skill-assets/sunlint-code-quality/rules/python/P002-specify-file-encoding.md +45 -0
  114. package/skill-assets/sunlint-code-quality/rules/python/P003-context-manager-for-resources.md +54 -0
  115. package/skill-assets/sunlint-code-quality/rules/python/P004-no-bare-except.md +65 -0
  116. package/skill-assets/sunlint-code-quality/rules/python/P005-use-isinstance.md +60 -0
  117. package/skill-assets/sunlint-code-quality/rules/python/P006-timezone-aware-datetime.md +58 -0
  118. package/skill-assets/sunlint-code-quality/rules/python/P007-use-pathlib.md +62 -0
  119. package/skill-assets/sunlint-code-quality/rules/python/P008-no-wildcard-import.md +52 -0
  120. package/skill-assets/sunlint-code-quality/rules/python/P009-logging-lazy-format.md +50 -0
  121. package/skill-assets/sunlint-code-quality/rules/python/P010-exception-chaining.md +57 -0
  122. package/skill-assets/sunlint-code-quality/rules/python/P011-subprocess-check.md +59 -0
  123. package/skill-assets/sunlint-code-quality/rules/python/P012-requests-timeout.md +70 -0
  124. package/skill-assets/sunlint-code-quality/rules/python/P013-no-global-statement.md +73 -0
  125. package/skill-assets/sunlint-code-quality/rules/python/P014-no-modify-collection-while-iterating.md +66 -0
  126. package/skill-assets/sunlint-code-quality/rules/python/P015-prefer-fstrings.md +61 -0
  127. package/skill-assets/sunlint-code-quality/rules/ruby-rails/AGENTS.md +121 -0
  128. package/skill-assets/sunlint-code-quality/rules/ruby-rails/RR001-strong-parameters.md +55 -0
  129. package/skill-assets/sunlint-code-quality/rules/ruby-rails/RR002-eager-load-includes.md +51 -0
  130. package/skill-assets/sunlint-code-quality/rules/ruby-rails/RR003-service-objects.md +99 -0
  131. package/skill-assets/sunlint-code-quality/rules/ruby-rails/RR004-active-job-background.md +67 -0
  132. package/skill-assets/sunlint-code-quality/rules/ruby-rails/RR005-pagination.md +53 -0
  133. package/skill-assets/sunlint-code-quality/rules/ruby-rails/RR006-find-each-batches.md +53 -0
  134. package/skill-assets/sunlint-code-quality/rules/ruby-rails/RR007-http-status-codes.md +76 -0
  135. package/skill-assets/sunlint-code-quality/rules/ruby-rails/RR008-before-action-auth.md +77 -0
  136. package/skill-assets/sunlint-code-quality/rules/ruby-rails/RR009-rails-credentials.md +61 -0
  137. package/skill-assets/sunlint-code-quality/rules/ruby-rails/RR010-scopes.md +57 -0
  138. package/skill-assets/sunlint-code-quality/rules/ruby-rails/RR011-counter-cache.md +59 -0
  139. package/skill-assets/sunlint-code-quality/rules/ruby-rails/RR012-render-json-status.md +42 -0
  140. package/skill-assets/sunlint-code-quality/rules/swift/C006-verb-noun-functions.md +37 -0
  141. package/skill-assets/sunlint-code-quality/rules/swift/C013-no-dead-code.md +55 -0
  142. package/skill-assets/sunlint-code-quality/rules/swift/C014-dependency-injection.md +69 -0
  143. package/skill-assets/sunlint-code-quality/rules/swift/C017-no-constructor-logic.md +66 -0
  144. package/skill-assets/sunlint-code-quality/rules/swift/C018-generic-errors.md +64 -0
  145. package/skill-assets/sunlint-code-quality/rules/swift/C019-error-log-level.md +64 -0
  146. package/skill-assets/sunlint-code-quality/rules/swift/C020-no-unused-imports.md +47 -0
  147. package/skill-assets/sunlint-code-quality/rules/swift/C022-no-unused-variables.md +46 -0
  148. package/skill-assets/sunlint-code-quality/rules/swift/C023-no-duplicate-names.md +55 -0
  149. package/skill-assets/sunlint-code-quality/rules/swift/C024-centralize-constants.md +68 -0
  150. package/skill-assets/sunlint-code-quality/rules/swift/C029-catch-log-root-cause.md +69 -0
  151. package/skill-assets/sunlint-code-quality/rules/swift/C030-custom-error-classes.md +77 -0
  152. package/skill-assets/sunlint-code-quality/rules/swift/C033-separate-data-access.md +89 -0
  153. package/skill-assets/sunlint-code-quality/rules/swift/C035-error-context-logging.md +66 -0
  154. package/skill-assets/sunlint-code-quality/rules/swift/C041-no-hardcoded-secrets.md +65 -0
  155. package/skill-assets/sunlint-code-quality/rules/swift/C042-boolean-naming.md +60 -0
  156. package/skill-assets/sunlint-code-quality/rules/swift/C052-controller-parsing.md +67 -0
  157. package/skill-assets/sunlint-code-quality/rules/swift/C060-superclass-logic.md +95 -0
  158. package/skill-assets/sunlint-code-quality/rules/swift/C067-no-hardcoded-config.md +80 -0
  159. package/skill-assets/sunlint-code-quality/rules/swift/S003-sql-injection.md +65 -0
  160. package/skill-assets/sunlint-code-quality/rules/swift/S004-no-log-credentials.md +67 -0
  161. package/skill-assets/sunlint-code-quality/rules/swift/S005-server-authorization.md +73 -0
  162. package/skill-assets/sunlint-code-quality/rules/swift/S006-default-credentials.md +76 -0
  163. package/skill-assets/sunlint-code-quality/rules/swift/S007-output-encoding.md +96 -0
  164. package/skill-assets/sunlint-code-quality/rules/swift/S009-approved-crypto.md +86 -0
  165. package/skill-assets/sunlint-code-quality/rules/swift/S010-csprng.md +71 -0
  166. package/skill-assets/sunlint-code-quality/rules/swift/S011-insecure-deserialization.md +74 -0
  167. package/skill-assets/sunlint-code-quality/rules/swift/S012-secrets-management.md +81 -0
  168. package/skill-assets/sunlint-code-quality/rules/swift/S013-tls-connections.md +67 -0
  169. package/skill-assets/sunlint-code-quality/rules/swift/S017-parameterized-queries.md +86 -0
  170. package/skill-assets/sunlint-code-quality/rules/swift/S019-session-management.md +131 -0
  171. package/skill-assets/sunlint-code-quality/rules/swift/S020-kvc-injection.md +91 -0
  172. package/skill-assets/sunlint-code-quality/rules/swift/S025-input-validation.md +125 -0
  173. package/skill-assets/sunlint-code-quality/rules/swift/S029-brute-force-protection.md +120 -0
  174. package/skill-assets/sunlint-code-quality/rules/swift/S036-path-traversal.md +102 -0
  175. package/skill-assets/sunlint-code-quality/rules/swift/S039-tls-certificate-validation.md +109 -0
  176. package/skill-assets/sunlint-code-quality/rules/swift/S041-logout-invalidation.md +103 -0
  177. package/skill-assets/sunlint-code-quality/rules/swift/S043-password-hashing.md +116 -0
  178. package/skill-assets/sunlint-code-quality/rules/swift/S044-critical-changes-reauth.md +145 -0
  179. package/skill-assets/sunlint-code-quality/rules/swift/S045-debug-info-exposure.md +116 -0
  180. package/skill-assets/sunlint-code-quality/rules/swift/S046-unvalidated-redirect.md +140 -0
  181. package/skill-assets/sunlint-code-quality/rules/swift/S051-token-expiry.md +134 -0
  182. package/skill-assets/sunlint-code-quality/rules/swift/S053-jwt-validation.md +139 -0
  183. package/skill-assets/sunlint-code-quality/rules/swift/S059-background-snapshot-protection.md +113 -0
  184. package/skill-assets/sunlint-code-quality/rules/swift/S060-data-protection-api.md +106 -0
  185. package/skill-assets/sunlint-code-quality/rules/swift/S061-jailbreak-detection.md +132 -0
@@ -0,0 +1,64 @@
1
+ ---
2
+ title: Use Form Request Classes for Validation
3
+ impact: HIGH
4
+ impactDescription: keeps controllers thin, centralizes validation logic and authorization, enables reuse
5
+ tags: validation, form-request, controller, laravel, clean-architecture
6
+ ---
7
+
8
+ ## Use Form Request Classes for Validation
9
+
10
+ Putting `$request->validate()` directly in controller actions mixes concerns, prevents reuse, and inflates controllers. Form Requests encapsulate validation + authorization as a dedicated class.
11
+
12
+ **Wrong:**
13
+
14
+ ```php
15
+ // Controller bloated with validation logic
16
+ public function store(Request $request)
17
+ {
18
+ $request->validate([
19
+ 'name' => 'required|string|max:255',
20
+ 'email' => 'required|email|unique:users',
21
+ 'age' => 'required|integer|min:18',
22
+ ]);
23
+
24
+ User::create($request->all());
25
+ }
26
+ ```
27
+
28
+ **Correct:**
29
+
30
+ ```bash
31
+ # Generate a Form Request
32
+ php artisan make:request StoreUserRequest
33
+ ```
34
+
35
+ ```php
36
+ // app/Http/Requests/StoreUserRequest.php
37
+ class StoreUserRequest extends FormRequest
38
+ {
39
+ public function authorize(): bool
40
+ {
41
+ return $this->user()->can('create', User::class);
42
+ }
43
+
44
+ public function rules(): array
45
+ {
46
+ return [
47
+ 'name' => 'required|string|max:255',
48
+ 'email' => 'required|email|unique:users',
49
+ 'age' => 'required|integer|min:18',
50
+ ];
51
+ }
52
+ }
53
+
54
+ // Controller is now thin
55
+ public function store(StoreUserRequest $request)
56
+ {
57
+ User::create($request->validated()); // never $request->all()
58
+ }
59
+ ```
60
+
61
+ **Key points:**
62
+ - Always use `$request->validated()` not `$request->all()` — only returns fields that passed validation
63
+ - Authorization logic belongs in `authorize()`, not the controller
64
+ - One Form Request per action (StoreUserRequest, UpdateUserRequest are separate)
@@ -0,0 +1,58 @@
1
+ ---
2
+ title: Eager Load Relationships to Prevent N+1 Queries
3
+ impact: CRITICAL
4
+ impactDescription: prevents N+1 query explosion that causes severe performance degradation in production
5
+ tags: n+1, eager-loading, eloquent, performance, with, laravel
6
+ ---
7
+
8
+ ## Eager Load Relationships to Prevent N+1 Queries
9
+
10
+ Accessing a relationship inside a loop without eager loading fires one query per iteration. With 1000 posts, that is 1001 queries. Always eager load with `with()` when you know you'll access the relationship.
11
+
12
+ **Wrong:**
13
+
14
+ ```php
15
+ // Executes 1 + N queries (one per post)
16
+ $posts = Post::all();
17
+
18
+ foreach ($posts as $post) {
19
+ echo $post->author->name; // <- SELECT * FROM users WHERE id = ? (×N)
20
+ echo $post->category->name; // <- SELECT * FROM categories WHERE id = ? (×N)
21
+ }
22
+ ```
23
+
24
+ **Correct:**
25
+
26
+ ```php
27
+ // Executes exactly 3 queries total
28
+ $posts = Post::with(['author', 'category'])->get();
29
+
30
+ foreach ($posts as $post) {
31
+ echo $post->author->name;
32
+ echo $post->category->name;
33
+ }
34
+
35
+ // Nested relationships
36
+ $posts = Post::with(['author.profile', 'tags'])->get();
37
+
38
+ // Selective columns to reduce memory
39
+ $posts = Post::with(['author:id,name,avatar'])->get();
40
+
41
+ // Conditional eager loading
42
+ $posts = Post::with(['comments' => function ($q) {
43
+ $q->where('approved', true)->latest()->limit(5);
44
+ }])->get();
45
+ ```
46
+
47
+ **Detection signal:**
48
+ - Any `->relationship` access inside a `foreach`, `map`, `each`, or `Collection` method without preceding `with()`
49
+ - Use Laravel Telescope or Debugbar in development to detect N+1 — queries > 10 for a single request is a red flag
50
+
51
+ **Also applies to:**
52
+ ```php
53
+ // WRONG: counting without withCount
54
+ $posts->each(fn($p) => $p->comments->count()); // N queries
55
+
56
+ // CORRECT: withCount
57
+ Post::withCount('comments')->get(); // 1 query
58
+ ```
@@ -0,0 +1,54 @@
1
+ ---
2
+ title: Use config() Helper, Never env() Outside Config Files
3
+ impact: HIGH
4
+ impactDescription: config caching (php artisan config:cache) breaks env() calls at runtime — app silently returns null
5
+ tags: config, env, caching, laravel, environment
6
+ ---
7
+
8
+ ## Use config() Instead of env() Outside Config Files
9
+
10
+ `env()` only works reliably before config is cached. Once you run `php artisan config:cache` (required in production), `env()` returns `null` everywhere except in `config/*.php` files.
11
+
12
+ **Wrong:**
13
+
14
+ ```php
15
+ // Service class — breaks after config:cache
16
+ class PaymentService
17
+ {
18
+ public function charge()
19
+ {
20
+ $key = env('STRIPE_SECRET'); // NULL in production!
21
+ }
22
+ }
23
+
24
+ // Controller
25
+ public function show()
26
+ {
27
+ $debug = env('APP_DEBUG'); // NULL after config:cache
28
+ }
29
+ ```
30
+
31
+ **Correct:**
32
+
33
+ ```php
34
+ // 1. Define in config/services.php
35
+ return [
36
+ 'stripe' => [
37
+ 'secret' => env('STRIPE_SECRET'), // env() ONLY here
38
+ ],
39
+ ];
40
+
41
+ // 2. Use config() everywhere else
42
+ class PaymentService
43
+ {
44
+ public function charge()
45
+ {
46
+ $key = config('services.stripe.secret'); // always works
47
+ }
48
+ }
49
+
50
+ // 3. With default fallback
51
+ $debug = config('app.debug', false);
52
+ ```
53
+
54
+ **Rule:** `env()` appears only in `config/` directory. Everywhere else → `config()`.
@@ -0,0 +1,51 @@
1
+ ---
2
+ title: Define $fillable — Never Use $guarded = []
3
+ impact: CRITICAL
4
+ impactDescription: disabling mass assignment protection allows attackers to write arbitrary model fields via crafted HTTP requests
5
+ tags: mass-assignment, fillable, guarded, security, eloquent, laravel
6
+ ---
7
+
8
+ ## Define $fillable — Never Use $guarded = []
9
+
10
+ Mass assignment attacks occur when a user passes unexpected fields (e.g. `is_admin=1`) that get written to the database. `$guarded = []` disables all protection.
11
+
12
+ **Wrong:**
13
+
14
+ ```php
15
+ class User extends Model
16
+ {
17
+ protected $guarded = []; // All fields writable — DANGEROUS
18
+
19
+ // Or: no $fillable defined at all (same effect with $guarded=[])
20
+ }
21
+
22
+ // Attacker sends: POST /users { "name": "Bob", "is_admin": 1 }
23
+ User::create($request->all()); // is_admin gets written!
24
+ ```
25
+
26
+ **Correct:**
27
+
28
+ ```php
29
+ class User extends Model
30
+ {
31
+ // Explicit allowlist
32
+ protected $fillable = [
33
+ 'name',
34
+ 'email',
35
+ 'password',
36
+ ];
37
+
38
+ // Fields like is_admin, email_verified_at are NOT here
39
+ // They are set explicitly:
40
+ }
41
+
42
+ // Controller
43
+ User::create($request->validated()); // only validated+fillable fields written
44
+
45
+ // When you must set a guarded field, use direct assignment:
46
+ $user = new User($request->validated());
47
+ $user->is_admin = false; // explicit, intentional
48
+ $user->save();
49
+ ```
50
+
51
+ **Also:** use `$request->validated()` not `$request->all()` so only schema-declared fields reach the model.
@@ -0,0 +1,71 @@
1
+ ---
2
+ title: Use Policies and Gates for Authorization
3
+ impact: HIGH
4
+ impactDescription: centralizes authorization logic, prevents scattered role checks, enables testing and auditing
5
+ tags: authorization, policy, gate, roles, security, laravel
6
+ ---
7
+
8
+ ## Use Policies and Gates for Authorization
9
+
10
+ Manual role checks scattered across controllers (`if ($user->role === 'admin')`) are fragile, hard to audit, and often inconsistently applied.
11
+
12
+ **Wrong:**
13
+
14
+ ```php
15
+ // Controller — authorization logic throughout
16
+ public function update(Request $request, Post $post)
17
+ {
18
+ if ($request->user()->id !== $post->user_id) {
19
+ abort(403);
20
+ }
21
+ if ($request->user()->role !== 'editor' && $request->user()->role !== 'admin') {
22
+ abort(403); // duplicated in show, destroy, etc.
23
+ }
24
+ $post->update($request->validated());
25
+ }
26
+ ```
27
+
28
+ **Correct:**
29
+
30
+ ```bash
31
+ php artisan make:policy PostPolicy --model=Post
32
+ ```
33
+
34
+ ```php
35
+ // app/Policies/PostPolicy.php
36
+ class PostPolicy
37
+ {
38
+ public function update(User $user, Post $post): bool
39
+ {
40
+ return $user->id === $post->user_id || $user->isEditor();
41
+ }
42
+
43
+ public function delete(User $user, Post $post): bool
44
+ {
45
+ return $user->id === $post->user_id || $user->isAdmin();
46
+ }
47
+ }
48
+
49
+ // Controller — thin and readable
50
+ public function update(UpdatePostRequest $request, Post $post)
51
+ {
52
+ $this->authorize('update', $post); // throws 403 if denied
53
+
54
+ $post->update($request->validated());
55
+ }
56
+
57
+ // Or in Form Request
58
+ public function authorize(): bool
59
+ {
60
+ return $this->user()->can('update', $this->route('post'));
61
+ }
62
+ ```
63
+
64
+ **Gates for non-model actions:**
65
+ ```php
66
+ // AppServiceProvider
67
+ Gate::define('access-reports', fn(User $u) => $u->hasRole('analyst'));
68
+
69
+ // Anywhere
70
+ if (Gate::denies('access-reports')) abort(403);
71
+ ```
@@ -0,0 +1,68 @@
1
+ ---
2
+ title: Queue Heavy Tasks — Never Block the Request Cycle
3
+ impact: HIGH
4
+ impactDescription: synchronous email/external API calls cause request timeouts and degrade user experience under load
5
+ tags: queues, jobs, email, performance, background, laravel
6
+ ---
7
+
8
+ ## Queue Heavy Tasks — Never Block the Request Cycle
9
+
10
+ Any task over ~200ms (sending email, calling external APIs, generating reports, processing images) must be dispatched to a queue, not executed synchronously in the HTTP request.
11
+
12
+ **Wrong:**
13
+
14
+ ```php
15
+ // Request handler blocked for seconds
16
+ public function register(StoreUserRequest $request)
17
+ {
18
+ $user = User::create($request->validated());
19
+
20
+ // Blocks for 1-3 seconds — user waits, timeout risk
21
+ Mail::to($user)->send(new WelcomeEmail($user));
22
+
23
+ // Calls Slack API — blocks if Slack is slow
24
+ Http::post(config('services.slack.webhook'), ['text' => "New user: {$user->email}"]);
25
+
26
+ return response()->json($user, 201);
27
+ }
28
+ ```
29
+
30
+ **Correct:**
31
+
32
+ ```bash
33
+ php artisan make:job SendWelcomeEmail
34
+ php artisan make:job NotifySlackNewUser
35
+ ```
36
+
37
+ ```php
38
+ public function register(StoreUserRequest $request)
39
+ {
40
+ $user = User::create($request->validated());
41
+
42
+ // Dispatch to queue — returns immediately
43
+ SendWelcomeEmail::dispatch($user)->onQueue('emails');
44
+ NotifySlackNewUser::dispatch($user)->onQueue('notifications');
45
+
46
+ return response()->json($user, 201); // responds in <50ms
47
+ }
48
+
49
+ // Job class
50
+ class SendWelcomeEmail implements ShouldQueue
51
+ {
52
+ use Queueable, Dispatchable, InteractsWithQueue, SerializesModels;
53
+
54
+ public function __construct(private User $user) {}
55
+
56
+ public function handle(): void
57
+ {
58
+ Mail::to($this->user)->send(new WelcomeEmail($this->user));
59
+ }
60
+
61
+ public function failed(Throwable $e): void
62
+ {
63
+ Log::error('WelcomeEmail failed', ['user' => $this->user->id, 'error' => $e->getMessage()]);
64
+ }
65
+ }
66
+ ```
67
+
68
+ **Queue-worthy operations:** email, SMS, push notifications, Slack/webhook, PDF generation, image resize, CSV export, expensive calculations, third-party API calls.
@@ -0,0 +1,51 @@
1
+ ---
2
+ title: Use Hash::make() for Passwords — Never md5/sha1/bcrypt()
3
+ impact: CRITICAL
4
+ impactDescription: MD5/SHA1 are broken for passwords; Laravel's Hash facade uses bcrypt/argon2 with proper salting automatically
5
+ tags: password, hashing, bcrypt, security, hash, laravel
6
+ ---
7
+
8
+ ## Use Hash::make() for Passwords
9
+
10
+ Laravel's `Hash` facade automatically uses the configured driver (bcrypt by default, upgradeable to argon2id). Never use PHP's raw `password_hash()` directly without going through Laravel's config, and never use `md5()` or `sha1()`.
11
+
12
+ **Wrong:**
13
+
14
+ ```php
15
+ // md5/sha1 — broken, no salting, rainbow-table vulnerable
16
+ $user->password = md5($request->password);
17
+ $user->password = sha1($request->password);
18
+
19
+ // Raw PHP without config — bypasses algorithm config
20
+ $user->password = password_hash($request->password, PASSWORD_BCRYPT);
21
+ ```
22
+
23
+ **Correct:**
24
+
25
+ ```php
26
+ // Creating a user
27
+ $user = User::create([
28
+ 'name' => $request->name,
29
+ 'email' => $request->email,
30
+ 'password' => Hash::make($request->password), // salted + bcrypt/argon2
31
+ ]);
32
+
33
+ // Verifying on login — NEVER compare raw strings
34
+ if (!Hash::check($request->password, $user->password)) {
35
+ throw ValidationException::withMessages([
36
+ 'email' => ['These credentials do not match our records.'],
37
+ ]);
38
+ }
39
+
40
+ // Rehash on login if algorithm changed
41
+ if (Hash::needsRehash($user->password)) {
42
+ $user->update(['password' => Hash::make($request->password)]);
43
+ }
44
+ ```
45
+
46
+ **Config:** Change algorithm in `config/hashing.php`:
47
+ ```php
48
+ 'driver' => 'argon2id', // upgrade from default bcrypt for new projects
49
+ ```
50
+
51
+ **In migrations:** password column must be `string(255)`, not `string(32)` — bcrypt hashes are 60 chars, argon2id up to 95.
@@ -0,0 +1,67 @@
1
+ ---
2
+ title: Use Route Model Binding — No Manual find() in Controllers
3
+ impact: MEDIUM
4
+ impactDescription: eliminates repetitive find-or-404 boilerplate, centralizes authorization scope
5
+ tags: route-model-binding, controller, eloquent, laravel, routing
6
+ ---
7
+
8
+ ## Use Route Model Binding
9
+
10
+ Laravel resolves model instances automatically from route parameters. Writing `User::findOrFail($id)` manually in every controller method is redundant boilerplate.
11
+
12
+ **Wrong:**
13
+
14
+ ```php
15
+ // routes/api.php
16
+ Route::get('/posts/{id}', [PostController::class, 'show']);
17
+
18
+ // Controller
19
+ public function show(int $id)
20
+ {
21
+ $post = Post::findOrFail($id); // unnecessary boilerplate
22
+ $this->authorize('view', $post);
23
+ return PostResource::make($post);
24
+ }
25
+ ```
26
+
27
+ **Correct:**
28
+
29
+ ```php
30
+ // routes/api.php — use the model name as parameter
31
+ Route::get('/posts/{post}', [PostController::class, 'show']);
32
+
33
+ // Controller — Laravel resolves Post automatically, throws 404 if not found
34
+ public function show(Post $post)
35
+ {
36
+ $this->authorize('view', $post);
37
+ return PostResource::make($post);
38
+ }
39
+ ```
40
+
41
+ **Custom binding key** (e.g. slug instead of id):
42
+
43
+ ```php
44
+ // Model
45
+ class Post extends Model
46
+ {
47
+ public function getRouteKeyName(): string
48
+ {
49
+ return 'slug'; // /posts/my-post-slug
50
+ }
51
+ }
52
+ ```
53
+
54
+ **Scoped binding** (child must belong to parent):
55
+
56
+ ```php
57
+ // /users/{user}/posts/{post} — post must belong to user
58
+ Route::get('/users/{user}/posts/{post}', [PostController::class, 'show'])
59
+ ->scopeBindings();
60
+ ```
61
+
62
+ **With eager loading:**
63
+
64
+ ```php
65
+ // Customize resolution for eager-loading
66
+ Route::bind('post', fn($value) => Post::with(['author', 'tags'])->findOrFail($value));
67
+ ```
@@ -0,0 +1,72 @@
1
+ ---
2
+ title: Use API Resources for Response Transformation
3
+ impact: HIGH
4
+ impactDescription: prevents over-exposure of model fields, decouples DB schema from API contract, enables versioning
5
+ tags: api-resources, response, transformation, security, laravel
6
+ ---
7
+
8
+ ## Use API Resources for Response Transformation
9
+
10
+ Returning `$model->toArray()` or `response()->json($model)` exposes all model attributes including sensitive fields (`password`, `remember_token`, internal flags). API Resources control exactly what is serialized.
11
+
12
+ **Wrong:**
13
+
14
+ ```php
15
+ // Exposes ALL columns including password, remember_token, internal flags
16
+ return response()->json($user);
17
+ return response()->json(User::all());
18
+ return response()->json($user->toArray());
19
+ ```
20
+
21
+ **Correct:**
22
+
23
+ ```bash
24
+ php artisan make:resource UserResource
25
+ php artisan make:resource UserCollection
26
+ ```
27
+
28
+ ```php
29
+ // app/Http/Resources/UserResource.php
30
+ class UserResource extends JsonResource
31
+ {
32
+ public function toArray(Request $request): array
33
+ {
34
+ return [
35
+ 'id' => $this->id,
36
+ 'name' => $this->name,
37
+ 'email' => $this->email,
38
+ 'avatar_url' => $this->avatar_url,
39
+ 'created_at' => $this->created_at->toIso8601String(),
40
+
41
+ // Conditional fields
42
+ 'phone' => $this->when(
43
+ $request->user()?->id === $this->id,
44
+ $this->phone
45
+ ),
46
+
47
+ // Nested resource
48
+ 'posts_count' => $this->whenCounted('posts'),
49
+ ];
50
+ }
51
+ }
52
+
53
+ // Controller
54
+ public function show(User $user): UserResource
55
+ {
56
+ return UserResource::make($user);
57
+ }
58
+
59
+ public function index(): JsonResponse
60
+ {
61
+ $users = User::with('profile')->paginate(20);
62
+ return UserResource::collection($users)->response();
63
+ }
64
+ ```
65
+
66
+ **Password and sensitive fields must be in `$hidden`:**
67
+ ```php
68
+ class User extends Model
69
+ {
70
+ protected $hidden = ['password', 'remember_token', 'two_factor_secret'];
71
+ }
72
+ ```
@@ -0,0 +1,58 @@
1
+ ---
2
+ title: Use chunk()/cursor() for Large Datasets — Never Model::all()
3
+ impact: HIGH
4
+ impactDescription: Model::all() on a large table loads all rows into memory, causing OOM errors and PHP timeouts
5
+ tags: chunk, cursor, memory, performance, eloquent, laravel
6
+ ---
7
+
8
+ ## Use chunk()/cursor() for Large Datasets
9
+
10
+ `Model::all()` or `->get()` with no limit fetches every row into PHP memory. On a 100k-row table this causes memory exhaustion.
11
+
12
+ **Wrong:**
13
+
14
+ ```php
15
+ // Memory bomb — loads all rows into PHP array
16
+ $users = User::all();
17
+ foreach ($users as $user) {
18
+ $user->sendNewsletter();
19
+ }
20
+
21
+ // Slightly better but still: loads 50k orders at once
22
+ $orders = Order::where('status', 'pending')->get();
23
+ foreach ($orders as $order) {
24
+ ProcessOrder::dispatch($order);
25
+ }
26
+ ```
27
+
28
+ **Correct:**
29
+
30
+ ```php
31
+ // chunk: fetches 200 rows at a time, re-queries per batch
32
+ User::chunk(200, function ($users) {
33
+ foreach ($users as $user) {
34
+ $user->sendNewsletter();
35
+ }
36
+ });
37
+
38
+ // cursor: uses lazy collection (PHP generator), one row in memory at a time
39
+ // Best for read-only iteration without modifying records
40
+ foreach (User::cursor() as $user) {
41
+ $user->sendNewsletter();
42
+ }
43
+
44
+ // chunkById: safer than chunk for updates (avoids row-skip bug)
45
+ User::where('status', 'inactive')
46
+ ->chunkById(500, function ($users) {
47
+ User::whereIn('id', $users->pluck('id'))->delete();
48
+ });
49
+
50
+ // For commands/jobs that process everything
51
+ User::select(['id', 'email', 'name']) // only needed columns
52
+ ->where('newsletter', true)
53
+ ->chunkById(1000, function ($batch) {
54
+ SendNewsletterJob::dispatch($batch->pluck('id')->all());
55
+ });
56
+ ```
57
+
58
+ **Rule of thumb:** any query without `->limit()` or `->paginate()` in a loop or report command must use `chunk()` or `cursor()`.
@@ -0,0 +1,73 @@
1
+ ---
2
+ title: Wrap Multi-Step Database Operations in Transactions
3
+ impact: HIGH
4
+ impactDescription: partial failures leave the database in an inconsistent state without transactions
5
+ tags: transactions, database, atomicity, eloquent, laravel
6
+ ---
7
+
8
+ ## Wrap Multi-Step Database Operations in Transactions
9
+
10
+ Any sequence of writes that must succeed or fail together (e.g. create order + deduct stock + charge payment) must run inside a `DB::transaction()`.
11
+
12
+ **Wrong:**
13
+
14
+ ```php
15
+ // If deductStock() succeeds but createTransaction() fails,
16
+ // stock is deducted but no order exists — corrupted state
17
+ public function placeOrder(Cart $cart, User $user): Order
18
+ {
19
+ $order = Order::create([...]);
20
+ $this->deductStock($cart->items); // can throw
21
+ $this->createTransaction($order); // can throw — order exists, stock reduced
22
+ $this->clearCart($cart);
23
+ return $order;
24
+ }
25
+ ```
26
+
27
+ **Correct:**
28
+
29
+ ```php
30
+ use Illuminate\Support\Facades\DB;
31
+
32
+ public function placeOrder(Cart $cart, User $user): Order
33
+ {
34
+ return DB::transaction(function () use ($cart, $user) {
35
+ $order = Order::create([
36
+ 'user_id' => $user->id,
37
+ 'total' => $cart->total(),
38
+ ]);
39
+
40
+ $this->deductStock($cart->items); // exception -> full rollback
41
+ $this->createTransaction($order); // exception -> full rollback
42
+ $this->clearCart($cart);
43
+
44
+ return $order;
45
+ });
46
+ // Any Throwable inside auto-rolls back and re-throws
47
+ }
48
+ ```
49
+
50
+ **With custom exception handling:**
51
+
52
+ ```php
53
+ DB::transaction(function () {
54
+ // ...
55
+ }, attempts: 3); // retry on deadlock (3 attempts)
56
+ ```
57
+
58
+ **Manual control when needed:**
59
+ ```php
60
+ DB::beginTransaction();
61
+ try {
62
+ // ...
63
+ DB::commit();
64
+ } catch (Throwable $e) {
65
+ DB::rollBack();
66
+ throw $e;
67
+ }
68
+ ```
69
+
70
+ **Note:** Dispatching jobs inside a transaction should use `afterCommit()`:
71
+ ```php
72
+ ProcessOrder::dispatch($order)->afterCommit(); // only dispatches if transaction commits
73
+ ```