Application Security / Client & Mobile

Client-Side & Mobile Security

The client runs on hardware the operator does not control. A determined user can read process memory, unpack a binary, patch it, and rewrite every request in transit — so no client-side check is a security boundary, and any secret shipped to a client is disclosed. What the client does provide is a set of browser- and OS-enforced boundaries that a server can configure but not replace: the origin model in the browser, and the app sandbox plus hardware-backed key storage on mobile. Client-side security engineering is mostly the work of configuring those boundaries correctly and knowing precisely what each one bounds.

Three panels. Browser isolation: the same-origin policy as the default boundary, CORS as the server-controlled relaxation of it, and CSP with SRI as added constraints on what may execute. Mobile platform isolation: Android per-app UIDs with SELinux, the iOS sandbox where all third-party apps run as the user mobile, and key storage in the Android Keystore backed by a TEE or StrongBox and in the iOS Keychain with keys protected by the Secure Enclave. Client-side hardening: response headers, never storing session tokens in localStorage, and reverse-engineering defenses that raise cost without being a boundary.

The three boundaries a client actually enforces, and where each one stops

The origin, and the controls that relax or constrain it

An origin is the triple of scheme, host, and port — https://app.example.com:443. The same-origin policy (SOP) is the browser’s default: script running in one origin may not read the DOM or the response body of another. It is a read restriction, which is the detail that explains most of what follows.

SOP does not prevent sending cross-origin requests. A form POST or an image load reaches another origin regardless; the browser simply withholds the response from script. That asymmetry is why CSRF exists as a separate class (covered in Web Application Vulnerabilities & OWASP Top 10) — the request succeeds even though the attacker never sees the answer.

Mechanism What it does How it fails when misconfigured
CORS Lets a server opt into having its responses readable by a named origin, via Access-Control-Allow-Origin and related headers. It is a relaxation of SOP, granted by the responding server. Reflecting the request’s Origin header into the response while also sending Access-Control-Allow-Credentials: true makes every origin trusted, and authenticated responses become readable by any site. Access-Control-Allow-Origin: * cannot be combined with credentials, so the reflection pattern is the dangerous one. A permissive regex matching example.com.attacker.net is the other common form.
CSP Constrains which sources the page may load and execute, independent of origin. A policy containing 'unsafe-inline' permits exactly the inline execution it exists to stop. A host-allowlist policy is bypassable through a JSONP endpoint or open redirect on any allowlisted host. A policy with no object-src or base-uri leaves plugin and base-tag injection open.
SRI Pins a third-party script or stylesheet to a cryptographic digest, so a modified file is refused. A missing or stale integrity attribute lets a compromised CDN serve modified script — the Magecart pattern. A subresource without crossorigin set will not be checked for a cross-origin fetch.
COOP / COEP / CORP Isolate the browsing context: COOP severs the window.opener relationship with cross-origin openers, CORP declares who may embed a resource, and COEP requires embedded resources to opt in. Without COOP, a cross-origin opener retains a handle to the window. Cross-origin isolation (COOP same-origin plus COEP require-corp) is also the precondition for SharedArrayBuffer and high-resolution timers, which is why Spectre-class mitigation and this configuration are linked.

Writing a policy that is actually strict

The host-allowlist form of CSP is the one most commonly deployed and the weakest. The CSP Level 3 draft’s authoring guidance advances nonces, hashes, and 'strict-dynamic' instead:

Content-Security-Policy:
  default-src 'self';
  script-src 'nonce-r4nd0mPerResponse' 'strict-dynamic';
  object-src 'none';
  base-uri 'none';
  frame-ancestors 'none';
  form-action 'self';
  require-trusted-types-for 'script';
  report-to csp-endpoint;

CSP is defense in depth. The specification says so directly: it “is not intended as a first line of defense against content injection vulnerabilities.”

Response headers, and what each one actually bounds

These are worth setting on a browser-rendered application. They are engineering recommendations here, not a normative requirement of any single standard.

Strict-Transport-Security: max-age=31536000; includeSubDomains
X-Content-Type-Options: nosniff
Referrer-Policy: strict-origin-when-cross-origin
Permissions-Policy: geolocation=(), camera=(), microphone=()
Cross-Origin-Opener-Policy: same-origin

Cookies and where tokens live

Set-Cookie: __Host-session=...; Secure; HttpOnly; SameSite=Lax; Path=/

Mobile: two different isolation models

“iOS and Android both sandbox apps” is true and hides a real difference in how, which matters when reasoning about what a compromise reaches.

