cyberaudit-skill 3.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 (60) hide show
  1. package/README.md +77 -0
  2. package/dist/cli.d.ts +2 -0
  3. package/dist/cli.js +241 -0
  4. package/dist/mcp-server.d.ts +2 -0
  5. package/dist/mcp-server.js +126 -0
  6. package/package.json +58 -0
  7. package/skills/cyberaudit/AGENT-BOOT.md +122 -0
  8. package/skills/cyberaudit/COMMANDS.md +1125 -0
  9. package/skills/cyberaudit/INSTALL.md +311 -0
  10. package/skills/cyberaudit/MASTER.md +194 -0
  11. package/skills/cyberaudit/README.md +154 -0
  12. package/skills/cyberaudit/USAGE-GUIDE.md +107 -0
  13. package/skills/cyberaudit/mobile/MOBILE-CHECKLIST.md +245 -0
  14. package/skills/cyberaudit/mobile/MOBILE-PHILOSOPHY.md +352 -0
  15. package/skills/cyberaudit/mobile/MOBILE-REMEDIATION-LIBRARY.md +468 -0
  16. package/skills/cyberaudit/mobile/MOBILE-THREAT-MODELS.md +279 -0
  17. package/skills/cyberaudit/mobile/frameworks/EXPO.md +247 -0
  18. package/skills/cyberaudit/mobile/frameworks/FLUTTER.md +338 -0
  19. package/skills/cyberaudit/mobile/frameworks/IONIC.md +248 -0
  20. package/skills/cyberaudit/mobile/frameworks/REACT-NATIVE.md +479 -0
  21. package/skills/cyberaudit/mobile/vulnerabilities/AUTH-MOBILE.md +160 -0
  22. package/skills/cyberaudit/mobile/vulnerabilities/BINARY-ANALYSIS.md +193 -0
  23. package/skills/cyberaudit/mobile/vulnerabilities/CRYPTO-MOBILE.md +192 -0
  24. package/skills/cyberaudit/mobile/vulnerabilities/IPC-DEEPLINKS.md +231 -0
  25. package/skills/cyberaudit/mobile/vulnerabilities/NETWORK-MOBILE.md +165 -0
  26. package/skills/cyberaudit/mobile/vulnerabilities/PERMISSIONS.md +180 -0
  27. package/skills/cyberaudit/mobile/vulnerabilities/RUNTIME-MOBILE.md +214 -0
  28. package/skills/cyberaudit/mobile/vulnerabilities/STORAGE.md +197 -0
  29. package/skills/cyberaudit/reports/EXECUTIVE-SUMMARY-TEMPLATE.md +188 -0
  30. package/skills/cyberaudit/reports/REPORT-TEMPLATE-MOBILE.md +410 -0
  31. package/skills/cyberaudit/reports/REPORT-TEMPLATE-WEB.md +222 -0
  32. package/skills/cyberaudit/shared/COMPLIANCE.md +307 -0
  33. package/skills/cyberaudit/shared/CVSS-GUIDE.md +175 -0
  34. package/skills/cyberaudit/shared/OWASP-MAPPER.md +130 -0
  35. package/skills/cyberaudit/shared/SEVERITY-SCORING.md +102 -0
  36. package/skills/cyberaudit/shared/THREAT-MODELING.md +112 -0
  37. package/skills/cyberaudit/web/WEB-CHECKLIST.md +222 -0
  38. package/skills/cyberaudit/web/WEB-PHILOSOPHY.md +308 -0
  39. package/skills/cyberaudit/web/WEB-REMEDIATION-LIBRARY.md +665 -0
  40. package/skills/cyberaudit/web/WEB-THREAT-MODELS.md +460 -0
  41. package/skills/cyberaudit/web/frameworks/ANGULAR.md +169 -0
  42. package/skills/cyberaudit/web/frameworks/EXPRESS.md +185 -0
  43. package/skills/cyberaudit/web/frameworks/LARAVEL.md +371 -0
  44. package/skills/cyberaudit/web/frameworks/NESTJS.md +337 -0
  45. package/skills/cyberaudit/web/frameworks/NEXTJS.md +352 -0
  46. package/skills/cyberaudit/web/frameworks/REACT.md +346 -0
  47. package/skills/cyberaudit/web/frameworks/VUE.md +205 -0
  48. package/skills/cyberaudit/web/vulnerabilities/AUTH-AUTHZ.md +117 -0
  49. package/skills/cyberaudit/web/vulnerabilities/BUSINESS-LOGIC.md +603 -0
  50. package/skills/cyberaudit/web/vulnerabilities/CORS.md +117 -0
  51. package/skills/cyberaudit/web/vulnerabilities/CSRF.md +131 -0
  52. package/skills/cyberaudit/web/vulnerabilities/DESERIALIZATION.md +96 -0
  53. package/skills/cyberaudit/web/vulnerabilities/HEADERS.md +105 -0
  54. package/skills/cyberaudit/web/vulnerabilities/IDOR-BOLA.md +153 -0
  55. package/skills/cyberaudit/web/vulnerabilities/INJECTION.md +165 -0
  56. package/skills/cyberaudit/web/vulnerabilities/SECRETS.md +119 -0
  57. package/skills/cyberaudit/web/vulnerabilities/SSRF.md +124 -0
  58. package/skills/cyberaudit/web/vulnerabilities/SUPPLY-CHAIN.md +142 -0
  59. package/skills/cyberaudit/web/vulnerabilities/XSS.md +133 -0
  60. package/skills/cyberaudit/web/vulnerabilities/XXE.md +94 -0
