# Final security report

Active-role-only authorisation is unchanged. No new permission codes or schema were added in Phase 7.

## Revalidated controls

| Control | Result | Evidence |
|---|---|---|
| Active-role-only | PASS | `AuthorizationService::can` uses `permissionCodesForRole(activeRoleId)` |
| No permission union | PASS | S. Patil Store Keeper session omits `INVOICE.ADD` |
| Company/Plant object GET | PASS | Out-of-scope GET returns **403** `SCOPE_DENIED` |
| CSRF | PASS | Global `csrf` filter |
| Session regeneration / `session_version` | PASS | Phase 6 tests retained |
| Argon2id | PASS | `PasswordHasher` `PASSWORD_ARGON2ID` |
| First-login gate | PASS | `/account/password` only until changed; Chrome fixture `qa.p7.first` |
| Temporary credential expiry | PASS | Phase 6 IAM domain tests |
| Hybrid C anti-escalation | PASS | Phase 6 IAM domain tests |
| Last interactive Super Admin | PASS | Phase 6 session tests |
| SYSTEM non-interactive | PASS | Login rejected; zero permissions |
| Immutable audit | PASS | No edit/delete routes; `AuditWriter::redactSecrets` |
| AUDIT.EXPORT vs REPORT.EXPORT | PASS | Independent grants; not widened |
| Opaque export tokens / 30-day expiry | PASS | Phase 5 export tests |
| CSV formula injection | PASS | CsvSafe tests |
| Secrets excluded from logs/audit | PASS | password_hash / tokens redacted |
| Production ignores `?frame=` | PASS | `FrameSimulation::overlayAllowed('production') === false` |
| Unauthorised POST | PASS | Permission denied mutations return **403** |
| No seed/demo/probe routes | PASS | `Routes.php` scan |
| No HTTP DELETE | PASS | No `$routes->delete` |
| No Credit Note Cancel/Delete/Void | PASS | Routes absent |
| No automatic IRN job/queue/retry | PASS | Commands + copy; no `irn:` cron |

## Checkpoint F application corrections

Object-level Company/Plant GET that previously returned empty **200** now returns **403**. Unauthorised POST no longer uses a flash redirect for permission denial.

## Residual notes

- CI4 debugbar can emit console noise in **development** only. Production must set `CI_ENVIRONMENT=production`.
- Checkpoint I created throwaway users `qa.p7.first` / `qa.p7.dead` on the local demo database, then **disabled** them. Their audit rows were retained (audit is immutable). They are not production identities.