Both models prevent app A from reading app B’s files. Only the mechanism differs — which is exactly the sort of distinction that matters when a platform-specific bypass is reported.

Two device columns compared. Android: each app has its own Linux UID with kernel-enforced separation and a per-app SELinux domain since Android 9, and the Keystore holds key material in a TEE or in StrongBox, which requires its own CPU, secure storage and true random number generator. iOS: all third-party apps run as the user mobile, isolated by a mandatory sandbox profile and a randomly assigned home directory, and the Keychain is an encrypted SQLite database on the file system whose per-item keys are protected by the Secure Enclave rather than stored inside it. A footer states that key material staying inside secure hardware means the app can request an operation but cannot export the key.

Same guarantee, two mechanisms — and where key material actually sits on each platform

Key storage

The property worth wanting is that key material never becomes readable by the app: the app asks the secure hardware to perform an operation and receives the result, and an attacker with full app-process compromise still cannot export the key.

MASVS control groups

The OWASP Mobile Application Security Verification Standard defines eight control groups:

Group Coverage
MASVS-STORAGE Secure storage of sensitive data on the device, including what must not be logged or backed up.
MASVS-CRYPTO Correct use of platform cryptographic primitives and key management.
MASVS-AUTH Authentication and authorization, including local biometric and device-credential flows.
MASVS-NETWORK Secure network communication, including transport security and identity pinning.
MASVS-PLATFORM Safe interaction with the platform and other apps — IPC, deep links, WebViews, custom URL schemes.
MASVS-CODE Code quality and build settings, including dependency and update handling.
MASVS-RESILIENCE Resistance to reverse engineering and tampering.
MASVS-PRIVACY Privacy properties: data minimization, transparency, and user control.

MASVS states the requirements; the MASTG (Mobile Application Security Testing Guide) provides the test cases that verify them.

Pinning and resilience, in proportion

Client and mobile review checklist

The checklist below is a journal working model, not a published audit standard. When auditing a web or mobile client, evaluate these eight criteria:

Diagnostic area Evaluation question Verification & audit evidence
CORS origin handling Are allowed origins matched against an explicit list, rather than reflecting the Origin header or matching a loose pattern — especially where credentials are allowed? CORS middleware source, response header capture for a forged Origin, a test with a suffix-matching hostile origin.
CSP strength and rollout Does the policy use per-response nonces with strict-dynamic rather than a host allowlist, set object-src and base-uri, and was it rolled out report-only first? Header capture across routes, CSP Evaluator output, nonce generation code, violation report volume.
Subresource integrity Do externally hosted script and link elements carry an integrity digest — SHA-256, SHA-384, or SHA-512 — with crossorigin set? HTML source inspection, build-time SRI generation step, a tampered-asset load test.
Transport policy Is HSTS set with an adequate max-age, and if the domain relies on preloading, is it actually on the preload list rather than merely sending the token? Header capture, preload list lookup for the domain, subdomain HTTPS coverage check.
Cookie and token placement Are session cookies Secure, HttpOnly, SameSite-explicit and __Host- prefixed, with no session token in localStorage? Set-Cookie capture, client storage inspection, a grep for token writes to localStorage.
Mobile key storage Is every long-lived secret held in the Android Keystore or the iOS Keychain, with key material non-exportable, rather than in preferences, plain files, or logs? Key generation call sites, a filesystem and backup dump of the app container, log review on a debug build.
Platform interaction surface Are exported components, deep links, custom URL schemes, and WebView settings reviewed — with JavaScript interfaces and file access disabled unless needed? Manifest and Info.plist review, WebView configuration, an IPC fuzz or intent-replay test.
Server-side enforcement Is every decision the client appears to make — entitlement, price, role, root status — re-decided server-side, with attestation verdicts validated on the server? API authorization tests issued with a patched or proxied client, attestation verification code path.
What I need to remember

Nothing the client enforces is a boundary — re-decide everything server-side, and ship no secret to a client. The same-origin policy restricts reading, not sending, which is why CSRF is a separate problem and why reflecting the Origin header with credentials allowed is the dangerous CORS mistake. Prefer per-response nonces with strict-dynamic over host allowlists, and remember HSTS is trust-on-first-use — the preload token alone does not put a domain on the preload list. On mobile, Android isolates by per-app UID and SELinux while iOS runs every third-party app as mobile under a sandbox profile; the iOS Keychain is an encrypted database whose keys are protected by the Secure Enclave, not storage inside it, and neither platform uses a TPM.

Primary references