# OGSG Asset Management System Production Audit

Audit date: 2026-06-27  
Scope: Laravel application in `C:\Users\PLAYHOUSE\Herd\ogsg-asset-management`  
Runtime verified: Herd PHP 8.4 via `C:\Users\PLAYHOUSE\.config\herd\bin\php84.bat`

## Executive Summary

This application is not ready for state-wide production use yet.

The system has moved well beyond a prototype. It has MDA tenancy, scoped roles, email verification, OTP/trusted-device login, public QR asset lookup, asset lifecycle history, approval assignments, budgets, budget ledger rows, row locking around approvals, and tests proving many high-risk paths. That is good progress.

But for a government system handling public assets and public funds, the current implementation still has launch-blocking gaps:

- Critical finance and audit records can still be erased or anonymized by cascading or nulling foreign keys.
- Budget and approval flows exist, but the surrounding procurement, asset lifecycle, reporting, and reconciliation flows are not complete enough for audit-grade public-sector control.
- Several bulk reports load entire datasets into memory and will not scale to state-wide asset volumes.
- File upload validation is inconsistent and does not always validate true MIME type.
- Local effective config is `app.env=local` and `app.debug=true`; this environment cannot be treated as production-ready.
- Some UI workflows are state changes disguised as direct status actions rather than formal request/approval workflows.

The strongest areas are login hardening, budget deduction integrity, approval assignment checks, and ministry tenancy tests. The weakest areas are data retention, irreversible auditability, procurement traceability, physical audit/disposal depth, scalable reporting, and production operations.

## Critical Production Blockers

1. Audit and finance data retention is unsafe.
   - Location: `database/migrations/2026_06_26_020001_create_budgets_table.php:13`, `2026_06_26_020002_create_budget_transactions_table.php:13`, `2026_01_29_222935_create_asset_requests_table.php:18`, `2026_01_29_224119_create_request_approvals_table.php:18`, `2026_01_29_224211_create_procurement_orders_table.php:18`.
   - Problem: ministry, budget, request, approval, and procurement deletion can cascade or detach records that must be retained for public audit.
   - Required fix: use restrict-on-delete for audit/finance records, soft-delete domain rows, and preserve actor/snapshot fields instead of relying only on nullable user links.

2. Asset lifecycle workflows are too shallow for government control.
   - Location: `app/Services/AssetLifecycleService.php:16`, `app/Http/Controllers/Admin/AssetController.php:184`.
   - Problem: transfer, maintenance, disposal, condition reporting, and physical verification are direct status/history writes. There is no transfer request, sender/receiver acceptance, disposal approval chain, maintenance cost capture, evidence requirement, or closed physical audit workflow.
   - Required fix: create formal lifecycle request models with status transitions, approvals, evidence, immutable closure, and reversal rules.

3. Reporting does not scale.
   - Location: `app/Http/Controllers/Admin/ReportController.php:27`, `:44`, `:66`, `:80`.
   - Problem: reports call `get()` and PDF generation loads entire datasets. Millions of assets or requests will exhaust memory and create timeouts.
   - Required fix: paginate onscreen reports, queue exports, stream CSV/XLSX, apply hard filters/date ranges, and use summary tables/materialized reporting where needed.

4. Production environment posture is not ready.
   - Evidence: `php84.bat artisan config:show app.debug` returned `true`; `php84.bat artisan config:show app.env` returned `local`.
   - Problem: this effective runtime would expose unsafe behavior if deployed as-is.
   - Required fix: production `.env` must set `APP_ENV=production`, `APP_DEBUG=false`, real mail, real queue worker, monitored logs, secure `APP_URL`, backup storage, and deployment health checks.

5. Procurement and asset creation are not financially reconciled end to end.
   - Location: `app/Services/ProcurementService.php:139`, `app/Services/BudgetService.php:76`.
   - Problem: budget deduction occurs at final approval, but procurement quote total can differ from approved amount and delivered assets are auto-created from supplier quote items without a formal reconciliation workflow.
   - Required fix: add purchase order amount controls, variance approval, delivery acceptance, invoice evidence, and reconciliation against budget ledger and approval printout.

## Security Findings

### High: Audit evidence can be destroyed by cascades

- Location: budget, request, approval, procurement, and assignment migrations listed above.
- Vulnerability: destructive foreign key behavior on records that must be retained.
- Exploit scenario: a privileged admin deletes an MDA, budget, request, user, or asset request and related ledger/approval/procurement records disappear or lose actor identity.
- Impact: internal or external auditors cannot reconstruct use of public funds.
- Recommended fix: replace cascades with restrict/soft-delete, preserve immutable snapshots of MDA name/code, user name/email, role/title, approval level, and budget balances.
- Priority: P0.

