---
key: "mobile-native-boundaries"
title: "Review trust boundaries in mobile and native clients"
url: "https://softwaresecurity.com/domains/appsec/platform-security/mobile-native-trust-boundaries/"
api: "https://softwaresecurity.com/api/v1/articles/mobile-native-boundaries.json"
domain: "appsec"
topic: "Application security"
type: "Guide"
identifiers: ["CWE-602","CWE-639","CWE-939","A06:2025","A01:2025","API1:2023","V8.3.1","V2.2.2"]
tags: ["Mobile apps","Desktop and native apps","Threat modeling"]
stages: ["design","build"]
roles: ["developer","appsec-engineer"]
license: "CC-BY-4.0"
license_url: "https://creativecommons.org/licenses/by/4.0/"
code_license: "MIT"
---

# Review trust boundaries in mobile and native clients

Protect local data and platform interactions while keeping server-side authorization independent of the client’s behavior.

Canonical URL: https://softwaresecurity.com/domains/appsec/platform-security/mobile-native-trust-boundaries/

## In short

Treat the client as untrusted: anyone can read its storage, change its requests or invoke its links and components directly. Keep every permission decision on the server, keyed to the authenticated caller and the requested object. On the device, protect every copy of a credential, clear cached data at logout and account switch, keep TLS validation on, and give deep links a small view-only vocabulary that still loads data through the authorized API.

- Hidden buttons, local role flags and obfuscated strings are not authorization; the server checks every resource.
- Deep links are untrusted input: parse them strictly and let them open screens, never approve actions.
- Track every copy of a credential or record: logs, backups, snapshots, caches and exported files.
- Keep TLS certificate validation on and test the failure path; never disable checks to make a request work.
- Offline access is a deliberate window in which revocation cannot reach data already delivered.

## Verify it

1. Change only the report id in an authenticated API request and confirm the server denies another user's report.
2. Open links with extra parameters, unsupported operations and the wrong scheme and confirm the app opens nothing.
3. Log out, switch accounts and restore a backup, then confirm no data from the previous account is visible.
4. Search logs, crash reports and analytics payloads for tokens and personal data.
5. Proxy the traffic through an untrusted certificate and confirm the app refuses the connection.

## What you will learn

- Locate server authority independently of client UI.
- Trace stored credentials and cached data through logout and restore.
- Constrain deep links and offline capabilities.

Audience: Mobile and desktop client developers, the backend teams that serve them, and reviewers assessing client trust assumptions.

Before you start:

- Distinguish client-controlled state from server-authoritative identity and data.

Time: About 29 minutes: 7 to read and 22 for the exercise

## Separate possession of a client from authority over data

A mobile or desktop client runs in an environment the application does not fully control. A person can inspect stored files, alter requests, invoke exported components or replace application code. That does not mean every device is compromised; it means server authorization cannot rely on a hidden button, an obfuscated string or a client-computed `isAdmin` flag. Treat the client as a presenter of credentials and requested actions. The server determines the caller's current permissions.

Follow a concrete expense application. Alice belongs to tenant red and can view her own report `R8`. The app stores a session credential and displays `R8`. A deep link `expense://report?id=R9` opens a report screen. If the server checks only a valid session, changing `R8` to another person's `R9` exposes the report regardless of the app's local validation. Device integrity signals may influence a risk decision, but they cannot establish Alice owns `R9`. The permission check belongs at the API's resource boundary; /domains/appsec/authorization/object-level-authorization/ shows it in code.

## Inventory data copies and externally callable entry points

Draw the credential's path from login to refresh, secure platform storage, memory, network use, logs, backup and logout. Platform credential facilities can reduce exposure compared with ordinary preferences, but their protection depends on access settings, device state and threat model. A token in secure storage is still available to the app when legitimately used; it does not authorize arbitrary server actions. Avoid placing credentials in URLs, analytics, crash reports or exported files.

Next enumerate entry points that bypass the normal interface: app links, custom URI schemes, share targets, exported activities, webviews, local IPC and privileged helpers. A link is untrusted input even when it looks like your product's URL. Parse it structurally, accept a small operation vocabulary and let the destination screen load only through authenticated, authorized APIs. Opening a review screen is a safer link action than immediately approving a payment or replacing an account credential. For webviews, keep untrusted navigation separate from native bridges and authenticated application content.

