← All repos

alist-v2

Legacy Laravel 9 + Vue 3 admin panel rewrite. Last meaningful work Dec 2023 — disabled the v1↔v2 database sync. Treat as abandoned reference, not production.

Primary stackLaravel 9 (PHP 8) backend + Vue 3 SPA admin Last commit2023-03-31 (master) / 2023-12-14 (latest branch) Repo size47 MB Files855 Branchmaster Health critical — 3 × P0

Executive summary

Tech stack

PHP 8.0+ Laravel 9.2 Vue 3.2 Vue Router 4 Vuex 4 Laravel Mix 6 (webpack) Sanctum spatie/laravel-permission l5-swagger laravel-echo + socket.io MySQL × 2
CategoryTechnologyVersionNotes
LanguagePHP^8.0.2PHP 8.0 reached EOL Nov 2023.
FrameworkLaravel^9.2Laravel 9 reached EOL Feb 2024.
Authlaravel/sanctum^2.14.1Token + cookie auth for the SPA.
RBACspatie/laravel-permission^5.5Roles/permissions; chat-agent and admin roles wired through.
API docsdarkaonline/l5-swagger^8.3OpenAPI annotations on controllers (e.g. LoginController). Output in storage/api-docs/.
Excelmaatwebsite/excel^3.1Used for offer-code exports.
Imagesintervention/image^2.7Banner upload + crop.
Mailjdavidbakr/mail-tracker^6.0Tracks opens/clicks for sent admin mail.
Geostevebauman/location^6.4IP geolocation for login activity.
IDshashids/hashids^4.1Public-facing obfuscation of integer IDs.
Cachepredis/predis^2.0Redis client; default driver is file in env.
FrontendVue + Vue Router + Vuex3.2 / 4.0 / 4.0Composition + Options API mix.
Buildlaravel-mix^6.0Webpack 5 wrapper. Not Vite.
Realtimelaravel-echo + socket.io-client1.13 / 2.3socket.io v2 is several majors stale; Pusher path is commented out.
Chartsapexcharts + vue3-apexcharts3.35 / 1.4Dashboard charts.
Formsvee-validate + yup4.5 / 0.32SPA form validation.
UI bitsvue-final-modal, vue-toastification, v-calendar, vue-advanced-cropper, vue3-editor, @vueform/multiselect, @sipec/vue3-tags-inputMany one-off Vue 3 widget libs, several still in alpha/beta at the time.
DatabaseMySQLTWO connections: mysql (this app) + mysql2 (legacy v1 via DB_*_SECOND).
Testingphpunit^9.5.10Only stub ExampleTest.php files exist.

Architecture

