start-vibing-stacks 2.2.0 → 2.3.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.
@@ -1,80 +1,198 @@
1
- # PHP Security Scan
1
+ # Laravel Security Scan
2
2
 
3
- ## OWASP Top 10 for PHP
3
+ ## OWASP Top 10 for Laravel
4
4
 
5
5
  ### A01 - Broken Access Control
6
6
 
7
7
  ```php
8
- // NEVER trust user input for authorization
9
- $userId = $_GET['user_id'];
8
+ // WRONG: Trust user input for authorization
9
+ $userId = $request->input('user_id');
10
10
  $user = User::find($userId);
11
11
 
12
- // Use authenticated session
13
- $user = Auth::user();
12
+ // CORRECT: Use authenticated session
13
+ $user = $request->user();
14
+
15
+ // CORRECT: Scope queries by authenticated user
16
+ $query = Resource::query();
17
+ if (!$request->user()->isAdmin()) {
18
+ $query->where('user_id', $request->user()->id);
19
+ }
14
20
  ```
15
21
 
22
+ **Rules:**
23
+ - NEVER return unscoped queries — always filter by authenticated user
24
+ - Use Policies for authorization logic
25
+ - Use Form Requests with `authorize()` method
26
+ - Use Route Model Binding with scoping
27
+
16
28
  ### A02 - Cryptographic Failures
17
29
 
18
30
  ```php
19
- // ❌ NEVER
31
+ // WRONG
20
32
  md5($password);
21
33
  sha1($password);
22
34
 
23
- // ALWAYS
24
- password_hash($password, PASSWORD_BCRYPT, ['cost' => 12]);
25
- password_verify($input, $hash);
35
+ // CORRECT (Laravel handles this via Hash facade)
36
+ Hash::make($password);
37
+ Hash::check($input, $hashedPassword);
38
+
39
+ // For API tokens: use Laravel Sanctum
40
+ // Table: users_access_tokens (UUIDs + Soft Deletes)
41
+ // All token actions logged via Loggable trait
26
42
  ```
27
43
 
28
44
  ### A03 - Injection
29
45
 
30
46
  ```php
31
- // SQL Injection
32
- $db->query("SELECT * FROM users WHERE id = {$_GET['id']}");
47
+ // WRONG: SQL Injection via DB::raw
48
+ $users = DB::select("SELECT * FROM users WHERE id = {$id}");
49
+ DB::raw("WHERE name = '{$name}'");
33
50
 
34
- // Prepared statements (PDO)
35
- $stmt = $db->prepare("SELECT * FROM users WHERE id = :id");
36
- $stmt->execute([':id' => $id]);
51
+ // CORRECT: Eloquent (preferred)
52
+ $user = User::findOrFail($id);
37
53
 
38
- // Prepared statements (MySQLi)
39
- $stmt = $db->prepare("SELECT * FROM users WHERE id = ?");
40
- $stmt->bind_param('i', $id);
41
- $stmt->execute();
54
+ // CORRECT: Query Builder with bindings
55
+ $users = DB::table('users')->where('name', $name)->get();
56
+
57
+ // CORRECT: Raw with parameter binding (last resort)
58
+ $results = DB::select('SELECT * FROM users WHERE id = ?', [$id]);
42
59
  ```
43
60
 
44
61
  ### A07 - XSS Prevention
45
62
 
46
63
  ```php
47
- // Raw output
48
- echo $userInput;
64
+ // WRONG: Unescaped output in Blade
65
+ {!! $userInput !!}
49
66
 
50
- // Always escape
51
- echo htmlspecialchars($userInput, ENT_QUOTES, 'UTF-8');
67
+ // CORRECT: Auto-escaped (Blade default)
68
+ {{ $userInput }}
52
69
 
53
- // In templates (Blade)
54
- {{ $userInput }} // Auto-escaped
55
- {!! $trustedHtml !!} // Only for trusted content
70
+ // Only use {!! !!} for TRUSTED, pre-sanitized HTML
71
+ {!! $trustedHtmlFromCMS !!}
56
72
  ```
57
73
 