@@ -0,0 +1,603 @@
1
+ # 🧠 BUSINESS LOGIC — CYBERAUDIT SKILL
2
+ # Business logic vulnerabilities — The hardest to detect
3
+
4
+ ═══════════════════════════════════════════════════════════════
5
+ NATURE OF THESE VULNERABILITIES :
6
+
7
+ Business logic flaws are not detectable
8
+ by automated scanners.
9
+ They require understanding WHAT THE APP IS SUPPOSED TO DO
10
+ to identify WHAT THE APP ACTUALLY DOES.
11
+
12
+ A scanner sees code. An expert sees intent.
13
+ These flaws require being the expert.
14
+ ═══════════════════════════════════════════════════════════════
15
+
16
+ ═══════════════════════════════════════════════════════════════
17
+ BUSINESS LOGIC FLAW CATEGORIES
18
+ ═══════════════════════════════════════════════════════════════
19
+
20
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
21
+ CATEGORY 1 — PRICE AND VALUE MANIPULATION
22
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
23
+
24
+ PRINCIPLE :
25
+ Price, quantity, amount must NEVER
26
+ come from the client. Always recalculated server-side.
27
+
28
+ CRITICAL PATTERN 1 — Price sent by client
29
+ DETECT :
30
+ // Frontend sends the price
31
+ POST /api/checkout
32
+ {
33
+ "product_id": 42,
34
+ "quantity": 1,
35
+ "price": 9.99 ← Price provided by client!
36
+ }
37
+
38
+ // And the backend uses it directly
39
+ public function checkout(Request $request) {
40
+ $order = Order::create([
41
+ 'product_id' => $request->product_id,
42
+ 'quantity' => $request->quantity,
43
+ 'price' => $request->price, // ← VULNERABLE
44
+ 'total' => $request->price * $request->quantity
45
+ ]);
46
+ $this->processPayment($order->total);
47
+ }
48
+
49
+ EXPLOITATION SCENARIO :
50
+ Attacker modifies request with Burp Suite :
51
+ "price": 0.01 → Buys a 999€ product for 0.01€
52
+ "price": -1 → Receives a refund on purchase
53
+ "quantity": -1 → Possible inverted logic
54
+
55
+ FIX :
56
+ public function checkout(Request $request) {
57
+ // Never trust the client's price
58
+ // ALWAYS recalculate server-side
59
+ $product = Product::findOrFail($request->product_id);
60
+
61
+ // Verify product is available
62
+ if (!$product->is_available) {
63
+ return response()->json(['error' => 'Product unavailable'], 400);
64
+ }
65
+
66
+ // Validate quantity
67
+ $quantity = (int) $request->quantity;
68
+ if ($quantity < 1 || $quantity > 100) {
69
+ return response()->json(['error' => 'Invalid quantity'], 400);
70
+ }
71
+
72
+ // Calculate price SERVER-SIDE only
73
+ $unitPrice = $product->current_price; // Price from DB
74
+ $discount = $this->calculateDiscount($product, auth()->user());
75
+ $total = ($unitPrice * $quantity) * (1 - $discount);
76
+
77
+ $order = Order::create([
78
+ 'user_id' => auth()->id(),
79
+ 'product_id' => $product->id,
80
+ 'quantity' => $quantity,
81
+ 'unit_price' => $unitPrice, // Server price
82
+ 'discount' => $discount,
83
+ 'total' => $total, // Server-calculated
84
+ ]);
85
+
86
+ $this->processPayment($order->total); // Server amount
87
+ }
88
+
89
+ CRITICAL PATTERN 2 — Negative values not validated
90
+ DETECT :
91
+ // Money transfer without sign validation
92
+ public function transfer(Request $request) {
93
+ $amount = $request->amount; // Can be negative!
94
+
95
+ auth()->user()->balance -= $amount;
96
+ $recipient->balance += $amount;
97
+ // If amount = -100 : the sender GAINS 100!
98
+ }
99
+
100
+ // Coupon application without validation
101
+ $discount = $coupon->discount_percentage / 100;
102
+ $total = $price - ($price * $discount);
103
+ // If discount = 150% : negative total → refund?
104
+
105
+ FIX :
106
+ public function transfer(Request $request) {
107
+ $request->validate([
108
+ 'amount' => 'required|numeric|min:0.01|max:10000',
109
+ // min:0.01 prevents negative values and zero
110
+ ]);
111
+
112
+ $amount = round((float)$request->amount, 2);
113
+
114
+ // Check balance before transaction
115
+ if (auth()->user()->balance < $amount) {
116
+ return response()->json(['error' => 'Insufficient balance'], 400);
117
+ }
118
+
119
+ DB::transaction(function() use ($amount, $recipient) {
120
+ auth()->user()->decrement('balance', $amount);
121
+ $recipient->increment('balance', $amount);
122
+
123
+ // Transaction log for audit
124
+ Transaction::create([
125
+ 'from_user_id' => auth()->id(),
126
+ 'to_user_id' => $recipient->id,
127
+ 'amount' => $amount,
128
+ 'ip_address' => request()->ip(),
129
+ ]);
130
+ });
131
+ }
132
+
133
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
134
+ CATEGORY 2 — RACE CONDITIONS
135
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
136
+
137
+ PRINCIPLE :
138
+ Two simultaneous requests can both pass
139
+ the same check "before" the state changes.
140
+ Result : double spend, double action, double allocation.
141
+
142
+ CRITICAL PATTERN 3 — Double spend / Double action
143
+ DETECT :
144
+ // Code that checks THEN modifies without atomicity
145
+ public function usePromoCode(Request $request) {
146
+ $code = PromoCode::where('code', $request->code)
147
+ ->where('used', false) // ← Check
148
+ ->first();
149
+
150
+ if (!$code) {
151
+ return response()->json(['error' => 'Invalid code'], 400);
152
+ }
153
+
154
+ // ← RACE CONDITION HERE
155
+ // Two simultaneous requests pass the check
156
+ // before either one sets used = true
157
+
158
+ $code->update(['used' => true]);
159
+ $this->applyDiscount(auth()->user());
160
+ }
161
+
162
+ EXPLOITATION SCENARIO :
163
+ Attacker sends 10 simultaneous requests.
164
+ All see used = false.
165
+ All apply the discount.
166
+ Code used 10 times instead of once.
167
+
168
+ FIX :
169
+ public function usePromoCode(Request $request) {
170
+ // Option A : Atomic update with where
171
+ $updated = PromoCode::where('code', $request->code)
172
+ ->where('used', false)
173
+ ->update([
174
+ 'used' => true,
175
+ 'used_by' => auth()->id(),
176
+ 'used_at' => now(),
177
+ ]);
178
+
179
+ // If no rows updated → code already used
180
+ if ($updated === 0) {
181
+ return response()->json(['error' => 'Invalid or already used code'], 400);
182
+ }
183
+
184
+ // Option B : DB Lock (for more complex cases)
185
+ DB::transaction(function() use ($request) {
186
+ $code = PromoCode::where('code', $request->code)
187
+ ->lockForUpdate() // SELECT ... FOR UPDATE
188
+ ->first();
189
+
190
+ if (!$code || $code->used) {
191
+ throw new \Exception('Invalid code');
192
+ }
193
+
194
+ $code->update(['used' => true, 'used_by' => auth()->id()]);
195
+ $this->applyDiscount(auth()->user());
196
+ });
197
+
198
+ // Option C : Redis for distributed operations
199
+ $key = "promo_code_lock:{$request->code}";
200
+ if (!Cache::add($key, true, 30)) { // NX = only if absent
201
+ return response()->json(['error' => 'Code already being used'], 429);
202
+ }
203
+ // Normal processing...
204
+ }
205
+
206
+ HIGH PATTERN 4 — Race condition on balance/stock
207
+ DETECT :
208
+ // Stock check without atomicity
209
+ if ($product->stock > 0) {
210
+ // ← Race condition possible
211
+ $product->decrement('stock');
212
+ // Stock can go negative with simultaneous requests
213
+ }
214
+
215
+ FIX :
216
+ // Atomic decrement with verification
217
+ $updated = Product::where('id', $product->id)
218
+ ->where('stock', '>', 0)
219
+ ->decrement('stock');
220
+
221
+ if ($updated === 0) {
222
+ return response()->json(['error' => 'Out of stock'], 400);
223
+ }
224
+
225
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
226
+ CATEGORY 3 — WORKFLOW BYPASS
227
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
228
+
229
+ PRINCIPLE :
230
+ Process steps (order, verification,
231
+ approval) must be in order and verified.
232
+ Do not assume the previous step was done.
233
+
234
+ HIGH PATTERN 5 — Skipped workflow step
235
+ DETECT :
236
+ // Checkout without verifying cart is valid
237
+ public function confirmOrder(Request $request) {
238
+ $order = Order::findOrFail($request->order_id);
239
+ // No verification that payment was made!
240
+ // No verification of order status!
241
+ $order->update(['status' => 'confirmed']);
242
+ $this->shipOrder($order);
243
+ }
244
+
245
+ // Publication without validation
246
+ public function publishArticle(Request $request) {
247
+ $article = Article::findOrFail($request->id);
248
+ // No verification article is in "draft" status
249
+ // Can a "rejected" article be republished directly?
250
+ $article->update(['status' => 'published']);
251
+ }
252
+
253
+ FIX :
254
+ public function confirmOrder(Request $request) {
255
+ $order = Order::where('id', $request->order_id)
256
+ ->where('user_id', auth()->id())
257
+ ->firstOrFail();
258
+
259
+ // Explicit state machine
260
+ $allowedTransitions = [
261
+ 'pending' => ['paid', 'cancelled'],
262
+ 'paid' => ['confirmed', 'refunded'],
263
+ 'confirmed' => ['shipped', 'cancelled'],
264
+ 'shipped' => ['delivered'],
265
+ ];
266
+
267
+ $targetStatus = 'confirmed';
268
+ $allowedNextStates = $allowedTransitions[$order->status] ?? [];
269
+
270
+ if (!in_array($targetStatus, $allowedNextStates)) {
271
+ return response()->json([
272
+ 'error' => "Invalid transition: {$order->status} → {$targetStatus}"
273
+ ], 422);
274
+ }
275
+
276
+ // Check specific prerequisites
277
+ if ($targetStatus === 'confirmed' && !$order->payment_verified) {
278
+ return response()->json(['error' => 'Payment not verified'], 400);
279
+ }
280
+
281
+ $order->update(['status' => $targetStatus]);
282
+ }
283
+
284
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
285
+ CATEGORY 4 — FEATURE ABUSE
286
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
287
+
288
+ PRINCIPLE :
289
+ A legitimate feature can be abused
290
+ in a way not anticipated by the developer.
291
+
292
+ HIGH PATTERN 6 — Circular reference / self-reference
293
+ DETECT :
294
+ // Transfer to self
295
+ public function transfer(Request $request) {
296
+ // No check that from ≠ to
297
+ $sender = auth()->user();
298
+ $recipient = User::findOrFail($request->to_user_id);
299
+
300
+ // If sender = recipient : unexpected behavior?
301
+ // If calculation error : money created?
302
+ }
303
+
304
+ // Self-referral
305
+ public function applyReferral(Request $request) {
306
+ // No check that referrer ≠ referred
307
+ $referrer = User::where('code', $request->code)->first();
308
+ $this->applyBonus($referrer, auth()->user());
309
+ }
310
+
311
+ FIX :
312
+ public function transfer(Request $request) {
313
+ $request->validate([
314
+ 'to_user_id' => 'required|integer|different:' . auth()->id(),
315
+ // different: prevents self-transfer
316
+ ]);
317
+ }
318
+
319
+ public function applyReferral(Request $request) {
320
+ $referrer = User::where('referral_code', $request->code)
321
+ ->where('id', '!=', auth()->id()) // Not self
322
+ ->first();
323
+
324
+ if (!$referrer) {
325
+ return response()->json(['error' => 'Invalid referral code'], 400);
326
+ }
327
+
328
+ // Check they haven't already used a code
329
+ if (auth()->user()->referred_by !== null) {
330
+ return response()->json(['error' => 'Code already used'], 400);
331
+ }
332
+ }
333
+
334
+ MEDIUM PATTERN 7 — Pagination/export abuse
335
+ DETECT :
336
+ // Export without limit
337
+ public function exportUsers(Request $request) {
338
+ // No limit → export of the ENTIRE database
339
+ return User::all()->toArray(); // Millions of records?
340
+
341
+ // Or pagination without limit
342
+ $limit = $request->limit ?? 100;
343
+ // Attacker sends limit=1000000
344
+ return User::paginate($limit);
345
+ }
346
+
347
+ FIX :
348
+ public function exportUsers(Request $request) {
349
+ $request->validate([
350
+ 'limit' => 'integer|max:1000', // Server-imposed maximum
351
+ ]);
352
+
353
+ // Export by chunks with absolute limit
354
+ $maxExport = 10000;
355
+ $limit = min($request->limit ?? 100, $maxExport);
356
+
357
+ // Log the export (traceability)
358
+ AuditLog::create([
359
+ 'action' => 'data_export',
360
+ 'user_id' => auth()->id(),
361
+ 'count' => $limit,
362
+ 'ip' => request()->ip(),
363
+ ]);
364
+
365
+ return User::select(['id', 'name', 'email']) // Minimal fields
366
+ ->paginate($limit);
367
+ }
368
+
369
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
370
+ CATEGORY 5 — TOCTOU (TIME OF CHECK / TIME OF USE)
371
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
372
+
373
+ PRINCIPLE :
374
+ The state verified at check time may have changed
375
+ by the time of use.
376
+ Check and use must be atomic.
377
+
378
+ HIGH PATTERN 8 — TOCTOU on eligibility
379
+ DETECT :
380
+ // Eligibility check followed by non-atomic action
381
+ public function claimReward(Request $request) {
382
+ $user = auth()->user();
383
+
384
+ // CHECK : Is the user eligible?
385
+ if ($user->points >= 1000 && !$user->reward_claimed) {
386
+
387
+ // ← TOCTOU HERE
388
+ // Between check and update,
389
+ // another request can modify state
390
+
391
+ sleep(1); // Simulate processing
392
+
393
+ // USE : Assign reward
394
+ $user->update([
395
+ 'reward_claimed' => true,
396
+ 'points' => $user->points - 1000
397
+ ]);
398
+
399
+ $this->sendReward($user);
400
+ }
401
+ }
402
+
403
+ FIX :
404
+ public function claimReward(Request $request) {
405
+ // Atomic check and update
406
+ $updated = User::where('id', auth()->id())
407
+ ->where('points', '>=', 1000)
408
+ ->where('reward_claimed', false)
409
+ ->update([
410
+ 'reward_claimed' => true,
411
+ 'points' => DB::raw('points - 1000'),
412
+ 'reward_date' => now(),
413
+ ]);
414
+
415
+ if ($updated === 0) {
416
+ return response()->json([
417
+ 'error' => 'Not eligible or reward already claimed'
418
+ ], 400);
419
+ }
420
+
421
+ $this->sendReward(auth()->user()->fresh());
422
+ }
423
+
424
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
425
+ CATEGORY 6 — UNVERIFIED LIMITS AND BOUNDARIES
426
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
427
+
428
+ MEDIUM PATTERN 9 — Business limits not enforced server-side
429
+ DETECT :
430
+ // Account limit per email not checked server-side
431
+ public function register(Request $request) {
432
+ // The "1 account per person" limit is only checked
433
+ // client-side → trivial bypass
434
+ User::create([...]);
435
+ }
436
+
437
+ // Offer usage limit
438
+ public function claimOffer(Request $request) {
439
+ // "One offer per user" only verified
440
+ // by a UI constraint
441
+ Offer::create(['user_id' => auth()->id()]);
442
+ }
443
+
444
+ FIX :
445
+ public function register(Request $request) {
446
+ // Server-side limit : 1 active account per email
447
+ $existingCount = User::where('email', $request->email)
448
+ ->where('status', 'active')
449
+ ->count();
450
+
451
+ if ($existingCount >= 1) {
452
+ return response()->json([
453
+ 'error' => 'An account already exists with this email'
454
+ ], 422);
455
+ }
456
+
457
+ // IP-based limit (anti-mass creation)
458
+ $recentRegistrations = User::where('registration_ip', $request->ip())
459
+ ->where('created_at', '>', now()->subHour())
460
+ ->count();
461
+
462
+ if ($recentRegistrations >= 3) {
463
+ return response()->json([
464
+ 'error' => 'Too many accounts created from this address'
465
+ ], 429);
466
+ }
467
+
468
+ User::create([..., 'registration_ip' => $request->ip()]);
469
+ }
470
+
471
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
472
+ CATEGORY 7 — STATE AND PARAMETER MANIPULATION
473
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
474
+
475
+ HIGH PATTERN 10 — Injectable context parameter
476
+ DETECT :
477
+ // Multi-tenant : tenant_id provided by client
478
+ public function getData(Request $request) {
479
+ $tenantId = $request->tenant_id; // ← Client controls!
480
+
481
+ $data = Data::where('tenant_id', $tenantId)->get();
482
+ // A user can access another tenant's data!
483
+ }
484
+
485
+ // Currency/locale manipulation
486
+ public function getPrice(Request $request) {
487
+ $currency = $request->currency ?? 'EUR';
488
+ $price = Product::find($id)->price;
489
+ return $this->convert($price, 'EUR', $currency);
490
+ // If conversion is poorly implemented → absurd prices
491
+ }
492
+
493
+ FIX :
494
+ public function getData(Request $request) {
495
+ // Always get tenant from session/token
496
+ // NEVER from the client request
497
+ $tenantId = auth()->user()->tenant_id; // From identity
498
+
499
+ $data = Data::where('tenant_id', $tenantId)->get();
500
+ }
501
+
502
+ public function getPrice(Request $request) {
503
+ // Validate currency against a whitelist
504
+ $allowedCurrencies = ['EUR', 'USD', 'GBP', 'JPY'];
505
+ $currency = in_array($request->currency, $allowedCurrencies)
506
+ ? $request->currency
507
+ : 'EUR'; // Secure default
508
+
509
+ $price = Product::findOrFail($id)->price;
510
+ return $this->convert($price, 'EUR', $currency);
511
+ }
512
+
513
+ ═══════════════════════════════════════════════════════════════
514
+ DETECTION METHODOLOGY FOR THE AGENT
515
+ ═══════════════════════════════════════════════════════════════
516
+
517
+ QUESTIONS TO ASK FOR EACH BUSINESS FEATURE
518
+ ────────────────────────────────────────
519
+
520
+ □ Do monetary values/quantities come from the client?
521
+ → Always recalculate server-side
522
+
523
+ □ Is there a "check then act" without atomicity?
524
+ → Race condition risk → atomize with DB transaction
525
+
526
+ □ Are workflow steps verified sequentially?
527
+ → Implement an explicit state machine
528
+
529
+ □ Can a feature be used on oneself?
530
+ → Check self-references
531
+
532
+ □ Are business limits enforced server-side?
533
+ → All business rules must be server-side
534
+
535
+ □ Does a context parameter (tenant, currency, role) come
536
+ from the client?
537
+ → Always extract from token/session, never from body
538
+
539
+ □ What happens with extreme values?
540
+ (0, -1, MAX_INT, empty string, very long string)
541
+ → Test edge cases
542
+
543
+ □ What happens if the action is repeated N times?
544
+ → Idempotence and uniqueness
545
+
546
+ □ What happens if steps are done out of order?
547
+ → Workflow enforcement
548
+
549
+ □ Who benefits financially or functionally
550
+ from exploiting this flaw?
551
+ → Identify attacker motivation
552
+
553
+ ═══════════════════════════════════════════════════════════════
554
+ BUSINESS LOGIC CHECKLIST
555
+ ═══════════════════════════════════════════════════════════════
556
+
557
+ VALUES AND CALCULATIONS
558
+ □ Prices/amounts calculated server-side only?
559
+ □ Negative values rejected on quantities/amounts?
560
+ □ Zero values handled correctly?
561
+ □ Numeric overflow/underflow handled?
562
+ □ Consistent monetary rounding (no naive floating point)?
563
+
564
+ CONCURRENCY
565
+ □ Critical operations atomic (DB transactions)?
566
+ □ Locks used on shared resources?
567
+ □ Idempotence on replayable operations?
568
+ □ Double-submit prevention (forms, buttons)?
569
+
570
+ WORKFLOW
571
+ □ Explicit state machine for multi-step processes?
572
+ □ State transitions validated server-side?
573
+ □ Prerequisites for each step verified before execution?
574
+ □ Rollback on partial failure?
575
+
576
+ BUSINESS LIMITS
577
+ □ All "1 per user", "max N" rules server-side?
578
+ □ Self-references prohibited (self-transfer, self-referral)?
579
+ □ Context parameters (tenant, role) from session?
580
+ □ Export/pagination limits enforced server-side?
581
+
582
+ EDGE CASES
583
+ □ Behavior with banned/suspended user verified?
584
+ □ Behavior at end of offer period verified?
585
+ □ Behavior with zero stock verified?
586
+ □ Behavior at boundary timezones?
587
+
588
+ ═══════════════════════════════════════════════════════════════
589
+ REFERENCES
590
+ ═══════════════════════════════════════════════════════════════
591
+
592
+ OWASP Testing Guide — Business Logic Testing :
593
+ https://owasp.org/www-project-web-security-testing-guide/
594
+ latest/4-Web_Application_Security_Testing/10-Business_Logic_Testing/
595
+
596
+ CWE-840 — Business Logic Errors :
597
+ https://cwe.mitre.org/data/definitions/840.html
598
+
599
+ OWASP A04:2021 — Insecure Design :
600
+ https://owasp.org/Top10/A04_2021-Insecure_Design/
601
+
602
+ PortSwigger Business Logic Vulnerabilities :
603
+ https://portswigger.net/web-security/logic-flaws