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.
/admin/. Covers offers, merchants, users, reviews, chat-agent moderation, RBAC, activity logs. NOT the public/customer app.master tip is 31 Mar 2023. One stray branch (ALISTV2-158-disable-v2-v1-sync) was pushed in Dec 2023 with the explicit purpose of disabling the cross-database sync — i.e. shutting it down rather than fixing it.mysql) and the legacy v1 system (mysql2 via DB_*_SECOND env vars). Most domain controllers and a dozen Sync* jobs reach across to mysql2. Local dev without a v1 mirror will silently break flows.alist-portal is on Laravel 11 and looks like the surviving codebase. Cross-check any "is this still used?" question against it.public/). 30+ stale branches.| Category | Technology | Version | Notes |
|---|---|---|---|
| Language | PHP | ^8.0.2 | PHP 8.0 reached EOL Nov 2023. |
| Framework | Laravel | ^9.2 | Laravel 9 reached EOL Feb 2024. |
| Auth | laravel/sanctum | ^2.14.1 | Token + cookie auth for the SPA. |
| RBAC | spatie/laravel-permission | ^5.5 | Roles/permissions; chat-agent and admin roles wired through. |
| API docs | darkaonline/l5-swagger | ^8.3 | OpenAPI annotations on controllers (e.g. LoginController). Output in storage/api-docs/. |
| Excel | maatwebsite/excel | ^3.1 | Used for offer-code exports. |
| Images | intervention/image | ^2.7 | Banner upload + crop. |
| jdavidbakr/mail-tracker | ^6.0 | Tracks opens/clicks for sent admin mail. | |
| Geo | stevebauman/location | ^6.4 | IP geolocation for login activity. |
| IDs | hashids/hashids | ^4.1 | Public-facing obfuscation of integer IDs. |
| Cache | predis/predis | ^2.0 | Redis client; default driver is file in env. |
| Frontend | Vue + Vue Router + Vuex | 3.2 / 4.0 / 4.0 | Composition + Options API mix. |
| Build | laravel-mix | ^6.0 | Webpack 5 wrapper. Not Vite. |
| Realtime | laravel-echo + socket.io-client | 1.13 / 2.3 | socket.io v2 is several majors stale; Pusher path is commented out. |
| Charts | apexcharts + vue3-apexcharts | 3.35 / 1.4 | Dashboard charts. |
| Forms | vee-validate + yup | 4.5 / 0.32 | SPA form validation. |
| UI bits | vue-final-modal, vue-toastification, v-calendar, vue-advanced-cropper, vue3-editor, @vueform/multiselect, @sipec/vue3-tags-input | — | Many one-off Vue 3 widget libs, several still in alpha/beta at the time. |
| Database | MySQL | — | TWO connections: mysql (this app) + mysql2 (legacy v1 via DB_*_SECOND). |
| Testing | phpunit | ^9.5.10 | Only stub ExampleTest.php files exist. |
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.
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.
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
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.
| Category | Status | Notes |
|---|---|---|
| A01:2021 Broken Access Control | vuln | AV-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 Failures | vuln | AV-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 Injection | vuln | AV-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 Design | vuln | AV-19 unsalted Hashids tokens; AV-21 cleartext-password email flow design. |
| A05:2021 Security Misconfiguration | vuln | AV-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 Components | vuln | AV-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 Failures | vuln | AV-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 Failures | vuln | AV-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 Failures | partial | An 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 Forgery | partial | No 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 Authorization | vuln | AV-05 — resources fetched/mutated by raw path id with no ownership scoping across read and destructive endpoints. |
| API2:2023 Broken Authentication | vuln | AV-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. |
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.
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.
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.Confirmed verbatim at AdminSeeder.php:18-25. Highest-severity finding alongside AV-02; trivially exploitable given the known login endpoint.
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).
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.
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.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.
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.
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.
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.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.
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.
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.
Confirmed: roles.* only validates exists:roles,id; assignRole called directly at :124 and :203.
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).
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.
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.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.
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.
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.
->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.Confirmed verbatim at OfferController.php:851; PR:L because it requires any authenticated token, but AV-03 means the lowest role qualifies.
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.
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.
Confirmed at all four cited command files.
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.
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.
composer audit + composer outdated -D each deploy. Do not expose to untrusted networks.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.
@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.
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.
"@babel/traverse": "^7.23.2", reinstall, and rebuild. Long-term migrate the build to Vite as alist-portal did.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).
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.
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.
'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.Confirmed: expiration null, localStorage storage, dead 401 handler all verified verbatim.
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).
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).
/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.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.
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.
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.
AIza[0-9A-Za-z_-]{35} plus a pre-commit hook.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.
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).
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.
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.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.
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.
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.
>=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.Version confirmed at yarn.lock:1744.
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.
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.
follow-redirects to >=1.15.6 and reinstall, or upgrade axios (which re-pins follow-redirects).Version confirmed at yarn.lock:3091.
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).
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.
['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.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).
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.
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.
ws >=7.5.10 as an interim mitigation.Versions confirmed at yarn.lock:5293/2797/6094.
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).
Bulk exposure of influencer PII (phone numbers, social identities) and merchant data to any authenticated account — a privacy/GDPR-class data-leak risk.
Confirmed select of influencers.mobile/instagram_url at ChatAgent/UserReviewController.php:57-78 with no role gate.
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.
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).
Confirmed: Login FormRequest has no captcha rule, throttle:10,2 only on API login, no lockout in LoginController.
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.
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.
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.composer.json:74 confirmed; package.json pre-release pins consistent with prior audit notes.
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).
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.
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.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.
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.
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.
Confirmed at AdminUserController.php:128/511 and generatePassword:454.
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.
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.
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.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.
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.
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.
{{ }}. 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 {!! !!}.Confirmed {!! !!} at user-review-email.blade.php:19. Defense-in-depth content-injection item; correctly NOT flagged as RCE/SSTI.
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.
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.
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.Version confirmed at yarn.lock:4129.
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.
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).
Confirmed; it is the documented public FullCalendar sample key. Kept as informational P3.
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.
If wired into any verification/OTP flow, codes become predictable/guessable. As dead code, impact is latent.
random_int(0, 999999)) with attempt rate-limiting and short expiry. Remove the helper if genuinely unused to prevent accidental reuse.Confirmed dead code: function defined at functions.php:33, zero call sites. Kept low because it is not currently reachable.
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.
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.
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.Confirmed: only .styleci.yml present at root; no pipeline config. Informational.
subin@alist.ae everywhere it was provisioned; remove the literal from AdminSeeder.php and require env('SEED_ADMIN_PASSWORD').auth:sanctum (AV-02), routes/api.php lines 54-82, or delete them if dormant.Alist@2022$! sync password (AV-07) on all bulk-provisioned influencer/merchant accounts.permission:/role: middleware and/or Policies to every /api/backend route; stop trusting client-side menu hiding.$e->getMessage() to clients./websocket/login and regenerate the session after a successful attempt (AV-11); verify the captcha server-side and add per-account lockout (AV-22).@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).composer audit, yarn audit, and gitleaks on every PR, with a test asserting every /api/backend route except login requires auth.minimum-stability to stable and remove pre-release / stray packages (AV-18).mt_rand with a CSPRNG (AV-27) and remove the FullCalendar sample-key demo asset (AV-26).app/Http/Controllers/Api/Backend/OfferController.php at 959 lines — clear God-controller smell. Merchant and UserReview controllers are also >200 lines.feat:, fix:) but ~70% of all 391 commits are "fix:" bug-bash messages with little context. Reshma authored 256 of those commits..github/, no bitbucket-pipelines.yml, no GitHub Actions. .styleci.yml exists but StyleCI was retired.Route::resource, but with frequent overrides using POST /resource/{id}/delete and POST /resource/{id} for update — non-idiomatic and inconsistent with Laravel conventions.LoginController.php has @OA\Post blocks). Worth running php artisan l5-swagger:generate to inspect the live API surface.composer.lock 368 KB, yarn.lock 292 KB) — reproducible builds possible if you can get the EOL toolchain to install.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
DB_* for v2's own schema, DB_*_SECOND for the legacy v1 read/write target. Without v1 access, sync-heavy flows will fail.resources/js/bootstrap.js wires laravel-echo to socket.io v2; expect a separate socket.io server. There's also a commented-out Pusher block — pick one.AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_BUCKET — likely for uploaded banner/screenshot images.mailhog on port 1025 for local; production override unknown.sync; real deployment likely used Redis or database queue for the Sync* jobs.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.
master. The most recent intent is on ALISTV2-158-disable-v2-v1-sync and fix-offer-page-loading-error, both from Dec 2023. master tip is 8 months older.mysql2 connection is the spine of the system. Many controllers do DB::connection('mysql2')->table('food_offers') directly. If you don't have a v1 database mirrored locally, expect "table doesn't exist" or silent empty queries.app/Helper/functions.php, dashboardBroadcast.php, offerBroadcast.php are loaded as PHP files, not PSR-4 classes. Grep there before adding utilities.yarn dev / yarn prod./ and /admin/login (same Vue component). The blade backend/login.blade.php titled "Websocket - Login" is dead code — ignore it.spatie/laravel-permission gates many controllers via middleware/policies. The ChatAgent namespace exists specifically to scope a moderation-only role. Check role permissions before assuming an endpoint is public.POST /resource/{id}, delete is often POST /resource/{id}/delete. Don't trust the Laravel convention; read routes/api.php.php artisan l5-swagger:generate and visit /api/documentation to get an interactive API surface — fastest way to understand the backend without reading 35 controllers.alist-portal is on Laravel 11 and is the likely successor. Always check it first if the question is "where does this happen today?"