### High: File upload validation is incomplete in several paths

- Location: `app/Http/Controllers/Admin/AssetController.php:370`, `app/Http/Requests/StoreAssetRequestFormRequest.php:30`, `app/Http/Requests/BulkMinistryUserInvitationImportRequest.php:26`.
- Vulnerability: `mimes` is used without consistent `mimetypes`; imported Excel/document/image attachments are not deeply scanned.
- Exploit scenario: malicious files are uploaded with allowed extensions or spoofed content.
- Impact: malware storage, unsafe public file exposure, or later compromise through downloaded attachments.
- Recommended fix: validate file extension and MIME type, store private by default, scan uploads, block executables/macros where possible, and serve downloads through authorization-controlled routes.
- Priority: P0/P1.

### High: Public asset page must be continuously constrained

- Location: `routes/web.php:33`, `app/Http/Controllers/PublicAssetController.php:13`.
- Current strength: uses `withoutGlobalScope(MinistryScope::class)`, not all scopes, and exposes only serial/location attributes.
- Residual risk: public UUID page still exposes official asset existence and basic identity. If future fields are added carelessly, financial or internal details may leak.
- Recommended fix: keep a dedicated public resource/view model, add tests asserting no cost, supplier, custodian, budget, internal notes, or lifecycle history appears.
- Priority: P1.

### Medium: Super Admin has a global Gate bypass

- Location: `app/Providers/AppServiceProvider.php:62`.
- Vulnerability: super-admin bypasses all permission checks.
- Exploit scenario: compromised super-admin account can perform every action without granular policy review.
- Impact: total system compromise.
- Recommended fix: keep CLI-only bootstrap, enforce MFA, monitor super-admin activity, require break-glass controls, and avoid assigning this role to operational users.
- Priority: P1.

### Medium: Production security headers are not configured in application middleware

- Location: `bootstrap/app.php`.
- Vulnerability: no explicit CSP, HSTS, frame, referrer, or permissions policy middleware is registered.
- Exploit scenario: browser-side attacks are easier if a dependency or page introduces XSS/clickjacking risk.
- Impact: account compromise or data leakage.
- Recommended fix: add secure headers at reverse proxy and/or middleware, with report-only CSP first.
- Priority: P2.

### Low: Dependency audit currently clean

- Evidence: `composer audit` returned `No security vulnerability advisories found.`
- Residual risk: npm audit was not run because dependency registry access is outside this audit pass.
- Recommended fix: add Composer and npm audit checks to CI.
- Priority: P2.

## Product and Domain Gaps

- Fixed assets are represented, but consumables/inventory stock are not modeled distinctly.
- Asset transfer exists as a direct action, not a request with sender approval, receiver acceptance, and formal custodian reassignment.
- Maintenance lacks vendor assignment, cost capture, budget impact, downtime, evidence upload, and completion workflow.
- Disposal lacks approval chain, valuation, disposal method, evidence, archive controls, and reversal policy.
- Physical audit is a direct verification event, not an audit campaign with scan sessions, variance reconciliation, closure, and immutable findings.
- Procurement traceability exists partially through asset requests and procurement orders, but purchase order, invoice, supplier quote variance, delivery acceptance, and budget reconciliation are incomplete.
- Budget closure and treasury return exist, but no scheduled year-end closure process, February extension governance, or treasury reconciliation report is visible.
- Reports are basic and do not yet cover expense-code utilization, returned-to-treasury balances, project codes, variance reports, audit closure, or procurement-to-asset traceability.
- ISO 55000 alignment is partial: identification, ownership, and status exist, but lifecycle planning, risk, performance, verification, and continuous improvement controls are weak.

## Role, Permission, and Tenancy Findings

The role model is substantially better than a basic RBAC setup.

Strengths:

- Canonical roles and scopes are seeded in `database/seeders/RolesAndPermissionsSeeder.php:15`.
- stale role records `admin`, `approver`, `approver-1`, and `approver-2` are removed by the role seeder; approval authority is assignment-based, not role-based.
- ministry tenancy is applied through `EstablishMinistryContext` and `BelongsToMinistry`.
- Super Admin account creation is CLI/bootstrap-only in the current architecture.
- Approver is implemented as functional assignment through `ApprovalAssignment`, not only as a base role.
- Tests prove key boundaries: ministry scoping, super-admin gate bypass, role permissions, and assigned approver behavior.

