← All repos

a-list-ios

Native iOS app for A-List (alist.ae) — a UAE creator/influencer marketplace where verified influencers browse and redeem dining offers, submit social-media proof, and track redemptions.

Primary stackiOS / Swift 5 Last commit2026-05-19 Repo size325 MB Files4,302 Branchmaster Health Critical

Executive summary

Tech stack

Swift 5.0 iOS 14+ (proj 15) UIKit + Storyboards CocoaPods 1.16.2 Alamofire 5.6.4 Firebase 10.4.0 Intercom 14.0.6 GoogleSignIn 7.1.0 Kingfisher 7.7.0 SDWebImage 5.15.0 Charts 4.1.0 IQKeyboardManager 7.0.3 Lottie 4.1.2 Siren (forked) Core Data
CategoryTechnologyVersionNotes
LanguageSwift5.0~294 .swift files, no Obj-C
iOS targetiOS Deployment14.0 (Podfile) / 15.0 (proj)Mismatch — Podfile post-install pins Pods to 14.0
UIUIKit + Storyboards6 storyboards (Main/Login/Home/PopUp/Venue/Profile), no SwiftUI
Dep managerCocoaPods1.16.2Pods/ directory is committed (325 MB total)
NetworkingAlamofire5.6.4No certificate pinning
Analytics/CrashFirebase Analytics + Crashlytics + Messaging10.4.0Project alist-application
AuthGoogleSignIn (+ Sign in with Apple via entitlement)7.1.0OTP-based email/phone login also used
SupportIntercom iOS SDK14.0.6App id uicijnyh, key hardcoded in SceneDelegate.swift:22
Image loadingKingfisher + SDWebImage7.7.0 / 5.15.0Both shipped — duplicated functionality, bloat
ChartsCharts (Daniel Gindi)4.1.0Used on History/Insights screen
Forced updatesSiren (custom fork)commit e52f7ef1Fork: qubicle-aswinrnath/Siren-Alist.git
PersistenceCore Data + UserDefaults + KeychainModel A-List.xcdatamodeld; auth token mirrored to Keychain keyed by user email
Misc UI PodsSVPinView, FSCalendar, Cosmos 23, DropDown, BSImagePicker, SKPhotoBrowser, BottomPopup, InkPageIndicator, PhoneNumberKit 3.7, ImageCropper, ParallaxHeader, SwiftMessages 10, SkeletonView27 Pods total — heavy dependency surface
CI/CDNoneNo fastlane, no bitbucket-pipelines.yml, no GitHub Actions
TestsXCTest stubs onlyBoth test targets are empty boilerplate

Architecture

Classic storyboard-driven MVC with thin "ViewModel" helpers (7 small VMs in ViewModels/). All API traffic funnels through one APIManager using Alamofire; endpoint catalogue lives in API enum in APIConstants.swift. Global state is in Session (UserDefaults-backed) and AppInfo singletons; the auth token is also mirrored into Keychain by user email. Multi-environment config (Development / Production) is driven by .xcconfig files that inject $(APP_BASE_URL) into Info.plist at build time.