The Swift function below is a strict parser for the expense app's only link. It accepts exactly `expense://report?id=R<digits>` and returns nothing for an extra parameter, a duplicated id, a different host such as `approve`, a path, user info, a port, a fragment or a different scheme, so a link can open a review screen but never trigger an action. It deliberately accepts `R9`: whether Alice may see that report is the server's decision, not the parser's. The file compiles against the iOS 27 SDK, and with 11 test cases in a `main.swift` it runs on macOS with `swiftc`. Android and iOS configuration for the same boundaries, such as exported components, verified links and keychain classes, is in /domains/appsec/platform-security/android-ios-security/.

```swift
import Foundation

enum LinkAction: Equatable {
    case viewReport(id: String)
}

/// Accepts exactly expense://report?id=R<1-9 digits>. Anything else opens nothing.
/// The report screen still loads the id through the authenticated, authorized API.
func parseLink(_ url: URL) -> LinkAction? {
    guard let parts = URLComponents(url: url, resolvingAgainstBaseURL: false),
          parts.scheme == "expense", parts.host == "report",
          parts.user == nil, parts.password == nil, parts.port == nil,
          parts.path.isEmpty, parts.fragment == nil,
          let items = parts.queryItems, items.count == 1,
          items[0].name == "id", let id = items[0].value,
          id.range(of: #"^R[0-9]{1,9}$"#, options: .regularExpression) != nil
    else { return nil }
    return .viewReport(id: id)
}

```

## Test boundary violations at both ends of the connection

A useful test changes only one trust assumption. Alter a report id while preserving Alice's valid session: the server must deny the foreign report. Remove the session while keeping a legitimate deep link: the app may show login, but must not show report data. Forge a `premium=true` local flag: the server must still enforce purchase entitlement. After logout, verify the refresh credential is revoked according to policy and sensitive local cached reports are cleared or inaccessible to the next account.

Use synthetic records and inspect both client-visible state and server response. A screen that hides an error while a background API response contains the foreign report still fails. Conversely, a denied API call does not prove a previously cached screen stopped displaying private information. Device snapshots, backup restoration and account switching are especially useful boundary cases because they reintroduce data after the normal happy path has finished.

The iOS and Android projects on /labs/ run the same link contract through the platforms' own parsers, `URLComponents` and `android.net.Uri`: look-alike hosts, user info, extra parameters and encoded separators open nothing, a valid link only opens a review screen, and payment needs the user's tap there.

## Balance offline use, revocation and resilience

Offline features create an explicit period during which the device cannot learn about server-side revocation. Decide whether an offline report is permitted, what fields it includes and when the local copy expires. Do not promise immediate revocation for data already delivered and stored on an offline device. Minimize retained data and require fresh server authorization before new high-impact operations when connectivity returns.

Obfuscation, root or jailbreak checks, attestation and anti-debugging may raise the cost of tampering. They add support and availability tradeoffs and can produce false denials on legitimate devices. Document the response to an unavailable attestation service rather than allowing a network error to grant extra authority. A practical review gives each control its job: secure storage limits credential exposure, transport validation protects the connection, server policy controls resources, and narrow entry points constrain what outside content can ask the client to do. Keep the platform's TLS certificate validation enabled, and test that the app fails safely on an invalid or intercepted certificate rather than disabling checks to make a request succeed. Certificate pinning, MASVS-NETWORK-2, adds rotation and recovery obligations of its own.

These controls map onto OWASP MASVS v2.1.0: MASVS-STORAGE-1 and MASVS-STORAGE-2 for local copies, MASVS-PLATFORM-1 and MASVS-PLATFORM-2 for links, IPC and WebViews, MASVS-NETWORK-1 for transport, MASVS-AUTH-1 for the server-side decisions and MASVS-RESILIENCE-1 to MASVS-RESILIENCE-4 for tamper resistance. In the OWASP Mobile Top 10 2024 they correspond mainly to M1 Improper Credential Usage, M3 Insecure Authentication/Authorization and M9 Insecure Data Storage.

## A real-world failure

CVE-2017-13156 (Janus) let an attacker modify an Android APK's code without breaking its signature, because the v1 signature scheme ignored bytes prepended to the file. The missing control was verifying integrity with APK Signature Scheme v2 or later.

## Separate token theft, client tampering and authorized offline access

Three scenarios require different controls. First, an attacker copies a refresh credential from a log: storage hygiene, restricted diagnostics, revocation and credential lifetime limit that exposure. Second, Alice modifies her own client to request Bob's report: only the server's resource authorization prevents that request from succeeding. Third, Alice legitimately downloads her own report and later loses membership while offline: the server cannot retroactively un-deliver those bytes. A review that labels all three 'device security' can choose an expensive control that addresses none of the actual missing boundaries.

