diff --git a/docs/OFFICIAL_APP_PRIVACY.md b/docs/OFFICIAL_APP_PRIVACY.md index 7cca411..42e1aa7 100644 --- a/docs/OFFICIAL_APP_PRIVACY.md +++ b/docs/OFFICIAL_APP_PRIVACY.md @@ -4,111 +4,119 @@ (`com.ipsgroupinc.parksmarter`, version 4.4.0), and how **BigBrainParking** — this open-source alternative — is different.* -This was written by inspecting the official app's own files (its permission list and -its program code). It's meant to be readable by anyone, not just programmers. Where we're -**sure**, we say so; where we're making an educated guess, we say that too. +This was written by taking the official app apart: reading its program code, and +watching its real network traffic through a proxy. Where we're **sure**, we say +so; where something is situational, we say that too. (For the technical teardown +with diagrams and evidence, see +[OFFICIAL_APP_REVERSE_ENGINEERING.md](OFFICIAL_APP_REVERSE_ENGINEERING.md).) --- ## The short version -- **Your exact location is sent to ParkSmarter's servers whenever you look at the map** - in the official app — automatically, just by opening it, not only when you ask it to - "find parking near me." -- **The app also carries a stack of tracking tools** (Segment, Amplitude, Firebase, - Sentry) that quietly report what you do and your device's identity to outside companies. -- **It can read a permanent hardware ID from your phone and send it to ParkSmarter.** +- **Your exact location is sent to ParkSmarter's servers when you use the map** + to find meters — and the app is built to do this **automatically** when the map + opens, not only when you ask it to "find parking near me." +- **The app carries one heavy tracking tool — Instabug** — that can record your + screen, take screenshots, and log the app's network activity, reporting to an + outside company. +- **It sends a permanent per-device ID to ParkSmarter.** It is *not* your phone's + IMEI (despite the app calling the field `IMEINumber`) — it's Android's device + ID. Still a fixed code that identifies your specific phone to ParkSmarter. - **Good news:** it **cannot** track your location while the app is closed. -- **BigBrainParking removes the tracking entirely** and only sends your location when - *you* deliberately tap "My location" and search. +- **BigBrainParking removes the tracking entirely** and only sends your location + when *you* deliberately tap "My location" and search. --- ## First, why does this matter? -"Location data" is a record of where you physically are. On its own, one location isn't -a big deal. But a *stream* of them — collected quietly and often — reveals where you live, -work, worship, and who you visit. When that's combined with a permanent ID for your phone -and shared with advertising/analytics companies, it becomes a profile of you that can be -sold, leaked, or subpoenaed. So the questions that matter are: **who collects it, how -often, is it tied to your identity, and does it leave in ways you didn't ask for?** +"Location data" is a record of where you physically are. On its own, one location +isn't a big deal. But a *stream* of them — collected quietly and often — reveals +where you live, work, worship, and who you visit. Tie that to a permanent ID for +your phone and it becomes a profile of you that can be sold, leaked, or +subpoenaed. So the questions that matter are: **who collects it, how often, is it +tied to your identity, and does it leave in ways you didn't ask for?** --- ## When does the official app send your location to ParkSmarter? -**To ParkSmarter's own servers, your GPS is used for one thing: finding nearby meters.** -There are exactly two requests that carry your coordinates (finding meters near a point); -your location is **not** attached to logging in, starting a parking session, or paying. -That part is reasonable. +**To ParkSmarter's own servers, your GPS is used for one thing: finding nearby +meters.** We confirmed this by watching the traffic: the coordinates go out as a +`GET …/api/Meter?Lat=…&Long=…` request, and your location is **not** attached to +logging in, starting a session, or paying. That part is reasonable. -**The catch is *when* it happens.** The official app is built to grab your live GPS and -ask ParkSmarter "what meters are near me?" **automatically when you open the map** — you -don't have to tap anything. So simply browsing the app quietly sends your precise location -to ParkSmarter's servers. +**The catch is *when*.** The app is built to grab your live GPS and ask "what +meters are near me?" **automatically when the map opens** — no tap required — as +long as you're logged in and have granted location permission. So simply browsing +the map quietly sends your precise location to ParkSmarter. -> **How sure are we?** *Fairly sure.* The code clearly reads your current GPS and has a -> "nearby meters" feature wired to the map. Confirming the exact moment it fires would take -> watching the app's live network traffic, which we haven't done yet. +> **One nuance we saw firsthand:** on a de-Googled phone (GrapheneOS), the map +> itself doesn't draw (it needs Google services), which can suppress that +> automatic send. On a normal Google phone the auto-send happens as designed. -**BigBrainParking does the opposite on purpose:** it opens on your **last parking lot** -(from your own history, not your GPS), and it only sends your location when you *explicitly* -tap **"My location"** and then search. +**BigBrainParking does the opposite on purpose:** it opens on your **last parking +lot** (from your own history, not your GPS), and it only sends your location when +you *explicitly* tap **"My location"** and then search. --- ## The bigger privacy concerns -These are things the official app *can* do beyond the parking API itself. They're taken -from the app's official permission list and the tracking libraries built into it. +### 1. A heavy tracking tool: Instabug -### 1. Hidden tracking/analytics companies (the biggest one) +The app bundles **Instabug**, a monitoring/bug-reporting toolkit — and it's the +serious one. It's capable of: -The app bundles four separate tracking/telemetry toolkits: +- **recording your session / replaying what you did**, +- **taking screenshots** of the app, +- **logging the app's network requests**, +- **tracking your taps and the steps you take** through the app. -- **Segment** — a "customer data platform." Its whole job is to collect what you do in the - app and fan it out to many other companies. -- **Amplitude** — product-analytics; records your actions to study behavior. -- **Firebase** (Google) and **Sentry** — Google's analytics/crash tools and an error tracker. +All of this reports to an outside company's servers (`api.instabug.com`). We saw +it phone home the moment the app started. -These typically collect: **which screens you visit and when, your device model, and your -IP address** (which reveals your rough city/neighborhood), and often an **advertising ID** -(a code used to follow you across different apps). Even without your exact GPS, this is a -detailed behavioral profile shared with outside companies. +> **What it is *not*:** we specifically checked and the app does **not** contain +> Segment, Amplitude, Sentry, Google Analytics/Firebase-Analytics, an advertising +> ID, or Play Store install-tracking. (An earlier version of this note listed +> those — that was wrong; Instabug is the actual story.) Google is present only +> for **push notifications**. -> **BigBrainParking has none of these.** No Segment, no Amplitude, no Firebase, no Sentry, -> no ad-ID, no Google services at all. +> **BigBrainParking has none of this.** No Instabug, no analytics, no ad-ID, no +> Google services at all. -### 2. A permanent hardware ID sent to ParkSmarter +### 2. A permanent device ID sent to ParkSmarter -The app is allowed to **read phone/device identifiers** (the "read phone state" -permission), and it has a feature that sends your device's **IMEI number** — a permanent -serial number for your phone — to ParkSmarter. Unlike a password you can change, this ID -is fixed, so it can be used to recognize your specific phone forever. +When you log in, the app sends ParkSmarter a fixed identifier for your phone, +inside a request to `…/api/Device`. The field is **named `IMEINumber`**, which is +misleading — **it is not your IMEI**. Modern Android forbids apps from reading the +IMEI at all, and this app never tries. The value it actually sends is your +phone's **Android device ID** (a code assigned to your device). Unlike a password, +it doesn't change, so it can be used to recognize your specific phone over time. +It goes only to ParkSmarter, not to a third party. -> **BigBrainParking never reads or sends this.** +> **BigBrainParking never reads or sends any device ID.** ### 3. Other capabilities worth knowing about -- **Install tracking** — records how you found/installed the app (via the Google Play - "install referrer"), tied to your Play account. -- **Wi-Fi scanning permission** — can see the Wi-Fi networks around you, which can be used - to *estimate your location even without GPS*. -- **Microphone permission** — unusual for a parking app. It may be an unused leftover from - a library, but the app is allowed to access your microphone. -- **Google push messaging (FCM)** — notifications are routed through Google, which links - your device to Google and to ParkSmarter's messaging system. +- **Microphone & camera permissions** — camera is for scanning kiosk QR codes; + microphone is unusual for a parking app (may be an unused library leftover). +- **Wi-Fi & phone-state access** — used by a networking library to report your + connection type and carrier name; it does **not** read your IMEI. +- **Bluetooth** — for talking to parking meters directly. +- **Google push messaging (FCM)** — notifications route through Google. --- ## To be fair — what it does *not* do -- **No background location tracking.** This is important: the app can only see your - location **while you have it open and are using it**. It has the everyday location - permission but **not** the special "all the time / in the background" one, so it can't - follow you around when the app is closed. -- Your location going to ParkSmarter is genuinely limited to the "find nearby meters" - feature — it isn't stapled onto payments or your account details. +- **No background location tracking.** The app has the everyday location + permission but **not** the "all the time / background" one, so it can't follow + you when it's closed. +- **No IMEI, no advertising ID, no Segment/Amplitude/Sentry.** +- Your location going to ParkSmarter is genuinely limited to the "find nearby + meters" feature — it isn't stapled onto payments or your account details. --- @@ -116,12 +124,13 @@ is fixed, so it can be used to recognize your specific phone forever. | | Official ParkSmarter app | BigBrainParking | | --- | --- | --- | -| Sends GPS to ParkSmarter | **Automatically** when you open the map | **Only** when you tap "My location" and search | +| Sends GPS to ParkSmarter | **Automatically** when the map opens | **Only** when you tap "My location" and search | | Opens the map on… | Your current GPS location | Your **last parking lot** (not your GPS) | -| Hidden analytics/tracking SDKs | Segment, Amplitude, Firebase, Sentry | **None** | -| Advertising ID / cross-app tracking | Likely collected | **Never** | -| Sends a permanent device ID (IMEI) | Yes (feature exists) | **Never** | -| Google services required | Yes (Firebase, FCM) | **None** (runs on GrapheneOS) | +| Third-party tracking SDKs | **Instabug** (session replay, screenshots, network logs) | **None** | +| Advertising ID / cross-app tracking | **No** (none present) | **Never** | +| Sends a permanent device ID | **Yes** — Android device ID, sent as `IMEINumber` | **Never** | +| Reads your hardware IMEI | **No** (not possible on modern Android) | **Never** | +| Google services required | Yes (push notifications) | **None** (runs on GrapheneOS) | | Background location | No | No | | Source code you can inspect | No (closed) | **Yes** (this repo) | @@ -129,18 +138,19 @@ is fixed, so it can be used to recognize your specific phone forever. ## How we checked (for the curious) -- **Permissions** come from the app's own `AndroidManifest` (the official list Android - shows). Confirmed present: precise + approximate location, read-phone-state, Wi-Fi - state, microphone, camera, Google push, install-referrer. Confirmed **absent**: - background location. -- **Tracking libraries** and the **location-bearing requests** (`getMetersByLocation` / - `getLimitedMetersByLocation`) and the **IMEI-sending** device-registration request come - from reading the app's decompiled program code (version 4.4.0). -- **Educated guesses** (clearly labeled above) are the auto-send-on-open behavior and the - advertising-ID collection — code strongly suggests them, but a live network capture would - make them certain. We can do that capture if you want proof. +- **Permissions** come from the app's own `AndroidManifest`. Confirmed present: + precise + approximate location, read-phone-state, Wi-Fi, microphone, camera, + Bluetooth, Google push. Confirmed **absent**: background location. +- **The device-ID and location behavior** were read from the app's decompiled + code (it's a React Native / Expo app, so the logic is in a JavaScript bundle) + **and confirmed on the wire** with a live proxy capture: the `…/api/Device` + request carrying the Android ID as `IMEINumber`, and the `…/api/Meter?Lat=…` + request carrying GPS. +- **The SDK list** (Instabug present; Segment/Amplitude/Sentry/Firebase-Analytics/ + ad-ID absent) comes from searching the app's bundled code and its network + endpoints. -*Not affiliated with or endorsed by IPS Group / ParkSmarter. This is an independent, -good-faith analysis of a publicly distributed app for the purpose of building a -privacy-respecting alternative. Findings reflect version 4.4.0 and could change in later -versions.* +*Not affiliated with or endorsed by IPS Group / ParkSmarter. This is an +independent, good-faith analysis of a publicly distributed app for the purpose of +building a privacy-respecting alternative. Findings reflect version 4.4.0 and +could change in later versions.* diff --git a/docs/OFFICIAL_APP_REVERSE_ENGINEERING.md b/docs/OFFICIAL_APP_REVERSE_ENGINEERING.md new file mode 100644 index 0000000..d23964c --- /dev/null +++ b/docs/OFFICIAL_APP_REVERSE_ENGINEERING.md @@ -0,0 +1,277 @@ +# Reverse-engineering the official ParkSmarter app + +A technical teardown of the official **Park Smarter** Android app +(`com.ipsgroupinc.parksmarter`, **v4.4.0**, versionCode 170), focused on **what +data leaves the device, when, and to whom** — with special attention to the +**device identifier** and **GPS location**. Combines static decompilation with a +live man-in-the-middle capture. + +> Independent, good-faith security/privacy research on a publicly distributed +> app, for the purpose of building a privacy-respecting alternative +> (BigBrainParking). Not affiliated with or endorsed by IPS Group. Findings +> reflect v4.4.0. The full decompiled source is kept in a **private** mirror; this +> report quotes only the excerpts needed as evidence. + +--- + +## TL;DR + +| Claim | Verdict | Evidence | +|---|---|---| +| The app reads your hardware **IMEI** | **False** | No `TelephonyManager.getImei/getDeviceId` anywhere; `react-native-device-info` isn't even bundled; and Android 10+ blocks IMEI for normal apps regardless. | +| The app sends a **persistent device ID** to its backend | **True** | `PUT /api/Device` carries the **Android SSAID** in a field misleadingly named `IMEINumber`, plus the push token as `DeviceID`. | +| The app sends your **GPS** to its servers | **True**, for meter search only | `GET /api/Meter?Lat=…&Long=…`; captured live. Not attached to login/session/payment. | +| GPS is sent **automatically** on map open | **True in code**, situational at runtime | Wired to map-ready when authenticated + permission granted; on GrapheneOS the map fails to render (needs Play Services), which suppresses the auto-fetch. | +| Bundles Segment / Amplitude / Firebase-Analytics / Sentry | **False** | None present. | +| Bundles heavy telemetry | **True** | **Instabug** — session replay, screenshot capture, network-request logging, visual user-steps → `api.instabug.com`. | +| Advertising ID / install-referrer | **False** | Neither is present. | + +--- + +## Methodology + +Both static and dynamic analysis were used. + +```mermaid +flowchart LR + A["Park+Smarter_4.4.0.xapk
(base + 3 config splits)"] --> B[jadx 1.5.1
dex → Java] + A --> C["hermes-dec
index.android.bundle → JS"] + A --> D[apktool 2.10.0
manifest + resources] + A --> E["APKEditor merge → 1 universal APK
+ apk-mitm (trust user CA)"] + E --> F["mitmproxy on device
(live HTTPS capture)"] + B --> G[(Findings)] + C --> G + D --> G + F --> G +``` + +- **jadx** decompiled the dex → Java. This is only the RN framework + native + modules + SDKs; the app's own logic is **not** here. +- The app is **React Native / Expo SDK 53**, so its business logic ships as + **Hermes bytecode** (`assets/index.android.bundle`, Hermes v96). **hermes-dec** + recovered readable pseudocode (`decomp.js`) with de-obfuscated function names. +- **apktool** decoded the manifest and resources. +- For the live capture, the split APKs were merged into one universal APK + (APKEditor) and repackaged to trust a user CA (`network_security_config`), then + driven through **mitmproxy**. (Merging first was necessary: patching only the + base split renumbers its resource IDs and desyncs the untouched `config.hdpi` + split, which crashes the app on the first text field.) + +--- + +## App architecture & network model + +Expo SDK 53 RN app. All API traffic goes through one request builder that stamps +these headers on **every** call: + +| Header | Value | Notes | +|---|---|---| +| `Application_Token` | `B66EEDDA-…` (static) | App-wide, embedded in the bundle | +| `X-Request-Id` | random UUID | Per request | +| `Auth_Token` | rolling | Rotates on each response | +| `ParkSmarter_SessionId` | per session | | +| `Content-Type` | `application/json` | POST/PUT only | +| `User-Agent` | `okhttp/4.12.0` | RN's HTTP stack | + +Five hard-coded environments (bundle string literals): + +- Prod: `https://apiv2.parksmarter.com`, `https://apiv3.parksmarter.com` +- Staging: `https://staging-parksmarter-api.ipsmeters.com` +- Testing: `https://testing-parksmarter-api.ipsmeters.com` + +No TLS pinning is configured (okhttp's `CertificatePinner` class is present but no +pins are set), and the manifest declares no `networkSecurityConfig` — which is why +a stock install can't be MITM'd without repackaging. + +--- + +## Finding 1 — Device identifier: the "IMEINumber" that isn't an IMEI + +The app defines a Redux thunk literally named `device/setIMEINumberAsyncThunk`. +Despite the name, it **never reads an IMEI**. It reads the **Android SSAID** +(`Application.androidId` from `expo-application`), caches it in secure storage as +`psUUID`, and later transmits it. + +Evidence (`decomp.js`): + +```js +// getAndroidId → expo-application's Application.androidId (the SSAID) +r0 = r0.default; r0 = r0.androidId; return r0; +// stored under 'psUUID' +r2 = 'psUUID'; r2 = setObjectAsync.bind(...)('psUUID', androidId); +// later placed on the wire: +r2['DeviceID'] = pushNotificationsToken; +r2['IMEINumber'] = imeiNumber; // === Application.androidId (SSAID) +r2['DeviceType'] = '1'; +// endpoint: putUpdateDeviceToken → { url: '/api/Device' } +``` + +Confirmations: + +- **No real IMEI read.** Zero `getImei` / `TelephonyManager.getDeviceId` / + `getSubscriberId` / `getSimSerialNumber` in the Java **or** the bundle. Every + `getDeviceId()` match in the Java is unrelated — `MotionEvent`/`KeyEvent` + device IDs (input routing) or `Context.getDeviceId()` (the API-34 *virtual* + device id, an `int`). `react-native-device-info` (which could read IMEI) isn't + bundled at all. +- **`READ_PHONE_STATE`** comes from `@react-native-community/netinfo`, which uses + `TelephonyManager` only for `getNetworkOperatorName()` (carrier name) and + network type — not identity. +- **Modern Android blocks it anyway**: since Android 10, `getImei()` throws for + non-privileged apps regardless of the permission. + +```mermaid +sequenceDiagram + participant App + participant OS as Android OS + participant PS as apiv2.parksmarter.com + Note over App: cold start / login / token change + App->>OS: Application.androidId (SSAID) + OS-->>App: e.g. "a1b2c3…" + App->>App: cache as psUUID (secure store) + App->>PS: PUT /api/Device
{ IMEINumber: SSAID, DeviceID: pushToken, DeviceType: "1" } + Note over PS: a stable per-device ID,
tied to the account — just not the IMEI +``` + +**Net:** the app does transmit a persistent, per-device identifier to its own +backend, mislabeled `IMEINumber`. It is the SSAID, not the hardware IMEI, and it +is **not** sent to any third party. The earlier "sends your IMEI" claim was wrong +in substance but pointed at something real — this field. + +--- + +## Finding 2 — GPS location + +Location is acquired via **expo-location** (`getLastKnownPositionAsync` + +`watchPositionAsync` at `Accuracy.Balanced`) and sent to the backend **only** on +the meter-search endpoints: + +``` +GET /api/Meter?Lat=&Long=&localeCode=en-US +``` + +The query carries **only** `Lat`/`Long` (no radius/limit); "all vs limited meters" +is an endpoint choice, not a parameter. Location is **not** attached to login, +session load, or payment — confirmed both in code and in the live capture. + +**Auto-send on map open.** The map hub wires `useAutoGoToUserLocation`, a state +machine that — when `isAuthenticated && isMapReady && locationPermissionGranted` +and the app is foreground — recenters on the user's GPS and fires the meter query +**without any tap**. Panning/zooming fires more (debounced). + +```mermaid +sequenceDiagram + participant User + participant Map as Map screen + participant Loc as expo-location + participant PS as apiv2.parksmarter.com + User->>Map: open Map (logged in, permission granted) + Map->>Loc: watchPositionAsync (Balanced) + Loc-->>Map: {lat, long} + Map->>PS: GET /api/Meter?Lat=…&Long=… (auto, no tap) + User->>Map: pan / zoom + Map->>PS: GET /api/Meter?Lat=…&Long=… (debounced) +``` + +**GrapheneOS caveat (observed).** `react-native-maps` needs Google Play Services +for tiles; on GrapheneOS the map doesn't render, so `isMapReady` may never flip +and the auto-fetch can be suppressed. In the live capture the single coordinate +sent (`Lat=48.274147628&Long=-116.550122619`) exactly matched a *known lot* +location rather than a fresh arbitrary GPS fix — consistent with the map being +degraded. Location egress is proven; the fully-automatic-on-open behavior is a +property of the code that a Play-Services device would exercise more visibly. + +**BigBrainParking, by contrast**, never sends GPS to the API except when you +explicitly tap "My location" and search; the map opens on your last lot from +history, not your GPS. + +--- + +## Finding 3 — Third-party telemetry: Instabug (and only Instabug) + +Contrary to the earlier analysis, **Segment, Amplitude, Sentry, Firebase +Analytics, Crashlytics, advertising-ID, and Play install-referrer are all +absent** (verified by package-dir and endpoint-host search). Firebase is present +only as **Cloud Messaging** (push), not analytics. + +The one real telemetry SDK is **Instabug** (2,328 classes), and its scope is +broad: + +- **Session replay** (`library/sessionreplay`) +- **Screenshot / screen capture** (`instacapture`, `screenshot`) +- **Network-request logging & interception** (`apm/networking`, `networkinterception`) +- **Visual user-steps / interaction tracking** (`visualusersteps`, `interactionstracking`) +- Crash reports, APM, surveys, user attributes + +All reporting to `api.instabug.com`. Captured live on startup: +`POST /api/sdk/v3/sessions/v2`, `GET /api/sdk/v3/features`, `/api/sdk/v3/first_seen`. + +```mermaid +flowchart TD + D[Your device] + D -->|"SSAID as IMEINumber + push token
GPS (Lat/Long) on meter search
account, vehicle, payment, sessions"| PS[apiv2.parksmarter.com] + D -->|"session replay, screenshots,
network logs, user-steps, crashes"| IB[api.instabug.com] + D -->|push registration| FCM[Firebase Cloud Messaging] + style PS fill:#294,color:#fff + style IB fill:#922,color:#fff + style FCM fill:#247,color:#fff +``` + +--- + +## Permissions (from the manifest) + +Present: `ACCESS_FINE/COARSE_LOCATION`, `READ_PHONE_STATE` (netinfo), +`ACCESS_WIFI_STATE`, `CAMERA`, `RECORD_AUDIO`, `BLUETOOTH_SCAN/CONNECT` (meter +BLE), `POST_NOTIFICATIONS`, `RECEIVE_BOOT_COMPLETED`, `FOREGROUND_SERVICE`, +`USE_BIOMETRIC`, `SYSTEM_ALERT_WINDOW`, `DETECT_SCREEN_CAPTURE`, install-referrer +service binding, plus a long list of launcher badge permissions. + +**Absent (important):** `ACCESS_BACKGROUND_LOCATION`. The app **cannot** track +location while closed. + +--- + +## Live capture — evidence timeline + +Captured through mitmproxy against a repackaged (user-CA-trusting) universal APK, +authenticated session: + +``` +18:44:07 GET apiv2.parksmarter.com/api/ParkingSession +18:44:07 GET apiv2.parksmarter.com/api/Session +18:44:08 POST api.instabug.com/api/sdk/v3/sessions/v2 (414B) ← telemetry +18:44:08 GET api.instabug.com/api/sdk/v3/first_seen +18:44:16 GET apiv2.parksmarter.com/api/Meter?ZoneName=dl +18:44:17 GET apiv2.parksmarter.com/api/Meter?Lat=48.274…&Long=-116.550… ← GPS +18:44:20 GET apiv2.parksmarter.com/api/ParkingEstimateItems +18:44:33 GET apiv2.parksmarter.com/api/ApplicationValidity +18:44:39 GET apiv2.parksmarter.com/api/ParkSmarterPrivacyPolicies +``` + +Header sample on a parksmarter request: `Application_Token: B66EEDDA-…`, +`Auth_Token: …` (rolling), `ParkSmarter_SessionId: …`, `User-Agent: okhttp/4.12.0`. + +`PUT /api/Device` (the `IMEINumber` PUT) did **not** fire in this session because +the login was cached — it sends on fresh login / token change. + +--- + +## Reproducing this + +The repackaged capture-ready APK and step-by-step mitmproxy instructions live in +the private source mirror (`ParkSmarterSourceCode`, `MITMPROXY.md`). In short: +merge splits → inject a user-CA `network_security_config` → resign → install the +CA as a user cert → proxy the phone through mitmproxy (mobile data **off** so the +Wi-Fi proxy applies). + +--- + +## Appendix — backend endpoint inventory (from the bundle) + +`ApplicationValidity`, `Auth`, `SignUp`, `User`, `Device`, `Session`, +`ParkingSession`, `Meter` (by location / scanner code / serial / zone name), +`ParkingLots`/`ParkingLogix`, `ParkingEstimateSingle/Multi/Items`, +`StartParkingSession`, `ParkingReceipt`, credit-card + vehicle CRUD, +`NotificationSettings`, `ParkSmarterPrivacyPolicies` / `Terms` / `About` / `FAQ`. +```