Standard Laravel 9 layout with a single SPA frontend. The browser hits /admin/* routes, Vue Router renders components, and every data call goes through /api/backend/*. The API is Sanctum-authenticated and gated by spatie permissions. A second MySQL connection (mysql2) points at the legacy v1 database; many controllers read/write it directly, and a fleet of Sync* jobs propagate v2 writes back to v1 so the legacy system stays consistent.

alist-v2/ ├── app/ │ ├── Console/Commands/ Sync* commands + PublishOffer + UpdateMerchant │ ├── Helper/ functions.php + dashboardBroadcast.php + offerBroadcast.php (autoloaded) │ ├── Http/Controllers/Api/Backend/ │ │ ├── Auth/ Sanctum login/logout │ │ ├── ChatAgent/ moderation-scoped endpoints │ │ └── Sync*Controller.php v1↔v2 sync handlers (mysql2 connection) │ ├── Jobs/Sync*.php queued cross-DB sync workers │ ├── Models/ ~30 Eloquent models (Offer, Merchant, User, Influencer, …) │ └── Events / Listeners broadcast events (DashboardUpdate, OfferUpdate) ├── config/database.php defines mysql + mysql2 connections ├── database/migrations/ 82 migrations, Apr 2022 → Feb 2023 ├── resources/ │ ├── js/ Vue 3 SPA │ │ ├── components/admin/ one folder per feature (Offers, Merchant, …) │ │ ├── router/index.js ~29 admin routes │ │ └── store/modules/ auth, page, offerWidget, offerTab │ └── views/backend/login.blade.php vestigial "Websocket - Login" blade ├── routes/ │ ├── api.php 189 lines — almost all behaviour │ └── web.php 28 lines — SPA catch-all ├── public/ 26 MB — committed compiled JS/CSS/images └── storage/api-docs/ generated Swagger JSON

Security assessment — methodology

This is a white-box source-level review of alist-v2 grounded in the anthropic-cybersecurity-skills playbooks. Each scanned dimension (secrets/crypto, supply chain, injection, access control, authentication) was run as a focused source scan, then every candidate finding was put through adversarial verification: each cited line was opened and confirmed verbatim, false positives were rejected, and severities were re-scored against CWE and CVSS 3.1. Findings below are confirmed unless a confidence tag says otherwise.

OWASP Top 10 2021 OWASP WSTG OWASP API Top 10 2023 OWASP MASVS (mobile only) CWE CVSS 3.1 NIST CSF 2.0
Skill playbooks applied to this repo (dimensions: secrets/crypto, supply-chain, injection, access-control, authentication):
implementing-secret-scanning-with-gitleaks detecting-aws-credential-exposure-with-trufflehog performing-cryptographic-audit-of-application testing-for-sensitive-data-exposure exploiting-sql-injection-vulnerabilities performing-second-order-sql-injection exploiting-mass-assignment-in-rest-apis exploiting-template-injection-vulnerabilities testing-for-broken-access-control testing-api-for-broken-object-level-authorization exploiting-idor-vulnerabilities exploiting-broken-function-level-authorization bypassing-authentication-with-forced-browsing testing-api-authentication-weaknesses testing-jwt-token-security testing-cors-misconfiguration testing-for-xss-vulnerabilities performing-sca-dependency-scanning-with-snyk analyzing-sbom-for-supply-chain-vulnerabilities detecting-supply-chain-attacks-in-ci-cd prioritizing-vulnerabilities-with-cvss-scoring implementing-epss-score-for-vulnerability-prioritization

Risk scorecard

Overall risk: critical

alist-v2 is an abandoned Laravel 9 / Vue 3 admin-panel rewrite whose security posture is critical even as a non-production reference. Three P0s combine into one-step total compromise: a committed hardcoded superadmin backdoor (subin@alist.ae with a cleartext seeded password), an entire family of voucher-export and cross-database sync/mutation routes registered OUTSIDE the auth:sanctum group (fully unauthenticated read and destructive write into both the v2 and legacy v1 databases), and a complete absence of function-level authorization (no policies, no role/permission middleware anywhere, all FormRequest authorize() return true) so any authenticated token has full admin capability. Reinforcing these, an authenticated SQL injection in OfferController::merchantWidget allows database exfiltration including Sanctum tokens and password hashes, mass-assignment lets any user self-escalate to superadmin, and IDOR pervades read/destructive endpoints. The supply chain sits on EOL PHP 8.0 / Laravel 9.23.0 with build-time RCE (CVE-2023-45133 in @babel/traverse) and a string of credential-leak/DoS CVEs (axios, follow-redirects, ws), none of which will ever be patched on this branch. Per the May 2026 A-List audit's rotate-before-refactor priority, the seeded superadmin credential, the shared 'Alist@2022$!' sync password, and the live Google Maps key must be rotated immediately; the only safe disposition is to keep this repo off untrusted networks and treat it as archived reference, not a deployable target.

Overall risk Critical
P03 P19 P27 P39

OWASP coverage

CategoryStatusNotes
A01:2021 Broken Access ControlvulnAV-02 unauthenticated sync/export routes (P0), AV-03 no function-level authz (P0), AV-04 mass-assignment escalation, AV-05 IDOR/BOLA, AV-20 unscoped PII listings.
A02:2021 Cryptographic FailuresvulnAV-27 mt_rand verification-code helper (dead code); tokens never expire (AV-10) and cleartext passwords emailed (AV-21) also touch this category.
A03:2021 InjectionvulnAV-06 confirmed SQLi in merchantWidget (P1); AV-23 unvalidated ORDER BY across ~17 endpoints; AV-24 HTML/content injection in review email.
A04:2021 Insecure DesignvulnAV-19 unsalted Hashids tokens; AV-21 cleartext-password email flow design.
A05:2021 Security MisconfigurationvulnAV-12 Maps key on every page, AV-13 wildcard CORS, AV-16 public Swagger UI with empty middleware, AV-26 sample key in vendored asset.
A06:2021 Vulnerable and Outdated ComponentsvulnAV-08 EOL PHP 8.0 / Laravel 9.23.0; AV-14 axios, AV-15 follow-redirects, AV-17 socket.io v2/ws, AV-25 nanoid — all stale with CVEs and no fix path.
A07:2021 Identification and Authentication FailuresvulnAV-01 hardcoded superadmin backdoor (P0), AV-07 shared sync password (P1), AV-11/AV-22 weak/bypassable brute-force controls.
A08:2021 Software and Data Integrity FailuresvulnAV-09 build-time RCE via @babel/traverse (CVE-2023-45133); AV-18 minimum-stability:dev + alpha/beta deps + stray 'add'; AV-28 no CI/SCA gate.
A09:2021 Security Logging and Monitoring FailurespartialAn ActivityLog dispatch exists for admin actions/logins, but verbose DB error messages are returned to clients (OfferController.php:867) and there is no alerting/monitoring or tamper-resistant audit trail.
A10:2021 Server-Side Request ForgerypartialNo first-party SSRF sink confirmed in app code; transitive SSRF exposure via axios 0.x (CVE-2025-27152, AV-14) and stevebauman/location IP geolocation lookups warrant review but were not confirmed exploitable.
API1:2023 Broken Object Level AuthorizationvulnAV-05 — resources fetched/mutated by raw path id with no ownership scoping across read and destructive endpoints.
API2:2023 Broken AuthenticationvulnAV-02 unauthenticated state-changing sync routes, AV-10 non-expiring localStorage tokens, AV-11 unthrottled web login + session fixation, AV-22 client-side-only captcha.

Detailed findings

3
P0 critical
9
P1 high
7
P2 medium
9
P3 low / info
P0 — critical P1 — high P2 — medium P3 — low / info
AV-01

Hardcoded superadmin backdoor credentials committed in database seeder

P0 A07:2021 Identification and Authentication Failures CWE-798 CVSS 9.8 EPSS n/a (hardcoded-credential, not a CVE) confirmed
Description

AdminSeeder.php hardcodes a real, working superadmin credential pair (email subin@alist.ae plus a literal cleartext password fed into Hash::make) and assigns the superadmin role. The seed runs during provisioning, so every environment built from this repo gets a known superadmin login; the password is in cleartext in git history and any clone. Per the May 2026 A-List audit (secrets-in-git endemic, rotate-before-refactor priority) this credential must be rotated wherever it was provisioned.

Evidence
database/seeders/AdminSeeder.php:18-23 User::updateOrCreate(['email'=>'subin@alist.ae','name'=>'Admin'],['password'=>Hash::make('@864pt4&2y46nR*&}%#pc$Q98yya+Gw7723a'),'username'=>'4pMa3h']) — verified verbatim database/seeders/AdminSeeder.php:25 $user->assignRole('superadmin') CLAUDE.md run instructions use 'php artisan migrate --seed', so this seeder provisions the backdoor in any environment built from the repo LoginController.php authenticates purely on email+password via Auth::attempt, making the seeded pair a usable live login (not test-only data)
Attack scenario
Anyone with repo/history access reads database/seeders/AdminSeeder.php, learns subin@alist.ae and the cleartext password '@864pt4&2y46nR*&}%#pc$Q98yya+Gw7723a', and submits them to POST /api/backend/login (or the web /websocket/login). The account holds the 'superadmin' role, so the attacker immediately controls the entire admin panel and, via the sync layer, the legacy v1 database.
Impact

Full administrative takeover of the v2 admin panel as superadmin (all spatie permissions), and via the cross-DB sync layer, write access into the legacy v1 (mysql2) database. Because the seeded credential is reused across any deployed/dev/staging instance, it is a single shared backdoor.

Remediation
Remove the literal password from the seeder; generate a random password at seed time (printed out-of-band) or require env('SEED_ADMIN_PASSWORD'). Rotate the subin@alist.ae credential everywhere it was provisioned (rotate-before-refactor). Scrub git history with git-filter-repo and force-push after team coordination. Add a secret-scanning rule + pre-commit hook.
Verifier note

Confirmed verbatim at AdminSeeder.php:18-25. Highest-severity finding alongside AV-02; trivially exploitable given the known login endpoint.

AV-02

Voucher-code export and cross-DB sync/mutation routes defined outside the auth:sanctum group (unauthenticated)

P0 A01:2021 Broken Access Control CWE-862 CVSS 9.4 EPSS n/a confirmed
Description

In Laravel, only routes inside a Route::middleware([...])->group() closure inherit that middleware. routes/api.php registers the two voucher-export endpoints and the entire sync-* family directly under the 'backend' prefix BEFORE the auth:sanctum group at line 87/90, so they are reachable by anyone. The exports stream Excel files of all voucher/discount codes (sequential, enumerable offer ids); the sync-* handlers perform privileged state changes and cross-database writes/deletes into the legacy v1 DB. Merges the access_control 'voucher export + sync' and auth_jwt 'unauthenticated sync routes' scanner findings (same root cause).

Evidence
routes/api.php:53 Route::prefix('backend')->group(...) opens the group; lines 54-82 register ~30 routes with NO middleware routes/api.php:54-55 export-new-codes / export-all-codes (GET, no auth) routes/api.php:56-81 the entire sync-* family (POST, no auth): sync-offer, sync-offer-delete, sync-user, sync-merchant, sync-user-review-approve/reject, sync-country/state/city/category[/delete], sync-dedicated-offer-user reset/approve/reject/reset-all routes/api.php:87/90 the auth:sanctum group only STARTS here — everything above is public app/Http/Kernel.php:40-44 'api' group is only ['throttle:500,1', SubstituteBindings]; EnsureFrontendRequestsAreStateful is commented out — no auth at group level app/Http/Controllers/Api/Backend/OfferCodeController.php:262 exportAllOfferCodes($offerId, Request) does Offer::findOrFail($offerId) then downloads ExistingOfferCodeExport — no auth/ownership check
Attack scenario
An unauthenticated attacker calls GET /api/backend/offers/{id}/export-all-codes and receives an XLSX of every redemption voucher code for that offer, then loops the sequential integer id to exfiltrate the entire voucher inventory. They additionally POST /api/backend/sync-user/{id}, sync-merchant/{id}, sync-user-review-approve/{reviewId}, sync-offer-delete/{groupid}/{offerid}, sync-dedicated-offer-user/{offerId}/reset-all, etc. — all without any credential — forcing writes/deletes into both the v2 (mysql) and legacy v1 (mysql2) databases.
Impact

Mass disclosure of all promotional voucher/discount codes (direct financial loss via code theft) and unauthenticated write/delete access to review-moderation, influencer, merchant, and geo data spanning two production databases — a complete authentication bypass for a large, destructive subset of the admin API. Even if the Dec 2023 ALISTV2-158 branch disabled v1<->v2 sync, on the master tip these routes are live and unauthenticated.

Remediation
Move routes/api.php lines 54-82 inside the existing auth:sanctum group and add appropriate role:/permission: middleware per action. Defense in depth: wrap the whole 'backend' prefix group in auth:sanctum so a future top-of-file route cannot accidentally become public. Add a CI test asserting every /api/backend route except login requires authentication. If the sync handlers are dormant, delete the routes.
Verifier note

Confirmed: line 53 opens the unprotected prefix block, auth:sanctum begins at 87/90, exportAllOfferCodes has no auth/ownership check. Raised CVSS to 9.4 vs scanner's 9.1 because both confidentiality (voucher exfil) and integrity (cross-DB writes/deletes) are fully unauthenticated.

AV-03

No function-level authorization on any backend endpoint — vertical privilege escalation

P0 A01:2021 Broken Access Control CWE-862 CVSS 8.8 EPSS n/a confirmed
Description

The app ships spatie/laravel-permission and a permission catalog but enforces it nowhere: no route has role:/permission: middleware, there are zero policies, and every FormRequest authorize() returns true. The server treats authentication as authorization — any valid Sanctum token grants every admin function (user CRUD, role/permission assignment, password resets, offer publish/delete, merchant data, review moderation, sync). Authorization exists only as client-side menu hiding in Vue.

Evidence
routes/api.php:90-188 the authenticated group applies ONLY ['auth:sanctum']; grep for role:/permission:/can: in routes/ returns nothing (verified, exit 1) app/Providers/AuthServiceProvider.php:15-17 $policies = [ // commented ]; no app/Policies directory exists (verified absent) app/Http/Kernel.php:73-75 spatie 'role'/'permission'/'role_or_permission' middleware ARE registered but never attached to a route Every FormRequest::authorize() returns true (verified AdminUser.php:14-17 and 27 other FormRequests) Only AuthController.php and Auth/LoginController.php reference Auth:: across all controllers (verified grep) — no controller checks caller role/permission before acting LoginController.php:68 returns the user's permissions to the SPA, so authorization is implemented purely client-side
Attack scenario
A low-privilege admin (e.g. a chat-agent moderator) logs in, obtains a Sanctum token, and — despite the SPA hiding the User Management menu — calls POST /api/backend/admin-users to create accounts, POST /api/backend/admin-users/{id}/reset-password to take over any account, POST /api/backend/roles/{id} to set permissions, and POST /api/backend/admin-users/{id}/delete to remove other admins. None of these is authorized server-side.
Impact

Complete vertical privilege escalation. Any authenticated account, including the least-privileged role, can perform every administrative action including creating/deleting admins and resetting other users' passwords — full administrative takeover of the platform and both backing databases.

Remediation
Attach permission:/role: middleware to each route (or group by capability), and/or implement Laravel Policies/Gates invoked via $this->authorize() in each action. Map the existing privilege-section/task permissions to concrete route guards. Re-check permissions server-side on every request; never rely on the SPA hiding menus.
Verifier note

Confirmed: no authz middleware anywhere in routes/, no policies, all authorize() true. P0 because it converts AV-01/AV-04/AV-05 into one-step full takeover from any logged-in account.

AV-04

Mass-assignment of role ids enables self-escalation to superadmin

P1 A01:2021 Broken Access Control CWE-269 CVSS 8.8 EPSS n/a confirmed
Description

The admin-user create/update endpoints accept a client-supplied roles array and pass it straight to spatie assignRole; validation only checks each id exists. An attacker can assign superadmin or the customer-tier merchant/influencer roles, crossing trust boundaries. The listing endpoints hide superadmin from the UI but the assignment path has no equivalent allowlist.

Evidence
app/Http/Controllers/Api/Backend/AdminUserController.php:124 if (isset($input['roles'])) $user->assignRole($input['roles']); (store) app/Http/Controllers/Api/Backend/AdminUserController.php:203 $user->assignRole([$input['roles']]); (update) app/Http/Requests/Backend/AdminUser.php:42-43 'roles'=>['nullable','array'], 'roles.*'=>['required','exists:roles,id'] — no exclusion of privileged role ids (verified) database/seeders/RoleSeeder seeds superadmin first (id 1), then merchant/influencer AdminUserController.php:52-58 index() only filters the LISTING with whereNotIn('name',['superadmin','merchant','influencer']) — it does not restrict assignment, giving a false sense of protection
Attack scenario
An authenticated attacker creates or updates an admin user via POST /api/backend/admin-users (or POST /api/backend/admin-users/{id}) with body roles:[1]. The only validation is exists:roles,id, so the superadmin role (first seeded, id 1) is accepted and assignRole grants it, elevating the attacker to superadmin.
Impact

Privilege escalation to superadmin and assignment of arbitrary roles, granting full control of the admin platform; also blurs the admin/customer role boundary. Combined with AV-03 (no function-level authz) any authenticated user can do this.

Remediation
Validate submitted role ids against an explicit allowlist of assignable roles (exclude superadmin and any role above the caller). Drive role assignment through a policy that forbids assigning a role higher than the caller's own.
Verifier note

Confirmed: roles.* only validates exists:roles,id; assignRole called directly at :124 and :203.

AV-05

IDOR / BOLA on object ids across read and destructive endpoints

P1 API1:2023 Broken Object Level Authorization CWE-639 CVSS 8.1 EPSS n/a confirmed
Description

Resources are fetched and mutated using the raw {id}/{offerId} path parameter with findOrFail/where and no check that the caller owns or is authorized for the object. Ids are sequential integers; hashids is a dependency but not applied here. Affects reads (merchant, admin-user, permissions) and destructive writes (dedicated-offer-user resets/deletes, review resets/approvals).

Evidence
app/Http/Controllers/Api/Backend/MerchantController.php:216 Merchant::findOrFail($id) — no ownership/role scoping (verified) app/Http/Controllers/Api/Backend/AdminUserController.php:256 User::with('shortcuts')->findOrFail($id) (show) and :301 User::findOrFail($id) (getPermissions) app/Http/Controllers/Api/Backend/DedicatedOfferUserController.php:62-66 DedicatedOfferUser::...->findOrFail($id); $user->delete(); (resetUser) and :90 DedicatedOfferUser::where('offer_id',$id)->whereIn('status',[0,2])->delete(); (reset) — destructive by path id only (verified) No controller scopes queries by Auth::id()/owner column (only AuthController/LoginController reference Auth)
Attack scenario
An authenticated low-privilege user enumerates sequential integer ids: GET /api/backend/merchant/{id} returns any merchant record; GET /api/backend/admin-users/{id} and /{id}/permissions return any admin's profile/roles; POST /api/backend/dedicated-offer-user/reset-user/{id} hard-deletes any dedicated-offer-user row; POST /api/backend/dedicated-offer-users/reset/{offerId} bulk-deletes participants of any offer; POST /api/backend/user-review/{id}/approve approves any review.
Impact

Horizontal access to any object: read disclosure of merchant and admin-user records, and tampering/deletion of dedicated-offer participation and review-moderation data. Because there is also no function-level authz (AV-03), even the lowest-privilege account can do this.

Remediation
Enforce object-level authorization in each action (Policies or scoped queries such as where('owner_id', Auth::id()) where ownership exists, or a role/permission check for shared admin objects). Do not rely on sequential ids for security.
Verifier note

Confirmed at MerchantController.php:216, AdminUserController.php:256/301, DedicatedOfferUserController.php:62-90. Distinct from AV-03: this is per-object (BOLA) vs per-function authz.

AV-06

First-order SQL injection in merchantWidget via unparameterized $request->date in whereRaw()

P1 A03:2021 Injection CWE-89 CVSS 8.1 EPSS n/a confirmed
Description

merchantWidget reads the date query parameter directly (OfferController.php:842-844) and string-interpolates it into a raw SQL fragment at line 851 with no binding or validation. Unlike the parameterized whereRaw calls elsewhere (ActivityLogController), this is unsafe. The value sits inside single quotes, so a quote breaks out of the literal; the toSql()+mergeBindings wrapping does not protect the baked-in text. MySQL backend.

Evidence
app/Http/Controllers/Api/Backend/OfferController.php:842-844 if ($request->date) { $date = $request->date; } — raw, no validation/cast (verified) app/Http/Controllers/Api/Backend/OfferController.php:851 ->whereRaw("DATE_FORMAT(offer_timeslots.date, '$format') = '$date' ") — $date interpolated directly, NO binding (verified) app/Http/Controllers/Api/Backend/OfferController.php:854 the subquery is wrapped via DB::raw('(' . $subQuery->toSql() . ') as S'); toSql() bakes the injected text into SQL before mergeBindings, so the injection survives app/Http/Controllers/Api/Backend/OfferController.php:867 catch returns 'error'.$e->getMessage() to the client routes/api.php:157 /merchant-widget inside the auth:sanctum group — authenticated but any admin/chat-agent token reaches it $format is constrained to safe constants (%Y / %Y-%m / %Y-%m-%d, lines 832-840) so date is the sole vector
Attack scenario
An authenticated user requests GET /api/backend/merchant-widget?date=2024-01-01' OR '1'='1 . The date breaks out of the single-quoted SQL literal in the whereRaw fragment, enabling boolean/time-based blind (SLEEP/BENCHMARK), error-based (EXTRACTVALUE/UPDATEXML), and UNION extraction against the MySQL v2 database. Verbose DB errors are returned to the client (line 867), aiding error-based exfiltration.
Impact

An authenticated admin-panel user (including lower-privileged chat-agent roles) can read/exfiltrate arbitrary data from the v2 MySQL DB — admin users, password hashes, Sanctum personal_access_tokens (enabling token theft/account takeover), RBAC tables, merchant/offer/PII data — via blind or UNION techniques, and potentially other schemas the DB user can read. With FILE privilege, INTO OUTFILE could enable webshell writes.

Remediation
Bind the value: ->whereRaw("DATE_FORMAT(offer_timeslots.date, ?) = ?", [$format, $date]) (match the safe pattern in ActivityLogController.php). Additionally validate date with a FormRequest date_format rule. Stop returning $e->getMessage() to clients. Audit for the same anti-pattern and confirm the DB user is least-privilege.
Verifier note

Confirmed verbatim at OfferController.php:851; PR:L because it requires any authenticated token, but AV-03 means the lowest role qualifies.

AV-07

Hardcoded shared password 'Alist@2022$!' assigned to every synced influencer and merchant account

P1 A07:2021 Identification and Authentication Failures CWE-798 CVSS 8.1 EPSS n/a confirmed
Description

The cross-database sync commands bcrypt-hash a single hardcoded plaintext password 'Alist@2022$!' and write it into the password column of every influencer and merchant account they create from legacy v1 tables. Although hashed at rest, the plaintext is known to anyone with source access. All synced accounts share this one credential, with no per-user randomization, no first-login reset, and a guessable pattern.

Evidence
app/Console/Commands/SyncUser.php:41 and :117 $password = bcrypt("Alist@2022$!"); used at :61 and :137 for Influencer create (verified) app/Console/Commands/SyncMerchant.php:37 $password = bcrypt("Alist@2022$!"); used at :44 (verified) app/Console/Commands/UpdateMerchant.php:37 $password = bcrypt("Alist@2022$!"); used at :43 (verified)
Attack scenario
An attacker reads the hardcoded password 'Alist@2022$!' from the repo (or guesses the CompanyName@Year pattern), takes any email known to have been synced from v1 signups/venues tables, and logs in to the corresponding influencer/merchant account with the single shared password, repeating for every synced account.
Impact

Mass account takeover of every influencer/merchant account provisioned via the sync layer: with the one known password and a victim email, an attacker authenticates as that account and acts on its behalf. Confidentiality and integrity of all bulk-provisioned accounts are compromised.

Remediation
Never hardcode credentials. Generate a unique cryptographically random password per account, or provision password-unset and require an email-based set-password flow. Force a reset for all accounts already provisioned with the shared value. Remove the literal, purge it from git history, add a secret-scanning rule.
Verifier note

Confirmed at all four cited command files.

AV-08

EOL runtime and framework: PHP 8.0 + Laravel 9.23.0 receive no security patches

P1 A06:2021 Vulnerable and Outdated Components CWE-1104 CVSS 7.5 EPSS n/a (framework EOL, not a single CVE) confirmed
Description

The app pins an EOL runtime (PHP ^8.0.2) and EOL framework (Laravel 9.23.0). Neither receives security backports. Running on an unsupported stack means any newly disclosed framework/runtime CVE will never be patched without a major migration. This is the broadest supply-chain exposure — every dependency sits on a foundation that no longer gets fixes.

Evidence
composer.json:8 "php": "^8.0.2" (PHP 8.0 EOL 26 Nov 2023) (verified) composer.json:15 "laravel/framework": "^9.2" -> composer.lock resolved v9.23.0 (Laravel 9 security EOL Feb 2024) (verified) CLAUDE.md note 6 confirms Laravel 9 EOL Feb 2024 / PHP 8.0 EOL Nov 2023 bundled Symfony components at 6.1.x are themselves behind 6.4 LTS
Attack scenario
An attacker finds a public Laravel 9.x or PHP 8.0 advisory (e.g. a query-builder SQLi, encryption, or bundled Symfony HTTP-kernel issue) disclosed after EOL. Because no vendor backport exists for this branch, the exploit works with no patch path on any still-running instance.
Impact

Any future framework- or runtime-level CVE (RCE, auth bypass, query-builder SQLi, deserialization) is permanently unpatched on this branch. Combined with the abandoned status the maintenance gap widens.

Remediation
Treat as non-deployable until migrated to supported PHP (8.2+) and Laravel LTS (10/11/12) per alist-portal. If it must run short-term, isolate behind authn/network controls and run composer audit + composer outdated -D each deploy. Do not expose to untrusted networks.
Verifier note

Confirmed. Lowered CVSS to 7.5 from scanner 8.1 and AC:H because exploitation is contingent on a future/public advisory, not a present concrete flaw; severity kept P1 given abandonment.

AV-09

Build-time arbitrary code execution via @babel/traverse 7.18.11 (CVE-2023-45133)

P1 A08:2021 Software and Data Integrity Failures CWE-94 CVSS 7.3 CVE-2023-45133 EPSS ~0.2% (low; requires attacker-controlled source under compilation) confirmed
Description

@babel/traverse <7.23.2 (here 7.18.11) is vulnerable to CVE-2023-45133: compiling specifically-crafted malicious code lets an attacker execute arbitrary code at compile time. The whole frontend toolchain compiles JS on build; if any source file or transitively-installed npm module under compilation is attacker-influenced, code runs on the build host. The package set already includes alpha/beta libs (AV-18), raising the odds of pulling crafted code.

Evidence
yarn.lock:892-893 @babel/traverse@^7.18.9/10/11 resolves version 7.18.11 (vulnerable; fixed in 7.23.2) (verified) package.json:19,27 @babel/core ^7.17.8 and babel-loader ^8.2.4 pull it transitively under laravel-mix 6 -> webpack 5 CVE-2023-45133: malicious code compiled by Babel can execute arbitrary code at compile time via prototype pollution of internal path/visitor state
Attack scenario
A contributor PR or a compromised/typosquatted npm dependency introduces specifically-crafted code into the build. On yarn dev/yarn prod, @babel/traverse 7.18.11 compiles it and the prototype-pollution flaw executes arbitrary code on the build host/CI with developer/CI privileges, enabling a poisoned bundle shipped to public/.
Impact

Arbitrary code execution on the build machine or future CI runner at compile time — a classic supply-chain pivot: compromise the build, poison the committed public/ bundle, and gain client-side persistence reaching every admin.

Remediation
Add a yarn resolutions override "@babel/traverse": "^7.23.2", reinstall, and rebuild. Long-term migrate the build to Vite as alist-portal did.
Verifier note

Version confirmed at yarn.lock:893. Lowered CVSS to 7.3/AC:H from scanner 9.4 because exploitation requires attacker-controlled code entering the compile step (not a remote runtime CVE).

AV-10

Non-expiring Sanctum tokens stored in localStorage with disabled client-side 401 handling

P1 API2:2023 Broken Authentication CWE-613 CVSS 7.1 EPSS n/a confirmed
Description

Sanctum is configured with expiration => null, so issued tokens (LoginController createToken) never expire. The SPA persists the bearer token in localStorage and attaches it on every request. Server-side revocation occurs only on explicit /logout (deletes currentAccessToken). The axios interceptor that would clear the session on 401/403 is commented out, so even a revoked token does not force re-auth client-side. A long-lived token in localStorage is directly stealable by XSS and valid indefinitely.

Evidence
config/sanctum.php:49 'expiration' => null — personal access tokens never expire (verified) resources/js/store/modules/auth.js:32-34 localStorage.setItem('user'/'token'/'ip', ...) (verified) resources/js/libs/request.js:14-16 attaches Authorization: Bearer ${token} on every request resources/js/libs/request.js:33-36 the 401/403 handler (auth/clear + redirect to /admin/login) is fully commented out (verified)
Attack scenario
An admin token is stolen (via any XSS in the abandoned-dependency SPA, a shared/forgotten device, browser cache, or logs). Because tokens never expire and the SPA's 401/403 auto-logout is commented out, the stolen bearer token grants indefinite admin API access with no automatic revocation.
Impact

A token leaked via XSS, a shared device, cache, or logs remains a permanent valid admin credential with no expiry and no automatic revocation, granting indefinite admin API access.

Remediation
Set a finite sanctum 'expiration' (e.g. 60-120 min) with refresh. Prefer Sanctum SPA cookie auth (re-enable EnsureFrontendRequestsAreStateful + httpOnly cookies) over localStorage bearer tokens. Re-enable the 401/403 auto-logout in request.js. Revoke all of a user's tokens on logout/password change.
Verifier note

Confirmed: expiration null, localStorage storage, dead 401 handler all verified verbatim.

AV-11

Unthrottled secondary web login endpoint with no session regeneration (brute-force bypass + session fixation)

P1 API2:2023 Broken Authentication CWE-307 CVSS 7.3 EPSS n/a confirmed
Description

Two login paths exist. The API path (POST /api/backend/login) is throttled (throttle:10,2). The web path (POST /websocket/login -> AuthController::login) runs the same Auth::attempt against the same users table with NO rate-limiting, an unthrottled brute-force oracle. Additionally the web controller never calls session()->regenerate() after a successful attempt (session fixation). The web session cookie is also not forced secure (config/session.php SESSION_SECURE_COOKIE unset in .env.example).

Evidence
routes/web.php:26-28 websocket/login GET+POST and websocket/logout — no throttle middleware (verified) app/Http/Controllers/AuthController.php:38-67 login(Request) runs Auth::attempt() then redirect, with NO $request->session()->regenerate() after success (verified) routes/api.php:84-86 throttle:10,2 exists ONLY on the API login path AuthController only filters out merchant/influencer roles post-attempt — the unthrottled attempt oracle still works against the same user table
Attack scenario
An attacker brute-forces/credential-stuffs admin passwords against POST /websocket/login, which has no throttle middleware, bypassing the throttle:10,2 that protects the API login. On a successful Auth::attempt the controller never regenerates the session, so a pre-set session id is retained across the privilege boundary (session fixation), letting an attacker who fixed the victim's session id hijack the authenticated session.
Impact

Attackers can brute-force/credential-stuff admin passwords without the API throttle and perform session fixation against the web guard to hijack an authenticated admin session. Compounded by the known seeded superadmin credential (AV-01).

Remediation
Add throttle middleware to /websocket/login (or remove the endpoint — CLAUDE.md flags it as dead 'Websocket - Login' code). Call $request->session()->regenerate() immediately after a successful Auth::attempt. Add per-account lockout (ThrottlesLogins/Fortify) rather than per-IP only. Set SESSION_SECURE_COOKIE=true.
Verifier note

Confirmed: no throttle on web login routes, no session regenerate in AuthController::login. Note the endpoint may be partly vestigial (redirects to laravel-websockets) but the Auth::attempt oracle and session-fixation behavior are real.

AV-12

Hardcoded Google Maps/Places API key served on every SPA page

P1 A05:2021 Security Misconfiguration CWE-798 CVSS 6.5 EPSS n/a confirmed
Description

A real Google Maps JavaScript API key with the Places library is hardcoded in the Blade layout backing the entire Vue admin SPA. Because routes/web.php returns the layout for '/' and the /admin/{any} wildcard, the key is in the HTML of every page and trivially harvested by any visitor or clone. Client-side keys are only safe with strict HTTP-referrer + API restrictions and quota caps; a key committed in a legacy project is unlikely to be properly restricted.

Evidence
resources/views/layouts/app.blade.php:48 <script src="https://maps.googleapis.com/maps/api/js?key=AIzaSyDKK512euBlFVw4EycJVjmRxo8GoRzXN_U&libraries=places"></script> (verified) routes/web.php:18-24 view('layouts/app') returned for '/' and the /admin/{any} wildcard, so the key renders on every page, retrievable unauthenticated (verified) Real Google key shape (AIza + 35 chars) loaded against maps.googleapis.com, distinct from the FullCalendar sample key (AV-19)
Attack scenario
An unauthenticated visitor opens https://<host>/ (or any /admin/* path), views source, and extracts the AIza... Maps/Places key embedded by the Blade layout; or clones the repo/history. They then drive Maps/Places/Geocoding APIs against A-List's Google Cloud quota and billing until throttled.
Impact

Unauthorized third-party use of A-List's Maps/Places quota leading to billing abuse and potential denial of the mapping feature when quota is exhausted. The credential is permanently in git history and must be rotated.

Remediation
Treat the key as compromised: rotate it in Google Cloud Console. Apply HTTP-referrer + API restrictions and per-key quota caps to the replacement. Inject the key at build time from an env var instead of hardcoding. Purge from git history. Add a Gitleaks rule for AIza[0-9A-Za-z_-]{35} plus a pre-commit hook.
Verifier note

Confirmed at app.blade.php:48 and the web.php routing. P1 kept; client-side key so impact is quota/billing (C:L/A:L) not full secret compromise.

AV-13

Wildcard CORS (allowed_origins '*', paths include '*') on a bearer-token API

P2 A05:2021 Security Misconfiguration CWE-942 CVSS 5.8 EPSS n/a confirmed
Description

CORS reflects any origin, method, and header on all paths including the catch-all '*'. supports_credentials is false, so this does not enable Sanctum-cookie theft by itself; but the admin SPA authenticates with a bearer token in the Authorization header, so any malicious site a logged-in admin visits can issue cross-origin requests and read JSON responses, including from the unauthenticated sync/export endpoints (AV-02). Merges the two CORS scanner findings (access_control P3 + auth_jwt P2).

Evidence
config/cors.php:18 'paths' => ['api/*', 'sanctum/csrf-cookie', '*'] — applies CORS to ALL paths (verified) config/cors.php:20-21 'allowed_methods'=>['*'], 'allowed_origins'=>['*'] (verified) config/cors.php:26 'allowed_headers'=>['*'] (verified) config/cors.php:32 'supports_credentials'=>false (verified)
Attack scenario
A logged-in admin visits a malicious site; its JavaScript issues cross-origin requests to the API. Because every origin/method/header is allowed and the export/sync routes (AV-02) need no credentials, the attacker's page reads voucher-code exports and other unauthenticated responses from the victim's network position. The wildcard also leaves no origin barrier should credentialed CORS ever be enabled.
Impact

Cross-origin scripts on attacker-controlled sites can call the API (including unauthenticated sync/export endpoints) and read sensitive JSON responses cross-origin, aiding data theft and token-exfiltration scenarios. A removed defense layer if credentialed CORS is later enabled.

Remediation
Restrict allowed_origins to the specific admin SPA domain(s); remove the '*' from paths (limit to api/*); avoid wildcard allowed_methods/headers; keep supports_credentials false unless paired with an explicit origin allowlist.
Verifier note

Confirmed verbatim in config/cors.php. Took the higher (P2) severity from the auth_jwt finding since the bearer-token model means supports_credentials=false does not fully protect responses.

AV-14

axios 0.25.0 vulnerable to CVE-2023-45857 (XSRF token leak) and CVE-2025-27152 (SSRF)

P2 A06:2021 Vulnerable and Outdated Components CWE-918 CVSS 6.5 CVE-2023-45857 EPSS ~0.3% confirmed
Description

axios 0.25.0 is affected by CVE-2023-45857 (XSRF-TOKEN header leaked to a cross-origin redirect target) and the 0.x line is exposed to CVE-2025-27152 (absolute-URL/SSRF + credential leak when a baseURL is set). The SPA's axios carries Sanctum/XSRF credentials for the admin API.

Evidence
package.json:14 "axios": "^0.25" -> yarn.lock:1744 version 0.25.0 (verified) fixed: 0.28.0 for CVE-2023-45857; 1.8.0 for CVE-2025-27152 axios is the SPA's primary HTTP client (resources/js/libs/request.js) carrying the admin API bearer/XSRF credentials
Attack scenario
An authenticated admin's request is redirected cross-origin (attacker-influenced URL or a redirect to an attacker host); axios 0.25.0 leaks the configured XSRF-TOKEN header to the redirect target, exfiltrating the token. The 0.x SSRF (CVE-2025-27152) can also leak credentials when a baseURL is set and an absolute URL is supplied.
Impact

Leakage of CSRF/session tokens of an authenticated admin to a third-party host on redirect, enabling session riding against the admin API. Lower bound because exploitation needs a redirect to a malicious origin.

Remediation
Upgrade axios to >=1.8.0 (review 0.x breaking changes) or at minimum 0.28.0 to close CVE-2023-45857. Re-pin package.json and refresh yarn.lock.
Verifier note

Version confirmed at yarn.lock:1744.

AV-15

follow-redirects 1.15.1 vulnerable to proxy-auth header leak (CVE-2024-28849) and URL parsing (CVE-2023-26159)

P2 A06:2021 Vulnerable and Outdated Components CWE-200 CVSS 6.5 CVE-2024-28849 EPSS ~0.2% confirmed
Description

follow-redirects 1.15.1 is below the fixes for CVE-2023-26159 (improper URL handling treating untrusted location as same-origin) and CVE-2024-28849 (forwards Proxy-Authorization/Authorization on cross-host redirect). Pulled transitively by axios and the dev-server, affecting both runtime SPA requests and the build pipeline.

Evidence
yarn.lock:3090-3091 follow-redirects@^1.0.0/^1.14.7 resolves version 1.15.1 (verified) CVE-2023-26159 fixed 1.15.4; CVE-2024-28849 fixed 1.15.6 transitive via axios 0.25.0 (axios deps follow-redirects ^1.14.7) and the laravel-mix dev-server stack
Attack scenario
On a cross-host redirect, follow-redirects 1.15.1 forwards the Proxy-Authorization/Authorization header to the new host, leaking credentials to an attacker-controlled redirect target. In the dev-server context, proxy credentials can leak from a developer's machine.
Impact

Credential/header leakage to attacker-controlled redirect targets; reinforces the axios token-leak path (AV-14). Dev-server proxy credentials can leak from a developer's machine.

Remediation
Add a yarn resolutions override pinning follow-redirects to >=1.15.6 and reinstall, or upgrade axios (which re-pins follow-redirects).
Verifier note

Version confirmed at yarn.lock:3091.

AV-16

Publicly exposed Swagger UI (v4.13.2) with empty middleware — API schema disclosure

P2 A05:2021 Security Misconfiguration CWE-1188 CVSS 5.3 EPSS n/a confirmed
Description

darkaonline/l5-swagger bundles swagger-ui v4.13.2 and registers /api/documentation with completely empty middleware (no auth, no throttle). An unauthenticated docs endpoint discloses the full admin API contract; older 4.x swagger-ui has had DOM-XSS issues via crafted spec/urls parameters, though that requires a victim opening a malicious spec URL (lower confidence).

Evidence
composer.lock:4797 swagger-api/swagger-ui version v4.13.2 (verified) config/l5-swagger.php:15 'api' => 'api/documentation' UI route (verified) config/l5-swagger.php:63-67 'middleware' => ['api'=>[], 'asset'=>[], 'docs'=>[], 'oauth2_callback'=>[]] — all empty, no auth/throttle (verified) config/l5-swagger.php:218 'generate_always' => env('L5_SWAGGER_GENERATE_ALWAYS', false) — route still serves the cached storage/api-docs spec
Attack scenario
An unauthenticated visitor opens /api/documentation and reads the full internal API surface (every backend route, parameters, and the Sanctum security scheme), using it as a recon map for the BOLA/auth findings. If docs were generated, the cached spec is served via empty middleware.
Impact

Unauthenticated information disclosure of the entire admin API contract, easing targeting of the access-control findings; secondary potential DOM-XSS in the docs viewer against an admin opening a crafted spec URL.

Remediation
Add auth + throttle middleware to the l5-swagger middleware config (e.g. ['auth','can:view-docs']) or disable the route in production. Upgrade swagger-ui to a current 5.x via a newer l5-swagger, or remove it entirely in prod.
Verifier note

Confirmed empty middleware and v4.13.2. The schema-disclosure aspect is the solid part; the DOM-XSS is theoretical (requires crafted spec URL), so kept the finding at the information-disclosure severity (P2).

AV-17

Abandoned socket.io-client v2 / engine.io-client v3 realtime stack (ws 7.4.6 ReDoS, CVE-2024-37890)

P2 A06:2021 Vulnerable and Outdated Components CWE-1104 CVSS 5.3 CVE-2024-37890 EPSS ~0.3% confirmed
Description

The realtime layer is on the socket.io v2 line (socket.io-client 2.5.0, engine.io-client 3.5.2), several majors behind and effectively unmaintained. Its transitive ws resolves to 7.4.6, vulnerable to CVE-2024-37890 (ReDoS via many HTTP headers; fixed 7.5.10/8.17.1). The v2 stack will not be patched.

Evidence
package.json socket.io-client ^2.3.0 -> yarn.lock:5293 version 2.5.0 (current is v4) (verified) yarn.lock:2797 engine.io-client version 3.5.2 (verified) yarn.lock:6094 ws ~7.4.2 resolves 7.4.6 — CVE-2024-37890 ReDoS, fixed 7.5.10 (verified) resources/js/bootstrap.js wires laravel-echo to socket.io
Attack scenario
A server response (or proxy) returns a request/handshake with many HTTP headers; the transitive ws 7.4.6 in the realtime stack triggers a ReDoS DoS (CVE-2024-37890). The v2 socket.io line is abandoned so these accumulate unpatched.
Impact

Denial-of-service against the websocket handshake/parse path and accumulating unpatched advisories with no upstream fix for the v2 line. Lower confidence on direct exploitability since ws here is the client; the DoS surface depends on whether server-supplied header sets reach the parser.

Remediation
Migrate to socket.io-client v4 (and matching server) or to the Pusher/laravel-echo native channels alist-portal uses. Add a yarn resolution for ws >=7.5.10 as an interim mitigation.
Verifier note

Versions confirmed at yarn.lock:5293/2797/6094.

AV-20

Sensitive influencer PII exposed through unscoped review/listing endpoints to any authenticated user

P2 A01:2021 Broken Access Control CWE-200 CVSS 6.5 EPSS n/a confirmed
Description

The moderation and dedicated-offer listing endpoints expose influencer personal data (mobile numbers, social handles, review content) and merchant data to every authenticated user with no role scoping. The 'ChatAgent' namespace implies a moderation-only role, but no such restriction is enforced server-side (consistent with AV-03).

Evidence
app/Http/Controllers/Api/Backend/ChatAgent/UserReviewController.php:57-78 index() selects influencers.mobile as phone_number, instagram_url, review comments, merchant and offer details, returned paginated to any authenticated caller (verified) ChatAgent/UserReviewController.php:47 index(Request) — no role/permission gate; route routes/api.php:182 only has auth:sanctum DedicatedOfferUserController index exposes influencer name/instagram/follower data filtered only by search string
Attack scenario
A low-privilege admin account lists the chat-agent review queue (GET /api/backend/reviews) and harvests influencer phone numbers, Instagram handles, review comments, and merchant data in bulk; no role gate restricts the 'ChatAgent' moderation surface.
Impact

Bulk exposure of influencer PII (phone numbers, social identities) and merchant data to any authenticated account — a privacy/GDPR-class data-leak risk.

Remediation
Restrict these endpoints to the intended moderation/admin role via permission middleware or policies, and minimize the columns returned (avoid raw phone numbers unless required for the specific authorized user).
Verifier note

Confirmed select of influencers.mobile/instagram_url at ChatAgent/UserReviewController.php:57-78 with no role gate.

AV-22

Weak login brute-force protection: per-IP-only throttle, client-side-only captcha, no account lockout

P2 API2:2023 Broken Authentication CWE-307 CVSS 5.3 EPSS n/a confirmed
Description

The only server-side brute-force control on the API login is throttle:10,2 keyed by route+IP, so a distributed attacker can brute-force; there is no per-account lockout. The captcha is a purely client-side widget — the LoginRequest validates only email and password and never verifies any captcha token — so it is trivially bypassed by calling the API directly. Combines with the unthrottled /websocket/login (AV-11) to make credential attacks practical.

Evidence
routes/api.php:84 throttle:10,2 keyed by route+client IP, no per-account counter (verified) app/Http/Requests/Backend/Auth/Login.php rules() validate only email and password — no server-side captcha token validation (verified) resources/js/components/admin/auth/Login.vue gates login only on a client-side isValidCaptcha flag (per scanner; consistent with FormRequest having no captcha rule) app/Http/Controllers/Api/Backend/Auth/LoginController.php login() runs Auth::attempt with no lockout/failed-attempt tracking (verified)
Attack scenario
A distributed/IP-rotating attacker calls POST /api/backend/login directly (the vue-client-recaptcha is verified only in the SPA, never server-side), bypassing the captcha and the per-IP throttle:10,2, and brute-forces/credential-stuffs admin accounts with no per-account lockout.
Impact

Practical credential stuffing/password brute-force against admin accounts, especially given the known seeded superadmin credential (AV-01). Slight account enumeration via distinct messages (auth.failed vs paused/deactivated).

Remediation
Implement per-account + per-IP lockout (ThrottlesLogins/Fortify). Verify the captcha server-side (reCAPTCHA secret verification) instead of trusting the client. Use uniform error responses to avoid enumeration. Lower the throttle window and add exponential backoff.
Verifier note

Confirmed: Login FormRequest has no captcha rule, throttle:10,2 only on API login, no lockout in LoginController.

AV-18

minimum-stability:dev with multiple alpha/beta/rc frontend deps and stray packages

P3 A08:2021 Software and Data Integrity Failures CWE-1104 CVSS 3.7 EPSS n/a confirmed
Description

composer minimum-stability is 'dev' (offset only by prefer-stable). The frontend pins several pre-release widget libs as production deps (vue-quill beta, pusher-js beta, v-calendar alpha, vue-toastification rc). Pre-release packages skip stable QA/security review. The manifest also contains the dubious 'add' utility and a self-referential 'yarn' dependency, common typosquat/dependency-confusion bait.

Evidence
composer.json:74 "minimum-stability": "dev" (verified) package.json @vueup/vue-quill ^1.0.0-beta.9, pusher-js ^7.1.0-beta, v-calendar ^3.0.0-alpha.8, vue-toastification ^2.0.0-rc.5 package.json stray 'add' package and a self-listed 'yarn' dependency widen the install surface
Attack scenario
A pre-release widget library is abandoned mid-stream or ships an undisclosed defect; or the generic-named 'add' package becomes a dependency-confusion/typosquat vector. Combined with the build-time RCE (AV-09), a crafted pre-release dependency widens the supply-chain surface.
Impact

Larger and less-vetted dependency attack surface; alpha/beta libs may receive no security fixes; dependency-confusion/typosquat risk from the stray generic-named 'add' package. Low direct exploitability.

Remediation
Set composer minimum-stability to 'stable'. Replace pre-release frontend deps with stable releases (or remove unused ones). Delete the add and self-listed yarn entries. Re-resolve the lockfile and re-run SCA.
Verifier note

composer.json:74 confirmed; package.json pre-release pins consistent with prior audit notes.

AV-19

Hashids used with empty default salt produces reversible, forgeable review/offer URLs

P3 A04:2021 Insecure Design CWE-330 CVSS 4.3 EPSS n/a confirmed
Description

User-review, rejected-review, and dedicated-offer links pass raw sequential database ids through Hashids constructed with no salt. Hashids is not encryption; with an empty salt it is a fixed, publicly documented, reversible permutation with zero secret material. The tokens are cosmetic obfuscation only: anyone can decode them to recover ids and encode arbitrary ids to forge URLs and enumerate records (an IDOR enabled by the weak token scheme).

Evidence
app/Helper/functions.php:176-178 $hashids = new Hashids(); encodeHex($userId)/encodeHex($offerId) in generateUserReviewURL (verified) app/Helper/functions.php:192-197 generateRejectedReviewURL same unsalted Hashids (verified) app/Http/Controllers/Api/Backend/DedicatedOfferController.php:102-104 new Hashids(); encodeHex($dedicated_offer_id); link emailed to merchant (verified) composer.lock hashids/hashids 4.1.0 — __construct(string $salt='') so new Hashids() uses an empty, publicly-known salt (verified)
Attack scenario
An attacker takes a /userReview/<enc>-<enc> or /dedicated_offer/<enc> link, decodes the segments locally with the open-source hashids library and the empty default salt to recover raw user/offer/review ids, then re-encodes arbitrary sequential ids to forge valid-looking links and enumerate records.
Impact

Predictable/forgeable links allow enumeration and forgery of review and dedicated-offer URLs; attackers can craft links for any id and harvest numeric ids, defeating the intended unguessability of these links.

Remediation
Do not rely on Hashids for security. Use a cryptographically random opaque token (Str::random(32)) stored with the record, or a signed expiring URL (URL::signedRoute), and enforce authorization on the consuming endpoint rather than trusting the decoded id. If Hashids is kept for cosmetic ids, supply a long secret salt from env and still enforce server-side ownership checks.
Verifier note

Confirmed: new Hashids() at functions.php:176/193 and DedicatedOfferController.php:102, hashids 4.1.0 empty default salt. Adjusted CVSS to PR:L (the consuming review endpoints are largely in-app/emailed) and added I:L for the forgery aspect.

AV-21

Cleartext passwords emailed on admin creation and password reset

P3 A04:2021 Insecure Design CWE-319 CVSS 4.3 EPSS n/a confirmed
Description

When an admin user is created (store) or has their password reset (resetPassword), the cleartext password is emailed via NewAdminUser/UpdatePassword notifications. Passwords transmitted over email traverse and are stored in mail systems in cleartext, outside the application's security boundary. The generate-password endpoint returns a CSPRNG Str::random(10) (acceptable entropy) but it is still emailed cleartext.

Evidence
app/Http/Controllers/Api/Backend/AdminUserController.php:128 $user->notify(new NewAdminUser($password, $email)) — $password is the cleartext input (verified) app/Notifications/NewAdminUser.php passes 'password'=>$this->password to emails.new-admin-user app/Http/Controllers/Api/Backend/AdminUserController.php:511 $password = $input['password']; $user->notify(new UpdatePassword($password)) (verified) app/Http/Controllers/Api/Backend/AdminUserController.php:454 generatePassword returns Str::random(10) (CSPRNG-backed) which is then emailed in cleartext
Attack scenario
An admin creates a user or resets a password; the cleartext password is emailed and persists in mail systems/inboxes/logs outside the app boundary. An attacker who later compromises a mailbox or mail-server logs recovers a working credential.
Impact

Admin passwords exposed in inboxes, mail-server logs, and compromised mailboxes, enabling later account compromise. Lower severity since it requires an authenticated admin to trigger and depends on mail-channel exposure.

Remediation
Do not email cleartext passwords. Send a one-time, signed, short-lived password-set/reset link and force a change on first login. If a temporary password must be shared, deliver out-of-band and expire after first use.
Verifier note

Confirmed at AdminUserController.php:128/511 and generatePassword:454.

AV-23

Unvalidated dynamic ORDER BY column from request 'oc' across ~17 list endpoints

P3 A03:2021 Injection CWE-89 CVSS 4.3 EPSS n/a likely
Description

Every list/index endpoint reads the sort column from the oc parameter with no allowlist and passes it to Eloquent orderBy("$oc", $od). The direction $od is safely constrained to asc/desc. In Laravel 9 the orderBy column passes through the grammar's wrap() routine which backtick-quotes identifier segments, neutralizing a naive oc=1;DROP payload — this mitigation is why this is 'likely' not 'confirmed'. But supplying unvalidated attacker input as a SQL identifier is fragile, can leak schema via error responses, and is one refactor (to orderByRaw) away from real injection across all endpoints.

Evidence
app/Http/Controllers/Api/Backend/OfferController.php:62 $oc = $request->has('oc') ? $request->oc : 'date'; — no allowlist (verified) app/Http/Controllers/Api/Backend/OfferController.php:125 $offers->orderBy("$oc", $od) (verified) Same orderBy("$oc", $od) pattern in DedicatedOfferUserController:49 and ChatAgent/UserReviewController:93 (verified) plus ~14 sibling controllers per scanner OfferController.php:867 catch returns $e->getMessage() to the client (verified) — error-message schema disclosure
Attack scenario
An authenticated user supplies an arbitrary oc value to sort by non-exposed columns or trigger verbose SQL errors that leak schema/column names (e.g. merchantWidget catch returns $e->getMessage()), aiding other attacks. A future refactor to orderByRaw on any of these 17 sites would turn this into direct injection.
Impact

Low-confidence SQLi contingent on a grammar-wrapping bypass; concretely, an authenticated user can sort by arbitrary/non-exposed columns and trigger verbose SQL errors disclosing schema. Primary risk is the unsafe pattern repeated across ~17 endpoints.

Remediation
Validate oc against a per-endpoint allowlist of sortable columns before calling orderBy. Never pass raw request input as a SQL identifier. Stop returning $e->getMessage() to clients to avoid leaking SQL errors.
Verifier note

Confirmed pattern at OfferController.php:62/125, DedicatedOfferUserController:49, ChatAgent/UserReviewController:93. Kept 'likely' because Laravel's identifier wrapping neutralizes straightforward injection; the error-message disclosure (OfferController.php:867) is confirmed.

AV-24

Unescaped admin-authored HTML rendered via Blade {!! !!} in outbound review email (content injection)

P3 A03:2021 Injection CWE-79 CVSS 3.5 EPSS n/a likely
Description

The review-notification email emits offer email_content via Blade's unescaped {!! !!} directive. The data comes from the offers.email_content column populated by admin-panel users. This is NOT SSTI — Blade {!! !!} renders raw HTML and does not re-parse Blade/PHP, so no RCE. The exposure is stored HTML injection: a backend user can embed arbitrary HTML (links, images, tracking pixels, phishing markup) delivered verbatim to influencer inboxes.

Evidence
resources/views/emails/user-review-email.blade.php:19 {!!$data['emailContent']!!}<br> — raw unescaped output (verified) app/Http/Controllers/Api/Backend/ChatAgent/UserReviewController.php:243 'emailContent' => $reviews->offerCode->offer->email_content — sourced from the admin-authored offers.email_content column app/Notifications/ReviewEmail.php view('emails.user-review-email', [...])
Attack scenario
A backend user with offer-authoring rights sets an offer's email_content to phishing/tracking HTML; that markup is rendered verbatim in review-notification emails sent to influencers under the A-List brand.
Impact

A backend user with offer-authoring rights can inject arbitrary HTML into emails to influencers, enabling phishing/content spoofing under the A-List brand. No code execution; limited by requiring an authoring role and email-client sandboxing.

Remediation
If email_content is plain text, escape with {{ }}. If limited rich text is needed, sanitize server-side with an HTML allowlist (e.g. HTMLPurifier) before storage/rendering rather than emitting raw model data with {!! !!}.
Verifier note

Confirmed {!! !!} at user-review-email.blade.php:19. Defense-in-depth content-injection item; correctly NOT flagged as RCE/SSTI.

AV-25

Predictable transitive deps: nanoid 3.3.4 (CVE-2024-55565) and stale build chain

P3 A06:2021 Vulnerable and Outdated Components CWE-330 CVSS 4.3 CVE-2024-55565 EPSS ~0.1% likely
Description

nanoid 3.3.4 is below the CVE-2024-55565 fix (3.3.8). Here nanoid is a build-time transitive of postcss, so direct security impact is limited, but it is representative of a broader pattern: the lockfile freezes numerous transitive packages at 2022-era versions that have since received security releases, and with the project abandoned none will ever be bumped.

Evidence
yarn.lock:4128-4129 nanoid version 3.3.4 (CVE-2024-55565 fixed 3.3.8) (verified) nanoid is transitive via postcss used by the laravel-mix/postcss build yarn.lock also freezes terser 5.14.2 and other 2022-era transitive deps
Attack scenario
A build path calls nanoid with a non-integer length, triggering the predictability/looping flaw (CVE-2024-55565). Mostly representative of a long tail of 2022-era transitive deps that an SCA scan flags and that will never be bumped on this abandoned branch.
Impact

Low direct impact (build-time usage), but indicative of dozens of unmaintained transitive deps an SCA scan would flag and that will never be remediated on this branch.

Remediation
Run yarn audit / snyk test and apply yarn resolutions for high/critical transitive hits (nanoid >=3.3.8, ws >=7.5.10, follow-redirects >=1.15.6, @babel/traverse >=7.23.2). Better: rebase the frontend on the maintained alist-portal toolchain.
Verifier note

Version confirmed at yarn.lock:4129.

AV-26

FullCalendar sample Google Calendar API key committed in vendored demo asset

P3 A05:2021 Security Misconfiguration CWE-798 CVSS 2.0 EPSS n/a confirmed
Description

A Google Calendar API key string is hardcoded in a vendored FullCalendar demo asset. The in-file comment and the value identify it as the well-known FullCalendar documentation sample key, not an A-List credential, referenced from a demo widget not wired into the active admin SPA routes. Risk is low and primarily hygiene/false-trail.

Evidence
public/assets/vendor/calendar/js/custom-google-calendar.js:20 googleCalendarApiKey: 'AIzaSyDcnW6WejpTOCffshGDDb4neIrXVUA1EAE' (verified) public/assets/vendor/calendar/js/custom-google-calendar.js:17 // THIS KEY WON'T WORK IN PRODUCTION!!! (verified)
Attack scenario
A secret scanner flags the AIza string in the vendored demo asset; it is the public FullCalendar sample key (marked non-production) so it yields no real access but consumes triage effort and can mask the genuine key finding (AV-12).
Impact

Negligible direct impact (not an A-List key and likely non-functional). Contributes to secret-scanning noise and signals weak vendoring hygiene; can mask the real key (AV-12).

Remediation
Remove the unused vendored demo asset, or replace the literal with a build-time env-injected value if the calendar is ever activated. Add a Gitleaks allowlist entry for this known sample key while keeping the real-key rule active.
Verifier note

Confirmed; it is the documented public FullCalendar sample key. Kept as informational P3.

AV-27

Non-cryptographic mt_rand used in verification-code helper (dead code)

P3 A02:2021 Cryptographic Failures CWE-330 CVSS 2.0 EPSS n/a likely
Description

verificationCode() produces a 6-digit code using mt_rand (Mersenne Twister), which is not cryptographically secure and predictable after observing enough outputs. In the current tree the function is unused, lowering practical severity to a hardening/dead-code concern, but the pattern is dangerous if reused.

Evidence
app/Helper/functions.php:33-35 function verificationCode() { return sprintf('%06d', mt_rand(0, 999999)); } (verified) grep across app/ and resources/ shows verificationCode() is defined but never referenced (verified — only the definition matches)
Attack scenario
If verificationCode() were wired into an OTP/verification/reset flow, an attacker observing several codes could predict the Mersenne Twister state, or brute-force the 6-digit space, to guess a victim's code. As currently dead code the path is latent.
Impact

If wired into any verification/OTP flow, codes become predictable/guessable. As dead code, impact is latent.

Remediation
Use a CSPRNG for any security-relevant code (random_int(0, 999999)) with attempt rate-limiting and short expiry. Remove the helper if genuinely unused to prevent accidental reuse.
Verifier note

Confirmed dead code: function defined at functions.php:33, zero call sites. Kept low because it is not currently reachable.

AV-28

No CI/CD pipeline: zero automated SCA, dependency, or secret scanning gate

P3 A08:2021 Software and Data Integrity Failures CWE-1395 CVSS 0.0 EPSS n/a confirmed
Description

The repository has no CI/CD pipeline of any kind; the only automation file is .styleci.yml (code-style preset, no security function). There is no automated SCA (composer/yarn audit, Snyk), no secret scanning, no dependency-pin enforcement, and no test gate. On the positive side, the absence of a pipeline also means there are no unpinned-action/pipeline-secret/force-push-to-mirror CI attack vectors.

Evidence
No .github/workflows, .gitlab-ci.yml, Jenkinsfile, .circleci, .travis.yml, or bitbucket-pipelines.yml at repo root — only .styleci.yml present (verified via ls) phpunit.xml exists but tests/ contains only ExampleTest stubs (CLAUDE.md note 3: CI signal is zero)
Attack scenario
No exploit path; this is a governance gap. The vulnerable dependencies and committed secrets catalogued above would never be surfaced automatically, so regressions and malicious-dependency introductions ship without a gate.
Impact

Vulnerable and outdated components accumulate silently with no detection; any future regression or malicious dependency ships without a gate. Governance/process gap rather than a directly exploitable runtime flaw, hence P3 / CVSS 0.0.

Remediation
If revived: add a pipeline (Bitbucket Pipelines or GitHub Actions) running composer audit, yarn audit --groups dependencies, and a secret scanner (gitleaks) on every PR, pinning third-party actions by commit SHA. If archived, mark read-only and document the unsupported status.
Verifier note

Confirmed: only .styleci.yml present at root; no pipeline config. Informational.

Remediation roadmap

Immediate
Rotate-before-refactor (per the May 2026 A-List audit) and close the one-step takeover paths:
  • Rotate the seeded superadmin credential (AV-01) subin@alist.ae everywhere it was provisioned; remove the literal from AdminSeeder.php and require env('SEED_ADMIN_PASSWORD').
  • Move the voucher-export and sync-* routes inside auth:sanctum (AV-02), routes/api.php lines 54-82, or delete them if dormant.
  • Force-reset the shared Alist@2022$! sync password (AV-07) on all bulk-provisioned influencer/merchant accounts.
  • Rotate the live Google Maps key (AV-12) and apply referrer/API/quota restrictions.
  • Keep the repo off untrusted networks — treat as archived reference until the above land.
This week
Restore the authorization model and close the confirmed injection:
  • Add function-level authz (AV-03): attach permission:/role: middleware and/or Policies to every /api/backend route; stop trusting client-side menu hiding.
  • Parameter-bind the merchantWidget query (AV-06) and stop returning $e->getMessage() to clients.
  • Allowlist assignable role ids (AV-04) to block self-escalation to superadmin.
  • Throttle /websocket/login and regenerate the session after a successful attempt (AV-11); verify the captcha server-side and add per-account lockout (AV-22).
This month
Object-level authz, token hygiene, and dependency remediation:
  • Enforce object-level authorization / ownership scoping on read and destructive endpoints (AV-05); replace unsalted Hashids links with signed/opaque tokens (AV-19).
  • Set a finite Sanctum expiration, prefer cookie auth, and re-enable the 401/403 auto-logout (AV-10).
  • Patch the supply chain: yarn resolutions for @babel/traverse >=7.23.2 (AV-09), axios >=1.8.0 (AV-14), follow-redirects >=1.15.6 (AV-15), ws >=7.5.10 (AV-17), nanoid >=3.3.8 (AV-25).
  • Lock down Swagger UI (AV-16), restrict CORS to the SPA origin(s) (AV-13), and scope influencer-PII listings to the moderation role (AV-20).
  • Stop emailing cleartext passwords — use signed one-time set/reset links (AV-21).
Hardening
Platform-level disposition and governance:
  • Treat the EOL stack as non-deployable (AV-08): migrate to supported PHP 8.2+ / Laravel LTS per alist-portal, or formally archive the repo read-only.
  • Add a CI/CD pipeline (AV-28) running composer audit, yarn audit, and gitleaks on every PR, with a test asserting every /api/backend route except login requires auth.
  • Set composer minimum-stability to stable and remove pre-release / stray packages (AV-18).
  • Replace mt_rand with a CSPRNG (AV-27) and remove the FullCalendar sample-key demo asset (AV-26).

Code quality

Run / deploy

Local setup

composer install
yarn install                # yarn.lock is committed, not package-lock.json
cp .env.example .env
php artisan key:generate
# Fill in DB_*, DB_*_SECOND, AWS_*, PUSHER_* (or socket.io host)
php artisan migrate --seed
yarn dev                    # laravel-mix watch
php artisan serve           # serve at http://127.0.0.1:8000

Environment

Deployment hints

No CI/CD configuration is checked in. Compiled assets are committed under public/, which suggests deploys may have been "git pull → composer install → php artisan migrate" without running yarn prod on the server. No Dockerfile, no docker-compose.yml, no infra-as-code. Confirm with the team where this was hosted (if it ever was). Given the Dec 2023 disable-sync commit, the most plausible end state is that traffic was cut over to alist-portal and this repo was left in place.

What to know before editing