A-List/ ├── AppDelegate.swift Firebase init, FCM, Siren force-update, deep-link router (alist://) ├── SceneDelegate.swift Intercom.setApiKey() — hardcoded ├── Info.plist ATS arbitrary loads = true; URL scheme "alist"; universal links applinks:alist.ae ├── GoogleService-Info.plist Firebase project alist-application (committed) ├── Configuration/ Base / Development / Production .xcconfig — switches base URL ├── Networking/ │ ├── Base Layer/ APIManager, APIConstants, RequestTypeProtocol, ResponseModel │ └── *API.swift OfferAPI, HistoryAPI, ProfileAPI, NotificationAPI, UserRegistrationAPI, CommonFetchAPI ├── Modals/ Codable model structs (mis-named — these are Models, not modal UI) ├── ViewModels/ AccountVM, AddressVM, FilterVM, HistoryVM, OfferVM, ProfileVM, VenueVM ├── Views/ ViewControllers grouped per feature (Login, SignUp, Register, Offers, History, Profile, Calendar, FAQ, …) ├── Custom Views/ Reusable cells, xibs, custom controls ├── Storyboards/ Main, Login, Home, PopUp, Venue, Profile, LaunchScreen ├── Database/ Core Data stack + .xcdatamodeld ├── Utilities/ Session, Keychain, Configuration, Common, Alert, Location, NetworkMonitor, … ├── Extensions/ 20+ extension files (UIView, UIViewController, String, Date, …) ├── Assets.xcassets 236 entries ├── cover.mp4 (6 MB) Onboarding video — committed ├── intro.mp4 (40 MB) Onboarding video — committed ├── QRCode/ Vendored QRCodeGenerator package source (not a pod) ├── Gif Loader/ GIF animation helper ├── Fonts/ Poppins + Segoe UI families └── *.entitlements 4 entitlement files (Debug/Release × Production/Development) — Apple Push + applinks:alist.ae AuthKey_V63PAFH55F.p8 ← APNS private key, committed to repo root (P0) Podfile / Podfile.lock CocoaPods 1.16.2, 27 Pods A-List.xcworkspace Open this, not the .xcodeproj A-ListTests / A-ListUITests Xcode template stubs — no real tests README 4 colors + Apple key id + 2 Bitbucket app passwords (P0)

Security assessment — methodology

Static security review of the a-list-ios source tree, git history, build configuration and dependency manifests, performed by an A-List consulting security engineer. Each candidate finding was de-duplicated and re-verified against the on-disk code (file:line evidence preserved), rated with CVSS 3.1 and mapped to OWASP/CWE, then classified by exploitation confidence (confirmed / likely / theoretical). Backend-only assertions are flagged as latent design weaknesses where server behaviour could not be observed from the client.

OWASP Top 10 2021 OWASP WSTG OWASP API Top 10 2023 OWASP MASVS for mobile CWE CVSS 3.1 NIST CSF 2.0
anthropic-cybersecurity-skills playbooks applied to this repo: conducting-mobile-app-penetration-test exploiting-insecure-data-storage-in-mobile intercepting-mobile-traffic-with-burpsuite performing-mobile-app-certificate-pinning-bypass testing-mobile-api-authentication testing-api-authentication-weaknesses conducting-api-security-testing detecting-api-enumeration-attacks performing-ssl-tls-security-assessment implementing-secret-scanning-with-gitleaks detecting-aws-credential-exposure-with-trufflehog performing-sca-dependency-scanning-with-snyk analyzing-sbom-for-supply-chain-vulnerabilities hunting-for-supply-chain-compromise detecting-supply-chain-attacks-in-ci-cd implementing-secrets-scanning-in-ci-cd

Risk narrative

The A-List iOS app exposes multiple production credentials directly in the git-tracked repository: an Apple APNS token-signing private key (team-wide, since 2022) and two Bitbucket app passwords, with the README conveniently publishing the matching Key Id/Team Id — any repo reader can push spoofed notifications to all users and pivot into the Bitbucket workspace and other A-List repos. The authentication design is fundamentally broken: the login OTP is echoed in the API response body (and harvested/auto-filled client-side), so it provides zero second-factor protection, and this is made trivially observable because App Transport Security is globally disabled with no certificate pinning — a single rogue-Wi-Fi position yields the Bearer token, the OTP, and full account takeover. Stolen tokens are non-expiring and have no server-side revocation, and the token is additionally persisted in cleartext UserDefaults and leaked to device logs via debugPrint in Release. A single-developer personal fork of Siren pinned to a mutable commit runs forced-update logic at every launch, creating a launch-time supply-chain backdoor that no CI/secret-scanning/SCA control would catch. Overall risk is Critical: confirmed credential compromise plus a practical, low-skill account-takeover chain affecting the entire creator base.

Overall risk Critical
3 P0 4 P1 3 P2 8 P3

OWASP coverage

CategoryStatusNote
Mobile M1: Improper Credential UsagevulnAPNS .p8, Bitbucket app passwords, Intercom key hardcoded/committed (ALI-01/02/16).
Mobile M2: Inadequate Supply Chain SecurityvulnPersonal Siren fork on mutable commit, vendored QRCode, no CI/SCA (ALI-09/13/14/15).
Mobile M3: Insecure Authentication/AuthorizationvulnOTP echoed+auto-filled, no token expiry/revocation, param-driven resetPin auth (ALI-03/07/10).
Mobile M4: Insufficient Input/Output ValidationvulnWebView forwards unvalidated server-supplied URLs/any scheme to UIApplication.open (ALI-08).
Mobile M5: Insecure CommunicationvulnATS fully disabled + no certificate/public-key pinning on all API traffic (ALI-04).
Mobile M6: Inadequate Privacy ControlspartialSensitive PINs/voucher codes to system pasteboard; PII trove persisted in plaintext plist (ALI-12/06).
Mobile M7: Insufficient Binary Protectionsn/aNo anti-tamper/obfuscation/jailbreak detection observed, but not in scope of cited findings; not separately assessed.
Mobile M8: Security MisconfigurationvulnATS off, Firebase plist committed, dormant applinks/alist:// deep-link surface, dev artifacts in builds (ALI-04/17/18).
Mobile M9: Insecure Data StoragevulnToken+PIN+PII in UserDefaults plist, Keychain without ThisDeviceOnly, token in device logs (ALI-05/06).
Mobile M10: Insufficient CryptographycleanNo custom/weak crypto primitives observed in cited code; transport weakness tracked under M5.
API2:2023 Broken AuthenticationvulnOTP-in-response ATO, no token revocation, param-driven auth header (ALI-03/07/10).
API3:2023 Broken Object/Property Level AuthpartialPre-OTP account-status/email enumeration via response discrepancy (ALI-11).
API others (Mobile-backed)partialFindings are client-side observations; full API authz testing requires backend access (out of scope for static iOS review).

Detailed findings

P0 critical — 3 P1 high — 4 P2 medium — 3 P3 low / info — 8
ALI-01

Apple APNS token-signing private key (AuthKey_V63PAFH55F.p8) committed in cleartext and git-tracked at repo root

P0 M1 / A02:2021 CWE-798 CWE-312 CVSS 9.1 confirmed
Description

The Apple Push Notification service token-signing private key (.p8) is committed in cleartext at the repository root and is git-tracked (not gitignored), present since 2022. A .p8 APNS auth key is a team-wide token-signing key: combined with the Key Id (V63PAFH55F) and Team Id (ASR5MYB7XL) — both helpfully published in the adjacent README — anyone holding it can sign APNS JWT provider tokens (ES256) for every app under the team. It is a server-side credential that has no business living in a client app repo.

Evidence
/Users/2dot4/Projects/alist-projects/a-list-ios/AuthKey_V63PAFH55F.p8 exists (257 bytes, EC PRIVATE KEY .p8); `git ls-files` lists it as tracked; `git check-ignore` returns exit 1 (NOT gitignored — no *.p8 rule) `git log --diff-filter=A` shows first committed 2022-05-18 by 'Rahul K' in commit 7f7d314 'changed the setup and url to live domain' — present in history ~4 years /Users/2dot4/Projects/alist-projects/a-list-ios/README:1-4 discloses Name AuthKey_V63PAFH55F.p8, Key Id V63PAFH55F, Team Id ASR5MYB7XL — exactly the values needed to mint APNS JWTs CLAUDE.md and bundle id ae.alist.ios confirm Team ASR5MYB7XL is the production Apple team
Attack scenario
An attacker with repo read access (or a leaked clone) downloads AuthKey_V63PAFH55F.p8, reads Key Id V63PAFH55F and Team Id ASR5MYB7XL from README, mints an APNS JWT, and sends spoofed push notifications with phishing links to every installed A-List user.
Impact

Any party with read access to the Bitbucket repo, any clone/branch, or the prior GitLab mirror can forge APNS provider tokens and push arbitrary notifications to ALL A-List users and to any other app under Team ASR5MYB7XL. Enables mass phishing/smishing ('Tap to verify your account' deep-linking into alist://), notification spoofing, and reputational damage. The key never expires; it must be treated as compromised.

Remediation
1) Revoke key V63PAFH55F in the Apple Developer portal immediately and issue a new APNS auth key stored only on the push backend / a secrets manager — never in the app repo. 2) Purge the .p8 from full git history (git-filter-repo / BFG) on Bitbucket and the GitLab mirror, coordinate a force-push. 3) Add *.p8 and AuthKey_* to .gitignore. 4) Remove the Key Id/Team Id disclosure from README. 5) Add gitleaks/trufflehog pre-commit + CI scanning.
Verifier note

Confirmed on disk and in git. CVSS raised to S:C (team-wide blast radius beyond this app); C set to L because the key's primary power is forging/sending (Integrity), not reading user content. Merged the duplicate mobile_storage and supply_chain raw findings. CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:L/I:H/A:N.

ALI-02

Two Bitbucket app passwords committed in cleartext in README and AppDelegate.swift

P0 A07:2021 / M1 CWE-798 CWE-540 CVSS 8.7 confirmed
Description

Two Bitbucket app passwords are hardcoded in the repository — in README and again as comments at the top of AppDelegate.swift (which ships in source archives). Bitbucket app passwords are user-scoped credentials granting API/git access to every repository the owning account can reach across the ParhamandCo (and historically Qubicle) workspace. The credential protecting the source tree is stored inside the source tree.

Evidence
/Users/2dot4/Projects/alist-projects/a-list-ios/README:10-12 — '//BitBucket App password:' then 'MpugS4rWnqX65pFTV8KU,' and 'C4N67XcyQARpCaE22Dpy' /Users/2dot4/Projects/alist-projects/a-list-ios/A-List/AppDelegate.swift:18-20 — '//BitBucket App password' / '//MpugS4rWnqX65pFTV8KU' / '//C4N67XcyQARpCaE22Dpy' (committed source comment) Both are 20-char Bitbucket-app-password-format strings; repo origin git@bitbucket.org:ParhamandCo/a-list-ios.git; README git-tracked in commits 7f7d314, 0099130, 4fdd7a3
Attack scenario
An attacker reads one of the two app passwords from the repo, authenticates to the Bitbucket API as the credential owner, clones all accessible repos (source theft), then plants a malicious commit to poison a future build and pivots into other A-List/Qubicle codebases.
Impact

Anyone with repo read access (or a historical clone/mirror, or unpacked dSYM/source archive) can authenticate to Bitbucket as the credential owner: source theft, malicious commits poisoning the build pipeline of the iOS app and ~12 other A-List repos, and lateral movement across the workspace. Scope=Changed: access extends to systems beyond this app.