58
- ## Security Checklist
74
+ ### A08 - Insecure Deserialization
75
+
76
+ ```php
77
+ // WRONG: Unserialize user data
78
+ $data = unserialize($request->input('data'));
79
+
80
+ // CORRECT: Use JSON with Model casts
81
+ protected $casts = [
82
+ 'metadata' => 'array',
83
+ 'settings' => 'array',
84
+ ];
85
+
86
+ // CORRECT: Defensive JSON handling
87
+ $data = is_string($model->data)
88
+ ? json_decode($model->data, true)
89
+ : $model->data;
90
+ ```
91
+
92
+ ## Laravel-Specific Security
93
+
94
+ ### Mass Assignment Protection
95
+
96
+ ```php
97
+ // WRONG: No protection
98
+ $user = User::create($request->all());
99
+
100
+ // CORRECT: Define $fillable
101
+ class User extends Model {
102
+ protected $fillable = ['name', 'email'];
103
+ }
104
+
105
+ // CORRECT: Use Form Request validated data
106
+ $user = User::create($request->validated());
107
+ ```
108
+
109
+ ### Environment Variables
110
+
111
+ ```php
112
+ // WRONG: env() outside config files (null when cached)
113
+ $apiKey = env('API_KEY');
59
114
 
60
- - [ ] All SQL uses prepared statements
61
- - [ ] All output is escaped (htmlspecialchars)
62
- - [ ] CSRF tokens on all forms
63
- - [ ] Passwords use password_hash/verify
64
- - [ ] File uploads validated (type, size, extension)
65
- - [ ] Sessions use httpOnly + secure cookies
66
- - [ ] Input validated before processing
67
- - [ ] No `eval()`, `exec()` with user input
68
- - [ ] No `extract()` on user data
69
- - [ ] Headers: X-Content-Type-Options, X-Frame-Options, CSP
115
+ // CORRECT: Use config files
116
+ // config/services.php
117
+ 'stripe' => ['key' => env('STRIPE_KEY')],
118
+
119
+ // In code:
120
+ $apiKey = config('services.stripe.key');
121
+ ```
122
+
123
+ ### CSRF & Authentication
124
+
125
+ ```php
126
+ // Blade forms — CSRF automatic
127
+ <form method="POST">
128
+ @csrf
129
+ ...
130
+ </form>
131
+
132
+ // API routes — use Sanctum middleware
133
+ Route::middleware('auth:sanctum')->group(function () {
134
+ Route::apiResource('users', UserController::class);
135
+ });
136
+ ```
137
+
138
+ ### File Upload Validation
139
+
140
+ ```php
141
+ public function rules(): array
142
+ {
143
+ return [
144
+ 'avatar' => [
145
+ 'required',
146
+ 'image',
147
+ 'mimes:jpg,jpeg,png,webp',
148
+ 'max:2048', // 2MB
149
+ 'dimensions:max_width=2000,max_height=2000',
150
+ ],
151
+ ];
152
+ }
153
+ ```
154
+
155
+ ## Octane Security Considerations
156
+
157
+ ```php
158
+ // WRONG: Static state leaks user data between requests
159
+ class AuthService {
160
+ private static ?User $currentUser = null; // LEAKS!
161
+ }
162
+
163
+ // WRONG: Superglobals are stale in Octane
164
+ $token = $_SERVER['HTTP_AUTHORIZATION'];
165
+
166
+ // CORRECT: Use Request object
167
+ $token = $request->bearerToken();
168
+ $user = $request->user();
169
+ ```
170
+
171
+ ## Security Checklist
70
172
 
71
- ## Sensitive Data Patterns (FORBIDDEN)
173
+ - [ ] All queries use Eloquent or Query Builder (no raw SQL with user input)
174
+ - [ ] All Blade output uses `{{ }}` (auto-escaped)
175
+ - [ ] CSRF protection on all forms (`@csrf`)
176
+ - [ ] Passwords use `Hash::make()` / `Hash::check()`
177
+ - [ ] File uploads validated (type, size, dimensions)
178
+ - [ ] API routes protected with Sanctum middleware
179
+ - [ ] Models define `$fillable` (no `$guarded = []`)
180
+ - [ ] Queries scoped by authenticated user (unless admin)
181
+ - [ ] No `env()` calls outside config files
182
+ - [ ] No static state in services (Octane-safe)
183
+ - [ ] No superglobals (`$_GET`, `$_POST`, `$_SESSION`)
184
+ - [ ] Sensitive headers set (X-Content-Type-Options, X-Frame-Options, CSP)
185
+ - [ ] Rate limiting on authentication endpoints
186
+
187
+ ## Sensitive Patterns (FORBIDDEN)
72
188
 