Weaknesses:

- Central Admin has broad operational power over assets, budgets, approvals, reports, and users. That may be acceptable, but it needs documented separation from developer/system Super Admin.
- Ministry Admin and Ministry Asset Officer can delete assets based on seeded permissions. For public-sector records, deletion should generally be replaced with archive/retire workflows.
- Viewer is read-only in seeded permissions, but export permissions need continued testing because report/export surfaces are separate from view routes.
- Super Admin bypass is technically powerful enough to override every policy. It must be operationally restricted and monitored.

Verdict: role/tenancy foundation passes the first layer, but deletion/export/central-admin governance is not production-grade yet.

## Workflow Validation

| Workflow | Result | Notes |
| --- | --- | --- |
| Asset registration | Partial pass | Unique asset tags, financial lock, QR support, history exist. Missing approval-before-activation and stronger procurement linkage. |
| Asset update | Partial pass | Financial fields are locked after registration. Non-financial edits and attribute replacement need stronger audit/revision controls. |
| Asset transfer | Fail for production | Direct transfer action, no formal sender/receiver approval workflow. |
| Maintenance | Fail for production | Direct maintenance status only; no cost/vendor/evidence/completion workflow. |
| Disposal | Fail for production | Direct disposal status with notes; no approval chain or disposal evidence model. |
| Physical audit | Fail for production | Verification event exists, but no audit campaign, scan reconciliation, or closure workflow. |
| Budget setup | Partial pass | Active budget uniqueness, organisation code, ledger allocation, status lifecycle exist. Delete/retention and governance gaps remain. |
| Purchase request | Partial pass | Captures organisation code, expense code, amount, year, MDA, user; needs stronger draft/submit lifecycle and procurement reconciliation. |
| Approval | Pass with caveats | Assigned approver, MDA scope, level order, direct POST blocking, and double-decision prevention are implemented and tested. |
| Budget deduction | Pass with caveats | Uses row locks, transactions, ledger, insufficient-budget checks, and unique deduction key. Needs DB-engine-specific concurrency testing in production DB. |
| Ledger | Partial pass | Allocation/deduction/reversal/adjustment/return rows exist. Retention constraints are unsafe. |
| Treasury return | Partial pass | Status and ledger action exist. Needs formal year-end treasury reconciliation report and approval/audit process. |

## Budget and Purchase Approval Findings

Strengths:

- Budget table captures organisation code, year, allocated/used/balance, status, closure, extension, returned amount, and active budget key.
- `BudgetService::deductForApproval` locks the request and budget rows before deduction.
- Deduction only happens on final approval level in `AssetRequestService.php:196`.
- Insufficient balance, missing active budget, expired extension, zero amount, and duplicate deduction are guarded.
- `budget_transactions` stores type, amount, balance before/after, description, creator, and request linkage.
- Unique `active_budget_key` and `deduction_request_key` reduce duplicate active budgets and duplicate deductions.
- Focused tests passed for duplicate budget blocking, assigned approvers, double approval prevention, insufficient budget, rejection without deduction, and approval summary.

Risks:

- Ledger rows cascade with budget deletion. That defeats the point of a ledger.
- Budget transaction link to `asset_request_id` uses null-on-delete, which can orphan financial evidence from the originating request.
- Budget edits after deductions are restricted at service level, but the database still permits mutable balance columns if an unsafe code path is introduced.
- Approval summary is available at `routes/web.php:192` under authenticated routes and policy checks; good, but the printout should be immutable/snapshotted at approval time.
- Procurement quote totals can diverge from the approved request amount without visible variance approval.
- There is no explicit scheduled budget close, February extension authority model, or treasury return approval chain.

## Engineering Review

Strengths:

- Domain services exist for budget, asset request, asset lifecycle, activity logs, procurement, onboarding, invitations, OTP, and trusted devices.
- Many controllers delegate important work into services.
- FormRequests are used in several high-risk paths.
- Tests cover critical budget, approval, auth, tenancy, QR, and lifecycle behavior.
- CSV/Excel export cells are escaped against formula injection in asset exports.

Weaknesses:

- Some controllers remain too broad. `Admin\AssetController` handles CRUD, lifecycle actions, import/export, QR labels, file upload, and route-prefix switching.
- Admin controllers are reused under ministry routes (`routes/web.php:150`), which works but increases risk that future admin-only behavior leaks into ministry context.
- There is a repository layer, but it appears thin and inconsistent; several services/controllers query models directly anyway.
- Some old domain language and newer MDA model concepts coexist, increasing maintenance risk.
- Activity logging is mixed between synchronous writes and queued jobs. The selected synchronous actions are sensible, but queue failure monitoring is not shown.