For a high-impact operation such as expense reimbursement, keep the decision online and bind approval to the displayed transaction details. The client can submit report id and intended amount, but the server reloads the current approved report and checks the actor's authority. A locally cached approval from yesterday should not approve an edited report today. If the server issues a short-lived authorization challenge, bind it to the relevant report revision and operation rather than a generic 'user passed authentication' flag.

Verification can stay precise without using a compromised production device. Supply the normal API request, change only report id or revision, and inspect the synthetic server state. Then test the separate local lifecycle with account switch and restored cache. This division lets the team identify whether a failure belongs to server policy, credential handling or local privacy rather than treating every visible symptom as an anti-tampering problem.

## Guided practice

Review an expense app with all identities, records and transitions supplied.

### Supplied material

```text
Alice=(A,red); Bob=(B,red). R8 owner=A; R9 owner=B. Server rule: valid session AND matching owner.
Alice's device: credential T1; cached R8 total=40; local premium=false.
Links: L1 expense://report?id=R8; L2 expense://report?id=R9; L3 expense://approve?id=R9.
Original API: validSession(T1) -> return report(id).
Logout rule: revoke T1 refresh authority; remove cached reports before another account uses the app.
Offline rule: previously authorized reports may be shown for 24 hours, no approvals offline. Snapshot S contains T1 and cached R8 from one hour before logout.
```

### 1. Trace the ordinary link

Evaluate L1 with T1 and explain the authority at each hop.

Worked answer: The link selects `R8` but grants no permission. `T1` authenticates Alice; the server ownership comparison authorizes `R8`; the app may display total 40.

### 2. Find client-only fixes

Evaluate L2 after merely hiding Bob's report in the interface. Then apply the server rule.

Worked answer: The original API still returns `R9`. The repaired API denies because B differs from A. Hiding the screen or using opaque ids does not repair the missing comparison.

### 3. Constrain unsolicited actions

Decide how L3 should behave under a view-only deep-link contract and the offline policy.

Worked answer: Reject the unsupported `approve` operation. It must not directly approve `R9` or queue an approval merely because a link was opened. Offline operation cannot add this capability.

### 4. Analyze logout and restoration

After logout restore `S` and try to refresh `T1` online; then consider the cached `R8` bytes offline.

Worked answer: The server rejects refresh because revocation is authoritative. The snapshot may still contain `R8`, illustrating why immediate erasure of all historical offline copies cannot be promised. The stated offline retention policy and local account separation need explicit implementation.

### 5. Test an altered entitlement

Change local `premium` to `true` and request a premium export. State the additional server check required.

Worked answer: The server looks up Alice's current entitlement and denies if absent. A local boolean is a display hint; it cannot become a purchase authorization.

Deliverable: A client/server trust map and five decisions covering links, foreign objects, logout, offline copies and entitlement.

Scope: Paper exercise: identities, links, cached data and policies are supplied, so no device or app build is needed. The Swift parser above checks the same link cases with `swiftc` on macOS.

## Executable practice

- [Keep an iOS app's storage, links and web bridge in bounds: project ZIP](https://softwaresecurity.com/downloads/projects/ios-platform-boundaries-5d4c4cd9417f9c64.zip) — Swift 6 · Xcode 16+ · iOS Simulator
- [Repair an Android app's links, components, network policy and storage: project ZIP](https://softwaresecurity.com/downloads/projects/android-platform-boundaries-52934d7ed9eeeff3.zip) — Kotlin · Android Gradle Plugin 9.4 · Robolectric 4.17 · Docker

Articles and teaching material: CC BY 4.0. Original executable code: MIT. Attribution: Software Security.

## Sources

- [OWASP Mobile Application Security Verification Standard](https://mas.owasp.org/MASVS/)
- [Android Developers: security best practices](https://developer.android.com/privacy-and-security/security-best-practices)
- [OWASP MASVS v2.1.0 release](https://github.com/OWASP/masvs/releases/tag/v2.1.0)
- [OWASP Mobile Top 10 2024](https://owasp.org/projects/mobile-top-10)
- [Apple: URLComponents](https://developer.apple.com/documentation/foundation/urlcomponents)
- [NVD: CVE-2017-13156, Android Janus APK signature bypass](https://nvd.nist.gov/vuln/detail/cve-2017-13156)