Remediation
1) Revoke BOTH app passwords in Bitbucket immediately and audit access/commit logs for misuse. 2) Remove from README and AppDelegate.swift and purge from full git history on Bitbucket and the GitLab mirror. 3) Replace human-credential git access with repository-scoped access tokens / deploy keys / OIDC for CI, stored in a secrets manager. 4) Add gitleaks rules for Bitbucket app-password patterns and enforce pre-commit + CI gates.
Verifier note

Confirmed in both files. Merged the duplicate mobile_storage and supply_chain raw findings. CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:L/A:N.

ALI-03

Login/signup OTP returned in the API response body and harvested+auto-filled client-side — second factor neutralized

P0 API2:2023 / M3 CWE-287 CWE-204 CWE-640 CVSS 8.1 confirmed
Description

The backend returns the one-time login/signup passcode inside the response JSON, and APIManager.validateResponse unconditionally harvests it into the Common.shared.otp singleton on EVERY response (regardless of build flag). The OTP screens auto-populate it when CAN_AUTO_FILL_OTP is true (Dev/pre-prod) and under #if DEBUG. An OTP delivered in the same channel as the request is not a second factor — anyone who can observe the login response obtains the code directly. Combined with ATS-disabled + no pinning (ALI-04), the response is trivially observable on a hostile network. Verification requires only an enumerable user_id plus the otp.

Evidence
/Users/2dot4/.../A-List/Networking/Base Layer/APIManager.swift:114-118 — EVERY response is scanned: `if let otp = responseJSON["otp"] { Common.shared.otp = "\(otp)" } else { Common.shared.otp = "" }` — the server returns the OTP in login/signup response JSON /Users/2dot4/.../Views/Login/LoginOTPViewController.swift:127-135 — `if Configuration.canFillOTP { if Common.shared.otp != "" { self.otp = Common.shared.otp; self.otp_panel.handlePastedContent(self.otp); ... } }`; lines 137-145 a separate `#if DEBUG` block also auto-fills /Users/2dot4/.../Views/Register/VerifyEmailViewController.swift — same server-OTP auto-fill on signup email verification /Users/2dot4/.../Configuration/Development.xcconfig:28 `CAN_AUTO_FILL_OTP = true` vs Production.xcconfig:17 `= false` verifyLoginOTPAPI (LoginOTPViewController.swift:178-208) sends only `user_id` (enumerable int) + `otp` to login/verify-otpv2; OTP cached in Common.swift:24 `var otp : String = ""`
Attack scenario
Attacker on rogue Wi-Fi MITMs the (unpinned, ATS-disabled) channel, submits/observes the victim's email to login/with-otp, reads the otp field from the JSON response, posts user_id+otp to login/verify-otpv2, and is authenticated as the victim without ever touching the victim's inbox.
Impact

Authentication bypass / account takeover. A network-position attacker who triggers or observes a victim's login response reads the OTP off the wire and completes login as the victim against api.alist.ae. Affects all creator accounts. The OTP factor provides zero added security because it round-trips in the response body. The cached OTP also leaks via the response-logging path.

Remediation
Server: stop returning OTP/PIN in any API response; deliver strictly out-of-band (email/SMS). Client: remove the responseJSON["otp"] harvesting (APIManager.swift:114-117) and all Common.shared.otp auto-fill paths from shipped code (not just build-flag gated). Set CAN_AUTO_FILL_OTP=false in every shipped config and strip the #if DEBUG autofill. Bind OTP verification to a server-issued non-enumerable challenge/session id rather than user_id; rate-limit and expire OTPs. Pair with re-enabled ATS + pinning.
Verifier note

Confirmed in all cited files. Merged the mobile_storage P2 and mobile_api_auth P0 raw findings. AC:H reflects the realistic prerequisite of observing the response (network position); the in-channel echo itself is the core defect. Kept P0 due to full ATO with no inbox access. CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:N.

ALI-04

App Transport Security fully disabled (NSAllowsArbitraryLoads=true) and no certificate/public-key pinning on the API client

P1 M5 / A02:2021 CWE-319 CWE-295 CVSS 7.4 confirmed
Description

ATS is globally disabled with no compensating exception scoping, and the single networking choke-point uses Alamofire's default Session with no ServerTrustManager / pinning and no URLSession challenge handler. Together these remove every OS-level transport guarantee: iOS will permit plain-HTTP (e.g. an attacker-injected redirect to http://), TLS downgrade to 1.0/1.1 or non-FS ciphers, and acceptance of any certificate chained to a trusted root — including an attacker CA installed on a target device. This matches the standard Burp mobile-interception workflow succeeding with no pinning bypass needed.