## Database Review

Strengths:

- Money fields use `decimal(15, 2)`.
- Asset tags, request codes, UUIDs, active budget keys, and deduction request keys are unique.
- Indexes exist for common status, ministry, approver, and category queries.
- Asset histories restrict deletion of assets.

Weaknesses:

- Finance/audit retention is the biggest schema defect: cascades and nulls on public-fund records are not acceptable.
- Several actor fields use `nullOnDelete`, losing accountability if users are removed.
- `activity_logs` has a polymorphic subject but no immutable snapshot of subject data; if subject records change/delete, audit meaning degrades.
- Asset attributes use EAV, which is flexible but weak for validation, indexing, and reporting at scale.
- Request approval level started as enum and was later migrated to integer. This is fine if fully migrated, but it needs production migration verification.
- No database-level invariant enforces `allocated_amount = used_amount + balance_amount + returned_to_treasury_amount` depending on status.

## API and Route Review

The app is primarily web/Blade, not a versioned API.

Strengths:

- Authenticated routes are behind `auth`, `verified`, and ministry context middleware.
- Admin surfaces are split between super-admin-only and super/central admin groups.
- Approval routes use `assigned.approver` middleware for index/show/approve/reject.
- CSRF is present in reviewed Blade forms.
- Route list verified 169 application routes.

Weaknesses:

- No API versioning or JSON error strategy is visible, if external APIs are planned.
- No idempotency keys for financial actions. The budget deduction unique key helps, but procurement/order actions are not similarly protected.
- Ministry routes reuse admin asset controller actions; this requires very strong tests for every route.
- Some state transitions are direct POSTs without formal workflow resources.
- Rate limiter definitions exist, but only selected auth routes visibly apply throttle middleware.

## Frontend/UX Review

Strengths:

- Role-aware navigation and `@can` checks are present in key views.
- Approval summary view exists and tests assert it renders after approval.
- Budget pages exist for admin and ministry users.
- Ministry reports hide financial cards behind `view asset financials`.
- Empty state and reusable UI components exist.

Weaknesses:

- Several critical lifecycle actions appear as immediate form actions; users need confirmation, evidence upload, and workflow status screens.
- The UI does not yet make budget balance and approval consequences prominent enough before submission/approval.
- Print layouts exist for procurement and approval, but approval output should be immutable and versioned.
- Large tables/reports need server-side filtering and export queues.
- Accessibility and mobile behavior were not browser-verified in this audit pass.

## Failure Handling Review

Strengths:

- Budget approval failure paths return validation errors for insufficient budget, missing budget, expired extension, duplicate approval, and invalid status.
- Login blocks inactive users and rate-limits failed attempts.
- Password reset revokes sessions, trusted devices, and OTP challenges.
- Budget deduction is inside a transaction and uses row locks.

Weaknesses:

- File upload failure and malicious upload handling are not strong enough.
- PDF/export generation failure handling is not visible.
- Queue failure monitoring for async activity logs and notifications is not shown.
- No production DB concurrency test was run against MySQL/PostgreSQL; SQLite-style tests are not enough for final approval concurrency assurance.
- Procurement delivery can partially create many assets if a late exception is introduced outside the transaction boundary in the future; current method is transactional, but bulk delivery needs stress tests.

## Performance Assessment

Major bottlenecks:

- Admin reports use unbounded `get()` and PDF generation.
- Dashboard service runs many aggregate queries live.
- Asset EAV attributes will become expensive for serial/location/supplier searches.
- Procurement delivery creates one asset per item quantity in a loop.
- Asset tag generation counts existing tags and loops until free; concurrent imports/deliveries can race and degrade with large datasets.
- Bulk imports are synchronous and will time out for large uploads.

Recommended scaling actions:

- Queue all large exports and PDFs.
- Add required date/MDA/status filters before export.
- Add summary tables or cached aggregates for dashboards.
- Add DB-level retry/unique handling for asset tag generation.
- Move high-volume import processing to queued jobs with row-level result reporting.
- Add production indexes based on real query plans.

## QA Test Matrix

