---
name: evolution_ajax_save_auth_gate
description: "Evolution AJAX *Save.php data/report handlers need a mod_* gate, not just checkSession()"
metadata: 
  node_type: memory
  type: feedback
  originSessionId: affdac8f-96f8-48a3-8a9a-ad71e22bbff1
---

In evolution, `checkSession()` only confirms a logged-in session — it does NOT block or enforce module permissions, and a customer-portal session shape can hold a valid session. So any AJAX `*Save.php` handler that returns sensitive data (labour cost, margin, per-employee figures, company-wide reports) must add an explicit `mod_*` gate after the Content-Type header, e.g.:

```php
if (($_SESSION['mod_rep'] ?? 0) < 1) {
	http_response_code(403);
	echo json_encode(['error' => 'You do not have permission to view this report.']);
	die;
}
```

Match the module to whatever screen hosts the endpoint (the 9 timesheet billing report handlers — 8 `reports/ts*Save.php` + `timesheetMonthlyBillingSave.php` — use `mod_rep`, consistent with the reportdb Reports Dashboard).

**Why:** these endpoints are reachable directly by URL regardless of UI gating; relying on the front-end screen's permission check leaves the data fully exposed to any authenticated session.

**How to apply:** when reviewing a merge, grep new/changed `*Save.php` (and `app/ajax/*`) handlers for ones whose only auth is `checkSession()` and that emit business data — flag each as an authz gap and add the matching `mod_*` gate. Relates to [[evolution_permission_system]] (the mod_* 0/1/2 model) and is the same "validate at the server endpoint, not just the client" review pattern as [[evolution_sort_injection_validator]].