73
189
  | Pattern | Risk |
74
190
  |---------|------|
75
- | `$_GET` used directly in SQL | SQL Injection |
76
- | `echo $_POST[...]` | XSS |
191
+ | `DB::raw()` with user input | SQL Injection |
192
+ | `{!! $userInput !!}` | XSS |
77
193
  | `md5()` / `sha1()` for passwords | Weak hashing |
78
- | `eval($userInput)` | RCE |
79
- | `include $_GET['page']` | LFI/RFI |
80
- | `serialize()` user data | Object injection |
194
+ | Dynamic code execution | RCE |
195
+ | `unserialize()` on user data | Object injection |
196
+ | `$request->all()` without `$fillable` | Mass assignment |
197
+ | `env()` in runtime code | Null after config cache |
198
+ | Static user state in services | Data leaks in Octane |
@@ -27,13 +27,20 @@
27
27
  "name": "Laravel 12 + Octane (RoadRunner) + Inertia.js",
28
28
  "icon": "🚀",
29
29
  "detectFiles": ["artisan", "rr.yaml"],
30
- "default": true
30
+ "default": true,
31
+ "skills": [
32
+ "laravel-patterns",
33
+ "laravel-octane"
34
+ ]
31
35
  },
32
36
  {
33
37
  "id": "laravel",
34
38
  "name": "Laravel 12 (standard)",
35
39
  "icon": "🏗️",
36
- "detectFiles": ["artisan", "bootstrap/app.php"]
40
+ "detectFiles": ["artisan", "bootstrap/app.php"],
41
+ "skills": [
42
+ "laravel-patterns"
43
+ ]
37
44
  }
38
45
  ],
39
46
  "databases": [
@@ -46,12 +53,20 @@
46
53
  "id": "react-inertia",
47
54
  "name": "ReactJS 19 + Inertia.js + TailwindCSS 4",
48
55
  "icon": "⚛️",
49
- "default": true
56
+ "default": true,
57
+ "frameworks": ["laravel", "laravel-octane"]
50
58
  },
51
59
  {
52
60
  "id": "blade",
53
61
  "name": "Blade + TailwindCSS 4",
54
- "icon": "🖼️"
62
+ "icon": "🖼️",
63
+ "frameworks": ["laravel", "laravel-octane"]
64
+ },
65
+ {
66
+ "id": "livewire",
67
+ "name": "Livewire + Alpine.js",
68
+ "icon": "⚡",
69
+ "frameworks": ["laravel", "laravel-octane"]
55
70
  },
56
71
  {
57
72
  "id": "none",
@@ -68,8 +83,6 @@
68
83
  "phpstan-analysis",
69
84
  "composer-workflow",
70
85
  "security-scan-php",
71
- "laravel-patterns",
72
- "laravel-octane",
73
86
  "api-design"
74
87
  ],