| Test case | Role | Expected result | Severity |
| --- | --- | --- | --- |
| Super Admin accesses all admin surfaces | Super Admin | Allowed and logged | Critical |
| Central Admin cannot access account recovery/super-admin-only user management | Central Admin | Forbidden | Critical |
| Ministry Admin views another MDA asset by UUID route | Ministry Admin | 404/403 | Critical |
| Ministry Viewer submits create asset POST | Viewer | Forbidden | Critical |
| Ministry Viewer submits request POST | Viewer | Forbidden | High |
| Ministry Admin changes posted `ministry_id` to another MDA | Ministry Admin | Validation error/no cross-MDA write | Critical |
| Assigned approver approves own MDA request at correct level | Assigned approver | Approved, ledger only on final level | Critical |
| Assigned approver approves another MDA request | Assigned approver | Forbidden | Critical |
| Approver tries level 2 before level 1 | Assigned approver | Blocked | Critical |
| Double approval direct POST | Assigned approver | Blocked, no second deduction | Critical |
| Approved request amount is edited | Admin or requester | Blocked/requires reversal workflow | Critical |
| Insufficient budget approval | Assigned approver | Validation error, no ledger row | Critical |
| No active budget approval | Assigned approver | Validation error, no approval completion | Critical |
| Two concurrent final approvals against same budget | Two approvers/sessions | One safe deduction, no negative balance | Critical |
| Budget close | Central Admin | Closed, ledger/audit recorded | High |
| Budget extension beyond February | Central Admin | Blocked | High |
| Return leftover to treasury | Central Admin | Balance zero, returned amount and ledger recorded | Critical |
| Delete MDA with budgets/requests | Admin | Restricted, not cascaded | Critical |
| Asset transfer requires approval workflow | Ministry/Admin | Must not direct-transfer in production design | High |
| Disposal requires evidence and approval | Ministry/Admin | Must not direct-dispose without workflow | High |
| Physical audit closure prevents edits | Auditor/Admin | Closed findings immutable | High |
| Upload spoofed executable renamed as PDF/XLSX | Any allowed uploader | Rejected | Critical |
| Export formula injection in asset name/tag | Export user | Escaped in CSV/XLSX | High |
| Public asset UUID page for soft-deleted asset | Public | 404 | High |
| Public asset page displays financial fields | Public | Must not display | Critical |
| Password reset after active sessions | User | All old sessions/devices revoked | High |
| Inactive user login | Inactive user | Login denied | Critical |
| Large asset report export | Admin | Queued/streamed without timeout | High |
| PDF generation failure | Admin | Friendly error, no stack trace | Medium |

## Production Readiness Score

Score: 5.5 / 10

Reasoning:

- + Strong improvement in budget approval integrity, tenancy, and auth hardening.
- + Good focused automated coverage for several critical workflows.
- + Meaningful domain services and approval assignment design.
- - Audit retention model is not acceptable for government finance records.
- - Asset lifecycle and procurement workflows are not mature enough.
- - Reporting/export architecture will not survive state-wide data volumes.
- - Current effective environment is local/debug.
- - Production operations, backup, monitoring, queue supervision, and security headers are not proven.

## Final Verdict

Not Ready

This application should not be deployed state-wide for OGSG yet. It can continue as a strong UAT/internal pilot candidate if access is limited, data is non-final, and operational stakeholders understand the gaps. Before production, fix retention constraints, formalize lifecycle/procurement workflows, harden uploads, queue/scalably redesign reports, verify production config, and run concurrency tests against the real production database engine.

## Verification Performed

- `C:\Users\PLAYHOUSE\.config\herd\bin\php84.bat artisan route:list --except-vendor`
  - Result: 169 application routes listed.
- `C:\Users\PLAYHOUSE\.config\herd\bin\php84.bat artisan test --compact tests/Feature/BudgetApprovalWorkflowTest.php tests/Feature/DynamicApprovalTest.php tests/Feature/AuthorizationPolicyTest.php tests/Feature/MinistryTenancyTest.php tests/Feature/AssetQrCodeTest.php tests/Feature/AssetLifecycleAuditTest.php tests/Feature/Phase1SecurityFixTest.php tests/Feature/ProcurementLifecycleTest.php`
  - Result: 60 tests passed, 153 assertions.
- `C:\Users\PLAYHOUSE\.config\herd\bin\composer.bat audit`
  - Result: no security vulnerability advisories found.
- `C:\Users\PLAYHOUSE\.config\herd\bin\php84.bat artisan config:show app.debug`
  - Result: `true`.
- `C:\Users\PLAYHOUSE\.config\herd\bin\php84.bat artisan config:show app.env`
  - Result: `local`.
