# Security Audit Report Summary

This document summarizes the findings from `docs/security-audit-2026-06-22.md` and tracks their current status as of the latest review.

## Audit scope

- **Original audit date:** 2026-06-22
- **Last status update:** 2026-07-06
- **Framework:** Laravel 13.8
- **Methodology:** Read-only source review + targeted runtime checks
- **Scope:** `app/`, `routes/`, `config/`, `database/`, `resources/views/`

## Overall posture

The application has an **improved** defensive posture with proper use of Form Requests, Eloquent parameter binding, role-based middleware, and policies on most resources. All high and medium findings from the 2026-06-22 audit and the follow-up review have been implemented or explicitly mitigated.

## Critical finding

### Global `Model::unguard()`

- **Location:** `app/Providers/AppServiceProvider.php`
- **Impact:** Disables Laravel's mass-assignment protection globally.
- **Status:** Intentionally retained per project decision (`AGENTS.md`). Removing it requires adding `$fillable`/`$guarded` to every model.

## High findings

| Issue | Status |
|-------|--------|
| Default super-admin password in seeder | Fixed — no default admin created by `DatabaseSeeder`; use `admin:create-super` command. |
| IDOR in bulk unit creation | Fixed — ownership verified. |
| IDOR in unit listing | Mitigated by design — public marketplace browse allowed; owner/employee scoped routes restrict results. |
| Cross-owner `unit_id` changes in reservations | Fixed — owner boundary checks added. |
| Empty collection returns all availabilities | Fixed — `whereIn` applied even for empty sets. |
| Reservation / on-arrival ID documents on public disk | Fixed — `documents` collection moved to the private `local` disk; served via authorized `GET /api/v1/media/{media}/document`. |

## Medium findings

| Issue | Status |
|-------|--------|
| Dependency CVEs in `guzzlehttp/guzzle` and `guzzlehttp/psr7` | Fixed at time of audit; monitor via `composer audit`. |
| CORS defaults | Fixed — `config/cors.php` published and `.env.example` restricts origins. |
| Missing security headers | Fixed — `SecurityHeadersMiddleware` added globally. |
| Debug mode and request docs in local config | Fixed — `.env.example` sets `APP_DEBUG=false` and `REQUEST_DOCS_ENABLED=false`; `NotFoundWhenProduction` enabled. |
| Sanctum token expiration | Fixed — token TTL set to 1 week in `config/sanctum.php`. |
| OTP returned in responses in local/testing | Intentional for development; not returned in production. |
| Login enumeration | Fixed — employee WhatsApp login now returns the same generic `401` as other failed logins. The verify-account → force-change-password flow still forces new employees to change their password on first login. |
| Stored content output encoding | Documented — clients must HTML-escape API strings. |
| Account enumeration via verify/resend OTP | Fixed — missing users receive the same generic response as invalid OTPs. |
| Weak password policy | Fixed — `Password::defaults()` now requires min 12 characters with mixed case, numbers, and symbols. |
| `verifyAccount` issues token for already-verified users | Fixed — already-verified accounts are rejected. |
| Inconsistent upload validation / `FileIsTooBig` exceptions | Fixed — `PhotoFileRules` and `config/media-library.php` both allow 20 MB; controllers catch `FileIsTooBig` and return validation errors. |
| Super-admin CLI accepts `--password` on the command line | Fixed — option removed; command always prompts securely with `$this->secret('Password')`. |
| Scheduled commands lack `onOneServer()` | Fixed — both scheduled commands now use `->onOneServer()`. |
| `AppDatabaseChannel` dead code | Fixed — removed. |

## Low findings

- Generated `api.json` / `routes.json` files added to `.gitignore`. ✅
- Password hashes excluded from audit logs via `User::$auditExclude`. ✅
- Exception messages no longer reflected in API responses. ✅
- `locations:download` now uses a pinned release URL, validates JSON schema, and writes output with restrictive permissions. ✅
- Password-reset email notification not implemented; OTP-based reset via WhatsApp is used. ⏳
- Upload validation should be standardized across endpoints. ✅
- Employee creation flow should include verification. ⏳
- Mail driver defaults to `log`; must be overridden in production. ⏳
- All WhatsApp notifications are now queued (`ShouldQueue`). ✅

## Positive findings

- No classic SQL injection (Eloquent/Query Builder with parameter binding).
- No server-side Blade XSS (`resources/views/` contains only the password-reset email view).
- Rate limiting on public auth routes.
- Role/permission middleware on admin, owner, employee, and customer routes.
- Policy usage on most resources.
- `.env` is gitignored.

## Current status

As of the latest review, all tests pass and all fixable high/medium audit items have been implemented. Remaining accepted risks and open hardening items are tracked in `docs/REMAINING_FIXES.md`.