Evidence
/Users/2dot4/.../A-List/Info.plist:39-43 — NSAppTransportSecurity → NSAllowsArbitraryLoads=true, with NO NSExceptionDomains/MinimumTLSVersion scoping (global override) /Users/2dot4/.../Networking/Base Layer/APIManager.swift:14 — `private static var alamofireManager = Alamofire.Session.default` (serverTrustManager: nil); image upload at :45 uses AF.upload (also Session.default) grep for ServerTrustManager|PinnedCertificates|PublicKeysTrust|URLAuthenticationChallenge across A-List/ (excl Pods) returns ZERO matches; no bundled .cer/.der pins Bearer attached on every request: APIConstants.swift:13-14 `authToken = "Bearer \(Session.userDetail?.token ?? "")"`; base URLs are HTTPS (Production.xcconfig:14 https://api.alist.ae/) but ATS-off removes OS enforcement
Attack scenario
An attacker on the same Wi-Fi presents a certificate from a CA installed on the target device (or downgrades to cleartext); the app accepts it silently, the attacker reads the Bearer token and OTP off the wire and replays the token indefinitely (no server revocation — see ALI-07).
Impact

On a hostile network (rogue Wi-Fi, malicious proxy, ARP/DNS spoof, attacker CA on a managed/jailbroken device) an attacker transparently MITMs all API traffic with no client-side error, capturing the Bearer session token (full account takeover), creator PII and offer/redemption data, and tampering with responses. This is the enabling primitive for the OTP-in-response ATO (ALI-03) and the WebView open-redirect (ALI-08).

Remediation
Remove NSAllowsArbitraryLoads so default ATS (TLS 1.2+, forward secrecy, valid chain, no cleartext) is enforced; if a single host genuinely needs an exception, scope it narrowly under NSExceptionDomains with NSExceptionMinimumTLSVersion=TLSv1.2 and NSExceptionAllowsInsecureHTTPLoads=false. Configure a dedicated Alamofire Session with a ServerTrustManager pinning the public keys of api.alist.ae and dev-api.alist.ae (PublicKeysTrustEvaluator, with a backup pin) and inject it into APIManager instead of Session.default. Add CI verification that pinning is active.
Verifier note

Confirmed: Info.plist, APIManager Session.default, and zero pinning code. Merged three overlapping raw findings (mobile_transport ATS P1, mobile_transport pinning P1, mobile_api_auth ATS+pinning P1) into one combined transport finding. AV:A/AC:H reflects adjacent-network position requirement. CVSS:3.1/AV:A/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:N.

ALI-06

Auth bearer token (and PIN/PII) persisted unencrypted in UserDefaults; Keychain mirror lacks accessibility/device-only attribute

P1 M9 / API2:2023 CWE-312 CWE-522 CWE-311 CVSS 6.2 confirmed
Description

On login the full UserModel — including the API bearer token, PIN/confirmPin, email, phone, DOB, nationality and social handles — is encoded via PropertyListEncoder into UserDefaults under key 'User'. The line that would null the token before persistence is disabled. UserDefaults is an unencrypted plist in the app sandbox, captured in unencrypted iTunes/iCloud backups. The request layer reads the token from this UserDefaults copy, not the Keychain. The parallel Keychain entry sets no kSecAttrAccessible, so it too is backup/migration-eligible and keyed by the user's mutable email.

Evidence
/Users/2dot4/.../Utilities/Session.swift:77-84 — `serverToken = userValue.token`; the strip line `//userValue.token = nil` is COMMENTED OUT; `user = userValue`; `UserDefaults.standard.set(try? PropertyListEncoder().encode(user), forKey: ValueKey.User)` — full UserModel incl token written to plist /Users/2dot4/.../Modals/UserModel.swift:15,20-21 — `token`, `pin`, `confirmPin` are Codable; CodingKeys (line 113) include token, pin, confirmPin — all persist to UserDefaults along with email/dob/nationality/socials /Users/2dot4/.../Networking/Base Layer/APIConstants.swift:13-14 — request token read from `Session.userDetail?.token` (the UserDefaults copy), so Keychain copy is decorative for request auth /Users/2dot4/.../Utilities/Keychain.swift:22-26 — SecItemAdd has only kSecClass/kSecAttrAccount/kSecValueData; NO kSecAttrAccessible (defaults to backup/migration-eligible); account key = mutable email (line 24)
Attack scenario
A device is lost while unlocked, or an unencrypted iTunes backup is obtained; the attacker reads Library/Preferences/<bundle>.plist, extracts the Bearer token and PIN, and authenticates to api.alist.ae as the victim with no way for the user to revoke.
Impact

An attacker with local access (jailbreak, lost/unlocked device, forensic extraction of an unencrypted iTunes backup) reads the live bearer token from the plaintext plist and fully impersonates the user, plus a PII trove (PIN, email, phone, DOB) at rest. Because there is no server-side revocation (ALI-07), a recovered token is replayable indefinitely.

Remediation
Restore the strip line so token (and pin/confirmPin) are removed from the object persisted to UserDefaults; read the token exclusively from Keychain. Add kSecAttrAccessibleWhenUnlockedThisDeviceOnly (and SecAccessControl/biometrics where appropriate) to the Keychain item so it cannot be backed up/migrated. Key the Keychain entry on a stable identifier, not the mutable email. Do not persist PIN material on-device; encrypt any required at-rest PII with a Keychain-held key.
Verifier note

Confirmed across Session.swift, UserModel.swift, APIConstants.swift, Keychain.swift. Merged the UserDefaults P1, the Keychain-accessibility P2, and the mobile_api_auth dual-storage P2 raw findings into one credential-storage finding (the Keychain weakness is the same secret's secondary store). CVSS:3.1/AV:L/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N.

ALI-07

Opaque session token never expires and has no server-side revocation; logout is local-only (no /logout or /refresh)

P1 API2:2023 / M3 CWE-613 CWE-522 CVSS 6.5 confirmed
Description

Authentication uses an opaque server token stored in Session.userDetail.token. The app never parses an expiry, there is no refresh mechanism, and there is no logout or revoke endpoint. Logout — and even account deletion — clear only the local UserDefaults/Keychain copies, so a token captured via MITM (ALI-04), device backup (ALI-06), or logs (ALI-05) remains valid on the server indefinitely. The user cannot invalidate a stolen token.

Evidence
/Users/2dot4/.../Networking/Base Layer/APIConstants.swift — endpoint catalog (lines 35-121) contains NO logout or token-refresh route; grep for logout|/refresh|revoke in A-List/Networking returns nothing /Users/2dot4/.../Utilities/Session.swift:195-202 — `clear()` only nils local state (userDetail/selectedAddress/userAddress) /Users/2dot4/.../Utilities/CCUtility.swift:261-262 — `clearAllDataAfterLogout()` calls Session.clear() plus a UserDefaults flag; no network call /Users/2dot4/.../Modals/UserModel.swift:15 — token is an opaque server string; no exp/JWT-claims parsing anywhere in the app
Attack scenario
An attacker captures a Bearer token once (via MITM or a device backup); the victim later 'logs out', but the token is never revoked server-side, so the attacker continues to access the account for months.
Impact

Any leaked/stolen token is replayable for an unbounded lifetime. Logout gives a false sense of security — the token the user believes invalidated is still accepted by the backend. A single interception becomes long-lived persistent access. PR:L reflects that an attacker needs a (any) valid captured token to exploit.

Remediation
Implement a server-side /auth/logout that revokes the presented token, called on logout and account deletion. Move to short-lived access tokens with rotating refresh tokens or a server-side revocation list. Enforce server-side token expiry and invalidate all tokens on PIN/password change.
Verifier note

Confirmed: no logout/refresh endpoint, local-only clear(). Largely a backend/design issue surfaced by the client; CVSS C:H/I:L reflects read-heavy account access with limited integrity impact on its own. CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:L/A:N.

ALI-09

Untrusted single-developer personal fork of Siren pinned to a mutable git commit; runs forced-update logic at every launch

P1 A08:2021 / M2 CWE-1357 CWE-494 CVSS 7.5 likely
Description

Instead of the canonical CocoaPods-trunk Siren pod, the app pulls Siren from a personal GitHub fork by git URL. CocoaPods git dependencies lack trunk's published-spec checksum verification; integrity rests solely on the commit hash in Podfile.lock, and the fork is controlled by an individual developer account on infra with no stated branch protection. If the account is compromised or the commit/branch is force-pushed, malicious Swift can be injected on the next pod install/update. Siren executes at every launch and controls the forced-update modal, giving injected code a guaranteed launch-time, all-users execution path.

Evidence
/Users/2dot4/.../Podfile:32 — `pod 'Siren', :git => 'https://github.com/qubicle-aswinrnath/Siren-Alist.git'` /Users/2dot4/.../Podfile.lock:228-234 — EXTERNAL SOURCES/CHECKOUT OPTIONS Siren :git qubicle-aswinrnath/Siren-Alist.git :commit e52f7ef1e2e6e901e70e9a944b57e5f17e8cde49 (personal account, not upstream ArtSabintsev/Siren) Pods/Siren/README.md:11 — upstream maintainer states 'I stopped being a proactive iOS engineer in 2021' /Users/2dot4/.../AppDelegate.swift:62-106 — configureSiren(): majorRules and minorRules both forAlertType:.force, promptFrequency:.immediately, showAlertAfterCurrentVersionHasBeenReleasedForDays:0; `siren.wail()` at every launch (line 106)
Attack scenario
An attacker compromises the qubicle-aswinrnath GitHub account, force-pushes a poisoned commit to Siren-Alist; on the next pod update the malicious Siren is built into a signed release and executes at launch for all users, redirecting them via the force-update modal to an attacker-controlled 'update' page.
Impact

Compromise of the personal fork (or a malicious maintainer commit) ships arbitrary code into the next signed build that runs at launch for every user and controls the forced-update UX — a software-supply-chain backdoor. The forced-update flow also enables fake 'Update Now' social engineering redirecting users to attacker destinations.

Remediation
Switch back to upstream ArtSabintsev/Siren via the verified CocoaPods trunk spec with an explicit version constraint, or vendor an audited copy. If a fork is required, host it under an org with branch protection / no force-push, require signed commits, pin to a reviewed immutable tag, and re-audit periodically. Add the dependency to CI dependency review.
Verifier note

Confirmed Podfile/Podfile.lock fork+commit pin and the launch-time force-update config. Confidence 'likely' (not confirmed) because exploitation requires future fork compromise/force-push; the insecure posture itself is confirmed. CVSS S:C/C-I-A:H reflects worst-case arbitrary-code-at-launch if realized; AC:H for the precondition. CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:C/C:H/I:H/A:H.

ALI-05

Bearer auth token and full request/response data written to device logs via debugPrint in Release builds

P2 M9 / A09:2021 CWE-532 CWE-312 CVSS 4.0 confirmed
Description

Every outbound request logs its full HTTPHeaders via debugPrint at APIManager.swift:40, and those headers include Authorization: Bearer <token>. Unlike print (which the app shadows to a no-op in Common.swift), debugPrint is never overridden, so it emits to the unified logging system (os_log/syslog) in production. On-device logs are readable via Console.app while plugged in, sysdiagnose bundles, MDM log collection, and support/telemetry pipelines.

Evidence
/Users/2dot4/.../Networking/Base Layer/APIManager.swift:38-40 — `debugPrint("Request URL: ...")`, `debugPrint("Parameters: ...")`, `debugPrint("Headers: \(headers)")`; headers include `Authorization: Bearer <token>` /Users/2dot4/.../Utilities/Common.swift:11-18 — `print(_:)` is overridden to a no-op (the DEBUG body is even commented out), but there is NO `debugPrint` override anywhere — so debugPrint runs the real Foundation impl in ALL configurations including Release Authorization header value = API.authToken (APIConstants.swift:13-14), used in NotificationAPI/HistoryAPI/ProfileAPI/UserRegistrationAPI headers
Attack scenario
A support engineer collects a sysdiagnose from a user's device for troubleshooting; the bundle contains the live Bearer token in os_log, which is later exfiltrated or mishandled and replayed to impersonate the user.
Impact

The live session Bearer token plus full request URLs/parameters are written to the device log stream in production builds. Anyone who can pull a sysdiagnose, attach Console, or read captured logs recovers a token that fully impersonates the user against api.alist.ae (and, given no server revocation per ALI-07, indefinitely).

Remediation
Gate the URL/parameter/header debugPrint calls behind #if DEBUG and redact the Authorization header before any logging. Add a logging wrapper that strips secrets. Audit Crashlytics breadcrumbs to ensure tokens are not captured. Consider defining a no-op debugPrint shadow like the existing print shadow for defense in depth.
Verifier note

Confirmed: the print override is a no-op AND its body is commented out; debugPrint is not overridden. AC:H reflects log-capture access requirement. CVSS:3.1/AV:L/AC:H/PR:N/UI:N/S:U/C:H/I:N/A:N.

ALI-08

WebView decidePolicyFor blindly forwards any cross-URL navigation to UIApplication.open over an unpinned, ATS-disabled channel (open redirect / URL-scheme launch)

P2 M4 / A05:2021 CWE-939 CWE-601 CWE-319 CVSS 5.7 confirmed
Description

The Terms and Privacy WebViews load a server-controlled URL (WebApiModel.data) with no scheme/host validation, then delegate any in-page navigation whose URL differs from the loaded document to UIApplication.shared.open() with no scheme allow-list. Because ATS is off and the API channel is unpinned (ALI-04), an on-path attacker can serve a MITM'd/plaintext page and inject markup/JS that triggers navigation to arbitrary schemes (tel:, sms:, mailto:, itms-apps://, shortcuts://, or any installed-app custom scheme), which the handler forwards straight into the iOS URL dispatcher — escaping the WebView into other apps. JS is enabled by default, so injected script can also run in the in-app chrome.

Evidence
/Users/2dot4/.../Views/Terms/TermsAndConditionsVC.swift:75-82 — decidePolicyFor: `if let dataUrl = dataUrl, let newUrl = navigationAction.request.url, dataUrl != newUrl, UIApplication.shared.canOpenURL(newUrl) { UIApplication.shared.open(newUrl, options: [:], completionHandler: nil); decisionHandler(.cancel); return }` /Users/2dot4/.../Views/Privacy /PrivacyPolicyVC.swift:82-89 — identical pattern /Users/2dot4/.../Views/Terms/TermsAndConditionsVC.swift:53 & Privacy/PrivacyPolicyVC.swift:54 — initial WebView URL is server-supplied (WebApiModel.data) loaded verbatim with no https/host allow-list Info.plist:39-43 ATS disabled + no pinning (ALI-04) — the page source is MITM-able; WKWebView uses default config (JavaScript enabled)
Attack scenario
On rogue Wi-Fi the attacker MITMs the Terms response, returns an http page with injected JS that navigates to a malicious itms-apps:// or custom-scheme URL; the WebView handler forwards it to UIApplication.open, launching an attacker-chosen app/flow without user awareness beyond opening Terms.
Impact

A MITM attacker can cause the app to silently launch arbitrary URL schemes (place calls, pre-fill SMS, open App Store pages, deep-link into other apps performing sensitive actions) and run arbitrary JavaScript inside the trusted in-app WebView, enabling phishing/content-spoofing within the app's chrome. Scope=Changed: control crosses from the WebView into other apps / the OS URL dispatcher. Realistic only once the channel is MITM'd (depends on ALI-04).

Remediation
Remove the blanket UIApplication.shared.open forwarding; only allow http(s) navigations to the trusted alist.ae origin (present external links in SFSafariViewController or block). Maintain an explicit https-only scheme allow-list before canOpenURL/open. Validate the initial WebApiModel.data URL (scheme==https, host in alist.ae allow-list). Fix ALI-04 so the page cannot be MITM'd. Set an explicit WKWebViewConfiguration disabling JavaScript for static legal content and disabling file access.
Verifier note

Confirmed in both VCs. Merged the two raw transport WebView findings (cleartext-load P3 and decidePolicyFor open-redirect P1) — same code paths. Net P2: real, but gated behind achieving MITM (ALI-04) and limited to static legal screens; CVSS reflects S:C with low C/I. CVSS:3.1/AV:A/AC:H/PR:N/UI:R/S:C/C:L/I:L/A:N.

ALI-14

No CI/CD, secret-scanning, SCA, or SBOM controls anywhere in the project

P2 A08:2021 / A06:2021 / M2 CWE-1104 CWE-1395 CVSS 5.3 confirmed
Description

There is no CI/CD pipeline and no automated security gating. Builds and App Store/TestFlight uploads are manual in Xcode. No SCA (Snyk/Dependency-Check), no SBOM generation, no Dependabot, no secret scanning, and no enforced provenance/integrity check on the git/vendored dependencies (Siren fork ALI-09, QRCode copy ALI-13). This systemic gap is the root cause that let the P0 secret leaks persist for years.

Evidence
Repo-wide: no .github/ (no GitHub Actions), no bitbucket-pipelines.yml, no .gitlab-ci.yml, no Jenkinsfile, no fastlane/ — the only YAML is A-List/QRCode/.spi.yml (Swift Package Index builder, not a pipeline) CLAUDE.md: 'No fastlane, no bitbucket-pipelines.yml, no GitHub Actions, no CI config of any kind. Builds and uploads are manual via Xcode' No .snyk policy, no Dependabot config, no SBOM, no dependency-scan step Empirically confirmed by the live committed APNS .p8 (ALI-01) and Bitbucket app passwords (ALI-02) that were never flagged
Attack scenario
A newly disclosed CVE in a bundled pod, or a tampered git/vendored dependency, reaches a signed App Store build with no automated control to stop it — exactly as the committed .p8 and app passwords went unflagged for years.
Impact

New CVEs in the bundled pods/transitive components and in the vendored/forked dependencies go undetected and unpatched; secrets can be committed without alerting; a tampered dependency can reach a signed build with no automated gate. Test coverage is also effectively zero (both test targets are template stubs).

Remediation
Stand up a CI pipeline (Bitbucket Pipelines / GitHub Actions with SHA-pinned actions and least-privilege tokens). Add: secret scanning (gitleaks/trufflehog) on push and full history; SCA + SBOM (CycloneDX via cyclonedx-cocoapods + Snyk or OWASP Dependency-Check) on Podfile.lock with fail-on-high; automated dependency-update PRs; provenance pinning checks for git/vendored deps. Store CI secrets in the platform secret store, never in repo.
Verifier note

Confirmed absence of all CI/security tooling. Merged the no-CI/SCA finding with the prior audit's 'zero test coverage / no CI' quality note. P2 systemic root-cause. CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:L/A:N.

ALI-10

resetPin builds its Authorization Bearer from a request-parameter token instead of the session token (parameter-driven auth header)

P3 API2:2023 / API5:2023 CWE-287 CWE-639 CVSS 4.8 likely
Description

Unlike every other authenticated endpoint (which derives Authorization from the live session via API.authToken), the resetPin request type builds its Authorization header from a token value carried inside the request parameter dictionary. The credential authorizing a security-sensitive PIN-reset is taken from caller-controlled input rather than the authenticated session. In normal flow the client populates this from a short-lived reset token (AccountViewModel.token), but the pattern means the operation's security hinges entirely on the server strictly binding that arbitrary token to the target account.

Evidence
/Users/2dot4/.../Networking/UserRegistrationAPI.swift:40-46 — `case .resetPin(let param): let headers: HTTPHeaders = ["Authorization": "Bearer \(param["token"] ?? "")", "Accept": "application/json"]` — unlike all other endpoints which use API.authToken /Users/2dot4/.../Networking/Base Layer/APIConstants.swift:40 — resetPin = 'signup/pin/reset' (PUT, UserRegistrationAPI.swift:89) /Users/2dot4/.../ViewModels/AccountViewModel.swift:28 — `var token: String?` exists as a flow-scoped token holder (the likely source of param["token"])
Attack scenario
An attacker who obtains any valid token (e.g. a captured reset token or a lower-privilege token) submits it as param["token"] to signup/pin/reset; if the backend trusts the supplied Bearer without binding it to the target account, the attacker resets the PIN for an account other than the token's owner.
Impact

Inconsistent, parameter-driven authentication for PIN reset enables token-confusion / authorization-bypass if the backend does not strictly bind the presented token to the authenticated subject, and broadens the blast radius of any leaked/lower-privilege token. Server-side validation cannot be confirmed from the client, so this is a latent design weakness rather than a proven bypass.

Remediation
Authorize resetPin with the standard authenticated session token (API.authToken) like all other endpoints; never let request parameters dictate the Authorization header. Ensure the server binds the PIN-reset operation to the authenticated subject of the presented token and re-verifies an out-of-band OTP before applying the reset.
Verifier note

Confirmed the client builds the header from param["token"]. Downgraded from the scanner's P2 to P3 and confidence to 'likely': there is no evidence the client value is attacker-controlled beyond any normal token, and exploitability depends on unverified server behavior. PR:L/AC:H reflect needing a valid token and server mis-binding. CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:L/I:L/A:N.

ALI-11

Pre-OTP account-status enumeration via distinct login response handling

P3 API2:2023 / API3:2023 CWE-204 CWE-203 CVSS 5.3 confirmed
Description

Submitting just an email to login/with-otp returns the account's existence and lifecycle status (approved/pending/rejected plus restricted_days) before any OTP challenge, and the client branches into different flows accordingly. The API discloses whether an email is a registered creator and its moderation state to an unauthenticated caller.

Evidence
/Users/2dot4/.../Views/Login/LoginViewController.swift:119-145 — after submitting only an email, the signInUser response branches on `_accountStatus`: 'approved' → push OTP screen; 'pending'/'rejected' → status screen with restrictedDays; else error /Users/2dot4/.../Modals/UserModel.swift:60-61 — accountStatus and restrictedDays are returned to the unauthenticated client at the email-submission step (before any OTP)
Attack scenario
An attacker scripts the email-submission endpoint over a list of candidate emails and records which return 'approved' vs error, building a verified list of active A-List creators for targeted phishing.
Impact

Unauthenticated attackers can enumerate valid creator emails and learn approval/rejection status, aiding targeted phishing, credential-stuffing list-building, and competitive intelligence on the invite-only creator base.

Remediation
Return a uniform response for the email-submission step regardless of account existence/status (e.g. always 'If an account exists, an OTP has been sent'). Defer disclosure of account status until after successful OTP verification, and rate-limit the email-submission endpoint.
Verifier note

Confirmed in LoginViewController and UserModel. Primarily a backend response-design issue observable via the client; rated P3. CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:N/A:N.

ALI-12

Sensitive verification PINs and voucher codes copied to the system-wide general pasteboard (no localOnly/expiration)

P3 M9 / MASVS-STORAGE-2 CWE-200 CVSS 3.1 confirmed
Description

Social-account verification PINs and offer voucher/redemption codes are written to UIPasteboard.general — the system-wide pasteboard readable by any other app while data persists, synced across devices via Universal Clipboard, and on older iOS persistent across reboots. These values are security-relevant: the social PIN is proof-of-ownership for a creator's Instagram/TikTok account, and the voucher code authorizes a venue redemption. No expiration or local-only restriction is set.

Evidence
/Users/2dot4/.../Views/SignUp/SignUpVC.swift:350 — `UIPasteboard.general.string = "\(_pin)"` (Instagram verification PIN) /Users/2dot4/.../Views/Register/VerifySocialViewController.swift:199 `UIPasteboard.general.string = instaPin`; :204 `= tiktokPin` /Users/2dot4/.../Custom Views/TableView Cells/Offers/VoucherDetailTVC.swift:255 `UIPasteboard.general.string = voucherName.text` (voucher redemption code) All use UIPasteboard.general with no expirationDate and no setItems(options:[.localOnly:true])
Attack scenario
A user copies their Instagram verification PIN to paste into Instagram; a background app polling UIPasteboard.general reads the PIN and uses it to complete the verification step impersonating the creator, or reads a copied voucher code and redeems it first.
Impact

A malicious or curious app on the device (or another of the user's devices via Universal Clipboard) can silently read social-verification PINs and voucher codes, enabling impersonation of the verification step or unauthorized voucher redemption. Confidentiality-only; requires the user to copy and an attacker app to be present.

Remediation
Use UIPasteboard.general.setItems([[UIPasteboard.typeAutomatic: value]], options:[.localOnly:true, .expirationDate:Date().addingTimeInterval(60)]) so values are not synced and auto-expire. Better, avoid the clipboard for one-time PINs (in-app display / direct share-sheet to the target app). Clear the pasteboard when leaving the screen.
Verifier note

Confirmed all four call sites. P3: confidentiality-only, requires a malicious app present and a user copy action. CVSS:3.1/AV:L/AC:H/PR:N/UI:R/S:U/C:L/I:N/A:N.

ALI-13

QRCode dependency vendored as raw third-party source with no version pin, lockfile, or update path

P3 A06:2021 / M2 CWE-1104 CWE-1395 CVSS 4.7 confirmed
Description

The dagronf/QRCode library is bundled by copying its entire source tree into A-List/QRCode/ rather than declared as a versioned dependency. With no version pin or lockfile entry there is no record of the upstream release/commit, no integrity/provenance check, and no path for tooling to surface upstream security fixes. QRCode parses untrusted input (scanned QR codes from venue/offer materials, plus embedded link/contact/SMS decoders), so a parser bug in a frozen, unpatchable copy is a realistic exposure.

Evidence
/Users/2dot4/.../A-List/QRCode/ holds a full vendored copy: Package.swift (swift-tools-version:5.4, name 'QRCode'), Sources/QRCode/ tree Source headers identify upstream dagronf/QRCode (Copyright (c) 2023 Darren Ford, MIT) Referenced via `import QRCode` (AppDelegate.swift:12) but NOT present in Podfile/Podfile.lock — copied source with no recorded upstream version and no automated update channel Attack-surface code present: QRCode+VideoDetector.swift (live AVCaptureVideoDataOutput QR detection) and link/message decoders, used in the offer/QR scan flow
Attack scenario
A QR-parsing CVE is later disclosed in dagronf/QRCode; because the app ships a frozen unpinned copy with no update channel, the fix never lands, and an attacker places a crafted QR code on venue offer materials that triggers the unpatched parser when a creator scans it.
Impact

The app may run an outdated, unpatchable QR-parsing library that processes attacker-influenceable QR payloads, with no mechanism to detect or remediate upstream vulnerabilities and no provenance verification (a tampered copy would go undetected).

Remediation
Replace the vendored copy with a pinned SPM/CocoaPods dependency on dagronf/QRCode at a specific released tag for reproducible, auditable updates. If vendoring is required, record the exact upstream commit/version in a manifest, document local diffs, and add it to periodic dependency review.
Verifier note

Confirmed vendored tree and absence from Podfile.lock. P3: today's confirmed-exploitable risk is low; concern is the unpatchable/unprovenanced trajectory. CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:L/I:L/A:L.

ALI-15

Globally stale dependency baseline (~3 years behind) across networking/SDK/image pods

P3 A06:2021 / M2 CWE-1104 CVSS 4.0 EPSS low confirmed
Description

Every significant dependency is pinned to a 2022-2023 release with no update mechanism (see ALI-14). No single pinned version maps to a confirmed critical, clearly-reachable published CVE in this app's usage at review time, so this is rated low/informational rather than a concrete exploit. The baseline is ~3 years behind across networking (Alamofire), analytics/push (Firebase/GoogleUtilities/nanopb), auth (GoogleSignIn/AppAuth), and two parallel image-decoding stacks that parse untrusted remote bytes.

Evidence
Podfile.lock pins (Manifest matches): Alamofire 5.6.4, Firebase 10.4.0 + GoogleUtilities 7.11.0 + GoogleDataTransport 9.2.1 + nanopb 2.30909.0, GoogleSignIn 7.1.0 + AppAuth 1.7.6 + GTMSessionFetcher 3.5.0, Kingfisher 7.7.0, SDWebImage 5.15.0, Intercom 14.0.6, lottie-ios 4.1.2, PhoneNumberKit 3.7.11, SwiftMessages 10.0.1, Charts 4.1.0 These are 2022-2023 releases (e.g. Alamofire 5.6.4 vs current 5.10.x; Firebase 10.4.0 vs 11.x; SDWebImage 5.15.0 vs 5.19+; GoogleSignIn 7.1.0 vs 8.x) Two overlapping image libraries (Kingfisher 7.7.0 AND SDWebImage 5.15.0) double the untrusted-image-decoding attack surface (CLAUDE.md gotcha #4)
Attack scenario
Over time a stale image decoder (Kingfisher/SDWebImage) acquires a newly disclosed reachable CVE; with no SCA monitoring the vulnerable component stays shipped and an attacker serving a malicious image to the app exploits it before anyone notices.
Impact

Increasing latent exposure to newly disclosed vulnerabilities in stale components (notably image decoders and the networking layer handling untrusted remote data), with no detection or patch path. Today's confirmed-exploitable risk is low; the trajectory and duplicate image-parser surface are the concern.

Remediation
Establish a quarterly dependency-bump cadence driven by SCA (Snyk/OWASP Dependency-Check on Podfile.lock) with EPSS/KEV prioritization. Bring Alamofire, Firebase, GoogleSignIn/AppAuth, Kingfisher and SDWebImage to current supported releases, and consolidate to a single image-loading library to halve that surface. Retain a CycloneDX SBOM for fast future CVE triage.
Verifier note

Confirmed pins against vendored Pods/. P3: no concrete reachable critical CVE confirmed at review; rated on trajectory + duplicate image stacks. No specific CVE assigned to avoid a false positive. CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:L/I:L/A:N.

ALI-16

Intercom SDK API key hardcoded in SceneDelegate.swift

P3 M1 / M9 CWE-798 CVSS 3.7 confirmed
Description

The Intercom mobile SDK API key (ios_sdk- prefix) and app id are hardcoded in source. Intercom iOS SDK keys are designed to ship in clients and are extractable from any IPA, so this is low-severity, but committing them in source and git history exposes them to repo readers and, if Intercom Identity Verification (HMAC) is not enforced, can permit identity spoofing of conversations / messaging-surface abuse.

Evidence
/Users/2dot4/.../A-List/SceneDelegate.swift:22 — `Intercom.setApiKey("ios_sdk-ed1d62a895abcc6d2185b49397efc2cdf64df762", forAppId:"uicijnyh")`
Attack scenario
An attacker extracts the ios_sdk- key from the IPA or repo and, absent HMAC identity verification, opens/spoofs Intercom conversations as arbitrary users to phish support or harvest workspace data.
Impact

If secure identity verification is not enabled in the Intercom workspace, the disclosed client key allows driving the Intercom API surface (e.g. create/spoof user conversations) and contributes to backend fingerprinting. Low because client SDK keys are extractable from any IPA by design.

Remediation
Enable Intercom Identity Verification (HMAC) so the SDK key alone cannot impersonate users. Move the key to an xcconfig/build setting injected at build time rather than literal source, and confirm it is the client SDK key (ios_sdk-) and not an access token. Treat as rotate-on-leak.
Verifier note

Confirmed at SceneDelegate.swift:22. Client SDK key; P3 by design. CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:N.

ALI-17

Firebase config (GoogleService-Info.plist) with Google API key committed to source control

P3 A05:2021 / M8 CWE-540 CWE-200 CVSS 2.3 confirmed
Description

The Firebase iOS config plist (with the Google API key, project id, sender id, app id) is committed and also ships inside the IPA — expected for Firebase iOS clients; the API key is not itself a high-value secret. The real risk is if the GCP API key lacks application (iOS bundle-id) and API restrictions, allowing reuse against billed Google APIs. Committing it (vs build-time injection) additionally leaves identifiers in source history/mirrors.

Evidence
`git ls-files` lists A-List/GoogleService-Info.plist as tracked; no gitignore rule covers it /Users/2dot4/.../A-List/GoogleService-Info.plist:9-10 API_KEY=AIzaSyC6d9WGadeTRazurF3pojWXH-SFFUHigk0; PROJECT_ID alist-application; GCM_SENDER_ID 614397009000; GOOGLE_APP_ID 1:614397009000:ios:6abcfb4217985d0082efdd Info.plist:11-12 also embeds GIDClientID for Google Sign-In
Attack scenario
An attacker reads the committed plist from a clone/mirror, extracts the API key and project id, and (if the key is unrestricted) abuses Firebase/GCP APIs tied to alist-application for quota/cost abuse or enumeration.
Impact

If the key lacks bundle-id/API restrictions, an attacker who extracts it (repo or IPA) could call enabled Google APIs against project alist-application, risking quota/billing abuse and enumeration. Real exploitability depends on key restrictions in the Google Cloud console (not verifiable via static review).

Remediation
Restrict the API key in Google Cloud to the iOS bundle id ae.alist.ios and only the Firebase/Identity APIs needed; enable Firebase App Check; lock down Firebase Security Rules so config disclosure alone grants no data access. Prefer injecting the plist at build time from a secrets store and gitignore it; rotate if ever used unrestricted.
Verifier note

Confirmed tracked + key contents. P3 informational; impact contingent on unverifiable cloud-side key restrictions. CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:N.

ALI-18

Pre-filled developer test email (DEBUG-gated) and ungated dummy email/PIN shipped in source

P3 M8 / M1 CWE-1188 CWE-798 CVSS 2.6 confirmed
Description

Developer test identities are embedded in source. The LoginViewController email prefill is correctly wrapped in #if DEBUG and stripped from Release. However the DummyData enum (test email rahulk2133@gmail.com and PIN 1234) in Constants.swift is NOT compile-gated and ships in every build, disclosing a developer email and a guessable default PIN that may map to a real test account.

Evidence
/Users/2dot4/.../A-List/Utilities/Constants.swift:11-13 — `enum DummyData { static let email = "rahulk2133@gmail.com"; static let pin = "1234" }` — NOT gated by #if DEBUG; compiled into every build /Users/2dot4/.../A-List/Views/Login/LoginViewController.swift:41-45 — `#if DEBUG email_text.text = "aswinrnath123@qubicle.ai" #endif` — this prefill IS DEBUG-gated (stripped from Release)
Attack scenario
An attacker runs strings on the shipped IPA, finds rahulk2133@gmail.com and PIN 1234, and tries that pair against the login/PIN flow on the chance the test account is still live in production.
Impact

Low: discloses a developer/test email and a weak default PIN that an attacker recovering strings from the IPA can try against the auth/PIN flow. Mostly information-disclosure/hygiene and an indicator of dev artifacts reaching production.

Remediation
Wrap the entire DummyData enum (and any test identities) in #if DEBUG or delete them. Add a CI check that fails a Release build if known test emails/PINs are present.
Verifier note

Confirmed: DummyData ungated, LoginVC prefill DEBUG-gated. P3 hygiene. CVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:L/I:N/A:N.

Remediation roadmap

Immediate
Rotate-before-refactor: treat every committed credential as compromised.
  • ALI-01: Revoke APNS key V63PAFH55F in the Apple Developer portal; issue a new key stored only on the push backend / secrets manager.
  • ALI-02: Revoke BOTH Bitbucket app passwords; audit Bitbucket access/commit logs for misuse.
  • ALI-03: Stop returning the OTP in any API response (server-side hotfix); set CAN_AUTO_FILL_OTP=false in every shipped config and remove the response-OTP harvesting.
  • ALI-16/ALI-17: Restrict the Firebase/GCP API key to bundle ae.alist.ios; enable Intercom HMAC identity verification.
This week
Close the transport and account-takeover chain; purge history.
  • ALI-04: Remove NSAllowsArbitraryLoads; add an Alamofire ServerTrustManager public-key pinning api.alist.ae / dev-api.alist.ae.
  • ALI-01/02: Purge the .p8 and app passwords from full git history (git-filter-repo/BFG) on Bitbucket and the GitLab mirror; add *.p8/AuthKey_* to .gitignore.
  • ALI-05: Gate URL/parameter/header debugPrint behind #if DEBUG and redact the Authorization header.
  • ALI-18: Wrap or delete the ungated DummyData test email/PIN.
This month
Fix credential storage, session lifecycle and the supply chain.
  • ALI-06: Restore the token-strip line so token/PIN are not persisted to UserDefaults; read token only from Keychain with kSecAttrAccessibleWhenUnlockedThisDeviceOnly.
  • ALI-07: Implement server-side /auth/logout + token expiry/revocation; rotating refresh tokens.
  • ALI-09: Move Siren back to upstream trunk (or an org-hosted, branch-protected, signed-commit, tag-pinned fork).
  • ALI-08: Remove blanket UIApplication.shared.open forwarding; https-only scheme allow-list and validated WebView origin.
  • ALI-14: Stand up CI (Bitbucket Pipelines / GitHub Actions) with gitleaks/trufflehog secret scanning + SCA/SBOM (CycloneDX + Snyk/Dependency-Check) fail-on-high.
Hardening
Reduce latent surface and prevent regression.
  • ALI-10: Authorize resetPin with the standard session token; never let request parameters set the Authorization header.
  • ALI-11: Return a uniform email-submission response and rate-limit the endpoint to stop creator enumeration.
  • ALI-12: Use localOnly+expiring pasteboard items (or avoid the clipboard) for PINs/voucher codes.
  • ALI-13/ALI-15: Pin/version the vendored QRCode source; quarterly SCA-driven dependency bumps with EPSS/KEV prioritization; consolidate to one image library.

Code quality

Run / deploy

Local setup

# Prereqs: macOS, Xcode 15+, CocoaPods 1.16.2, Ruby
# Pods/ is committed so you don't strictly need to run pod install. If you do:
pod install

# Open the workspace (NOT the .xcodeproj)
open A-List.xcworkspace

# Schemes:
#   A-List-Development   → base URL https://dev-api.alist.ae/, bundle id ae.alist.ios
#   A-List-Release       → base URL https://api.alist.ae/, bundle id ae.alist.ios (App Store)
#   A-List               → legacy, prefer the two above
#   Service              → exists; purpose unclear (no Service target obvious in pbxproj)

# Signing:
#   Team: ASR5MYB7XL (Apple)
#   Style: Manual
#   Profiles required in your local keychain:
#     - "Alist Dev"    (Debug Development, Release Development)
#     - "Alist-Store"  (Release Production)
#   Entitlements: Push (development for Dev, production for Release), associated domain applinks:alist.ae
#   Sign in with Apple capability is also enabled

Environment

Deployment hints

No CI/CD configured (no bitbucket-pipelines.yml, no fastlane/, no GitHub Actions). Releases go App Store → bundle id ae.alist.ios, current marketing version 1.1.09. Distribution is manual via Xcode (Product → Archive → Distribute App → App Store Connect). TestFlight is the implied beta channel. First step for any new release engineering work: add a Bitbucket Pipeline that runs xcodebuild test plus the secret-scanning/SCA gates from ALI-14, and a fastlane beta lane to upload to TestFlight from CI.

What to know before editing