# ICOMAN Security Audit

Audited 2026-09-20 against the application routes, controllers, services, policies, views, configuration, and automated feature tests.

## Findings and remediation

| Severity | Area | Finding | Resolution |
|---|---|---|---|
| High | Organizational authorization / IDOR | A state or regional approver could submit `REJECTED` for an out-of-scope application because scope validation depended on the destination status. | `TransitionMembershipApplication` now authorizes against the application’s current workflow stage and assignment scope before every decision. |
| High | BOT card approval | Route permission alone was insufficient assurance that a caller held the BOT Chairman office. | The action requires both `cards.approve_print` and the `bot_chairman` role; a privileged non-chairman direct-call regression test now proves denial. |
| Medium | Card file IDOR | Card generation/download routes accepted an arbitrary card ID without member-scope enforcement. | Both operations now require the caller to pass the `MemberPolicy` view check for the card owner. |
| Medium | Bulk-import handling | Upload validation accepted generic files before service parsing; XLSX parsing had no archive/row limits, and the processor referenced an unimported DB facade. | Controller MIME validation, CSV/XLSX extension checks, 10,000-row and 50 MiB expanded-XLSX limits, workbook validation, and the DB facade import were added. Files remain on the private local disk. |
| Medium | Session hardening | Sessions were not encrypted and persisted after browser close by default. | Encryption and close-on-browser-exit now default to enabled; HttpOnly, SameSite, and production HTTPS-only cookies remain configured. |
| Low | Response hardening | Baseline headers did not include object blocking or cross-origin isolation protections. | CSP now blocks objects; COOP/CORP, frame denial, MIME sniffing prevention, referrer and permissions policies are set, with HSTS on secure production requests. |

## Verified controls

- Authentication: login throttled at five attempts/minute per normalized email/IP; inactive users are rejected; sessions regenerate at login and are invalidated/token-rotated on logout.
- Authorization/RBAC: permission middleware is applied to staff routes; `MemberPolicy` denies cross-state and cross-region API member access. The test suite now also denies cross-state URL-parameter rejection.
- Member access and files: member documents require ownership and approval status; generated cards require policy scope; import reports require owner/national/superadmin access.
- Card approval/activation: BOT printing approval is role-and-permission constrained, identity locking and audit records are retained, and card lifecycle transitions reject invalid states. No public card activation endpoint is exposed.
- QR/barcode: public verification returns explicit allow-listed fields only after live card/member/token checks. Revoked QR tokens return `REVOKED` with no member identity; revoked barcodes are also rejected by the same verifier.
- API endpoints: the member API requires auth, active/verified user, `members.view`, and `MemberPolicy`; it emits an explicit field list rather than model serialization.
- CSRF/XSS/SQLi: state-changing browser routes use Laravel CSRF middleware with no exemptions; reviewed Blade views retain escaped output; request values are validated and reviewed queries use bindings (including the single `whereRaw` placeholder query).
- Audit logging: membership transitions, correction requests, import lifecycle, card approval, and public verification are recorded.

## Tests run

- `php artisan test --filter="RbacScopeTest|SecurityRegressionTest|MemberPortalTest|CardLifecycleTest"` — 10 passing tests.
- State Admin cross-state API and approval URL manipulation — denied.
- Privileged administrator calling BOT Chairman approval URL directly — denied.
- Revoked QR portal response — `REVOKED`, with no membership identifier.

## Deployment requirements

- Set `APP_ENV=production`, `APP_DEBUG=false`, force HTTPS, and set `SESSION_SECURE_COOKIE=true`.
- Keep `storage` outside the web root; add antivirus/content scanning before any future member-document upload endpoint is exposed.
- Use a durable queue backend (not `sync`) and monitoring for production imports/notifications.
- Password-reset controllers are not routed or implemented; keep them unavailable until Laravel’s password broker flow, notifications, reset validation, and throttling are implemented.