75
88
  "requirements": [
@@ -16,6 +16,8 @@
16
16
  |-----------|------------|
17
17
  | Language | PHP >= 8.3 |
18
18
  | Framework | {{FRAMEWORK}} |
19
+ | Server | Octane + RoadRunner |
20
+ | Frontend | ReactJS 19+ / Inertia.js / TailwindCSS 4+ |
19
21
  | Database | {{DATABASE}} |
20
22
  | Package Manager | Composer |
21
23
  | Static Analysis | PHPStan (level 6) |
@@ -26,41 +28,104 @@
26
28
 
27
29
  ```
28
30
  project/
29
- ├── src/ # Application source (PSR-4: App\)
30
- ├── public/ # Web root (index.php)
31
- ├── config/ # Configuration files
31
+ ├── app/
32
+ ├── Console/ # Artisan commands
33
+ ├── Exceptions/ # Exception handlers
34
+ │ ├── Http/
35
+ │ │ ├── Controllers/ # Thin controllers (Inertia::render + Services)
36
+ │ │ ├── Middleware/ # HTTP middleware (HandleInertiaRequests)
37
+ │ │ └── Requests/ # Form request validation
38
+ │ ├── Models/ # Eloquent models (UUIDs, $fillable)
39
+ │ ├── Providers/ # Service providers
40
+ │ ├── Services/ # Business logic (single responsibility)
41
+ │ │ └── Helpers/ # Extracted logic for complex services
42
+ │ ├── Support/ # Helper classes (InertiaShare)
43
+ │ ├── Traits/ # Shared traits (Loggable, FormatsDatesForApi)
44
+ │ └── Jobs/ # Queue jobs (idempotent, chunked)
45
+ ├── bootstrap/ # Framework bootstrap
46
+ ├── config/ # Configuration (immutable at runtime)
47
+ │ └── translations_inertia.php # Per-page translation mapping
48
+ ├── database/
49
+ │ ├── factories/ # Model factories
50
+ │ ├── migrations/ # Incremental migrations ONLY
51
+ │ └── seeders/ # Database seeders
52
+ ├── lang/
53
+ │ ├── en/ # English translations
54
+ │ └── pt/ # Portuguese translations
55
+ ├── public/ # Web root (index.php)
56
+ ├── resources/
57
+ │ ├── views/ # Blade root template (app.blade.php)
58
+ │ ├── css/ # Stylesheets (TailwindCSS)
59
+ │ └── js/
60
+ │ ├── Pages/ # React page components (mapped by Inertia)
61
+ │ ├── Components/ # Reusable React components
62
+ │ ├── Layouts/ # Page layouts (Authenticated, Guest)
63
+ │ ├── Icons/ # SVG icons (import with ?react)
64
+ │ └── Utils/
65
+ │ └── translate.js # __() translation helper
66
+ ├── routes/
67
+ │ ├── api.php # API routes (RESTful + action endpoints)
68
+ │ └── web.php # Web routes (Inertia pages)
69
+ ├── storage/ # Logs, cache, uploads
32
70
  ├── tests/
33
- │ ├── Unit/ # PHPUnit unit tests
34
- │ └── Feature/ # Feature/integration tests
35
- ├── storage/ # Logs, cache, uploads
36
- ├── .claude/ # AI agent configuration
37
- ├── composer.json # Dependencies
38
- ├── phpstan.neon # Static analysis config
39
- └── CLAUDE.md # This file
71
+ │ ├── Unit/ # PHPUnit unit tests
72
+ │ └── Feature/ # Feature/integration tests
73
+ ├── .claude/ # AI agent configuration
74
+ ├── artisan # CLI entry point
75
+ ├── rr.yaml # RoadRunner config (Octane)
76
+ ├── composer.json # Dependencies
77
+ ├── phpstan.neon # Static analysis config
78
+ └── CLAUDE.md # This file
40
79
  ```
41
80
 
42
81
  ## Critical Rules
43
82
 
44
- - **PHP >= 8.3** — use modern features (readonly, enums, typed constants)
83
+ - **PHP >= 8.3** — readonly, enums, typed constants, match expressions
45
84
  - **`declare(strict_types=1)`** in EVERY PHP file
46
- - **PSR-4 autoloading** — no manual includes
47
- - **Prepared statements** — NEVER concatenate SQL
48
- - **Type everything** — properties, params, returns
49
- - **htmlspecialchars()** — all user output
50
- - **password_hash/verify** — NEVER md5/sha1
85
+ - **Octane-safe code** — no static state, no globals, no `die()`/`exit()`
86
+ - **Dependency Injection** — use DI over `app()` or `resolve()`
87
+ - **Thin controllers** — delegate business logic to Service classes
88
+ - **Form Requests** — validate input in dedicated request classes
89
+ - **Type everything** — properties, params, returns (no `mixed` without justification)
90
+ - **UUIDs** — all new models use `HasUuids` trait as primary key
91
+ - **Mass assignment** — always define `$fillable` on models
92
+ - **Auditing** — critical models use `Loggable` trait
93
+ - **Eloquent only** — no raw SQL unless strictly justified with parameter binding
94
+ - **Config immutable** — never use `config()` to SET values at runtime
95
+ - **Request object** — use `$request->input()`, never `$_GET`/`$_POST`/`$_SESSION`
51
96
 
52
97
  ## FORBIDDEN
53
98
 
99
+ ### Backend
100
+
101
+ | Action | Reason |
102
+ |--------|--------|
103
+ | Dynamic code execution functions | Remote code execution risk |
104
+ | `DB::raw()` with user input | SQL injection risk |
105
+ | `{!! $userInput !!}` | XSS — use `{{ }}` instead |
106
+ | Mass assignment without `$fillable` | Uncontrolled field injection |
107
+ | Business logic in controllers | Move to Service classes |
108
+ | `env()` outside config files | Returns null when config is cached |
109
+ | `static` properties on services | Memory leaks in Octane workers |
110
+ | Global variables / superglobals | Stale state in Octane |
111
+ | `die()` / `exit()` | Kills the Octane worker process |
112
+ | `config(['key' => 'val'])` at runtime | Affects all concurrent requests |
113
+ | `migrate:fresh` / `db:wipe` / `db:reset` | Destroys production data |
114
+ | `app()` / `resolve()` in constructors | Use constructor DI instead |
115
+ | `Inertia::render()` after POST/PUT/DELETE | Use `redirect()->route()` instead |
116
+
117
+ ### Frontend (React)
118
+
54
119
  | Action | Reason |
55
120
  |--------|--------|
56
- | `eval()` | Remote code execution risk |
57
- | `mysql_*` functions | Deprecated, use PDO |
58
- | `extract($_POST)` | Variable injection |
59
- | `include $_GET[...]` | Local file inclusion |
60
- | SQL without prepared statements | SQL injection |
61
- | `echo $userInput` without escape | XSS |
62
- | `md5($password)` | Weak hashing |
63
- | PHP < 8.3 syntax | Version requirement |
121
+ | `__()` inside JSX / render | Hook violation define as CONST before hooks |
122
+ | `fetch()` / `axios` for page data | Bypasses Inertia — use props from controller |
123
+ | `<a href>` for internal links | Full page reload — use `<Link>` |
124
+ | `window.location` for navigation | Full reload use `router.visit()` |
125
+ | Inline SVGs in JSX | Bloats components — use SVG files with `?react` |
126
+ | Raw `console.log` | Uncontrolled use debug constant pattern |
127
+ | Inline Tailwind class soup | Unreadable use STYLES const object |
128
+ | `axios.post()` for forms | No CSRF/errors — use `useForm().post()` |
64
129
 
65
130
  ## Quality Gates
66
131
 
@@ -68,21 +133,35 @@ project/
68
133
  vendor/bin/phpstan analyse --level=6 # Static analysis
69
134
  vendor/bin/phpunit # Tests
70
135
  vendor/bin/php-cs-fixer fix --dry-run # Code style
136
+ php artisan test # Laravel test runner
71
137
  ```
72
138
 
73
- ## HTTP Requests
139
+ ## Database
74
140
 
75
- All database queries MUST use prepared statements:
141
+ Use Eloquent ORM and Query Builder. Raw queries only when strictly necessary with parameter binding:
76
142
 
77
143
  ```php
78
- $stmt = $pdo->prepare("SELECT * FROM users WHERE id = :id");
79
- $stmt->execute([':id' => $userId]);
144
+ // Eloquent (preferred)
145
+ $user = User::findOrFail($id);
146
+
147
+ // Query Builder
148
+ $users = DB::table('users')->where('active', true)->get();
149
+
150
+ // Raw query (last resort — always bind parameters)
151
+ $results = DB::select('SELECT * FROM users WHERE id = ?', [$id]);
80
152
  ```
81
153
 
154
+ ## Migration Safety
155
+
156
+ - **ALWAYS** use incremental migrations (`make:migration`)
157
+ - **NEVER** execute `migrate:fresh`, `migrate:refresh`, `db:wipe`, `db:reset`
158
+ - If a migration fails, fix the file or create a new one
159
+ - Assume all environments contain critical data
160
+
82
161
  ## Workflow
83
162
 
84
163
  1. Create feature branch
85
- 2. Implement with types, strict mode
164
+ 2. Implement with types, strict mode, Octane-safe patterns
86
165
  3. Run PHPStan + PHPUnit
87
166
  4. Update domains & CLAUDE.md
88
167
  5. Commit → merge to main