iOS Universal Links Security Review
Universal Links bind an HTTPS domain to an iOS app through signed Associated Domains entitlement and Apple App Site Association (AASA) data. This lets iOS choose app or browser for matching URL. It does not authenticate user, authorize record, validate query parameter, or approve transaction. Review domain control, signed entitlement, AASA scope, runtime handler, and server authorization as separate boundaries.
Inspect signed association
Read release .entitlements, provisioning configuration, bundle identifier, application identifier prefix, and every applinks: entry. Normal entitlement contains domain only: applinks:shop.example.test. Do not include scheme, path, query, or trailing slash. Each host is distinct: example.test, www.example.test, and support.example.test need correct entitlement and corresponding AASA delivery. Wildcard subdomains require deliberate scope.
AASA must be named apple-app-site-association, without extension, served over valid HTTPS at https://shop.example.test/.well-known/apple-app-site-association, and not redirected. Its application ID is application identifier prefix plus bundle identifier. Check live response headers, body, TLS, response path, cache behavior, and release identity rather than copied staging JSON.
{
"applinks": {
"details": [{
"appIDs": ["ABCDE12345.com.example.shop"],
"components": [
{ "/": "/orders/*" },
{ "/": "/admin/*", "exclude": true }
]
}]
}
}
components can match path using "/", query items using "?", fragments using "#", and exclusions. /orders/* defines routing scope; it is not authorization. Add explicit exclusions for routes that must stay web-only. Keep paths narrow enough that new web routes do not silently become app entry points.
Test installed release app
Install signed release build, open matching and nonmatching HTTPS URLs from Safari and Messages, then capture iOS version, app build, exact URL, handler outcome, signed entitlement, AASA response, account state, and server result. Test fresh install and updates because associated-domain retrieval and caching are asynchronous; do not infer current device association from source repository alone.
Test malformed path, unexpected subdomain, duplicate query key, encoded slash, missing ID, external redirect, expired session, and object owned by another user. Handler must parse URL with allowlists, reject unsupported state, obtain normal session, and make server authorize requested object/action. Universal Link can open an authenticated screen only after normal checks.
Review record
| Artifact | Evidence | Owner | Result | Exception | Remediation |
|---|---|---|---|---|---|
| Entitlement | signed release values | iOS owner | pass/fail | approved host | remove broad domain |
| AASA delivery | HTTPS, no redirect, app ID | web owner | pass/fail | staged domain | publish valid file |
| Components | route and exclusion tests | product owner | pass/fail | web-only route | narrow matcher |
| URL handler | hostile URL cases | app owner | pass/fail | documented legacy link | allowlist parse |
| Destination | foreign-object server test | API owner | pass/fail | none | enforce authorization |
Decision rule: Universal Link association selects application routing. It cannot bypass authentication, authorization, state checks, confirmation, or input validation.
Remediate and retest
Remove unused associated domains, publish precise AASA components, keep administrative and irreversible routes excluded unless application flow requires them, and validate inbound parameters before navigation. Monitor domain ownership and deployment changes because website control controls association data. Remove development alternate-mode settings before release. Mobile testing can validate entitlement, AASA delivery, device routing, and endpoint authorization.
Evidence and release ownership
Keep release evidence with build number, entitlement extract, application identifier, AASA URL and body digest, TLS observation, test URL, iOS version, account state, handler result, API authorization result, owner, and retest date. Mark pass only when app opens intended route and hostile cases fail safely. An exception needs route owner, documented reason, compensating control, expiry, and release approval. Web deployment owner must revalidate AASA after CDN, certificate, hostname, or routing change.
Operations check
Do not use an AASA change as emergency authorization control. Roll back unsafe route in application and API as well as website data. Coordinate iOS, web, and API owners before adding host or wildcard. Verify production file after deployment from independent network path, then keep test result attached to change record. This catches stale CDN content, accidental redirects, and wrong release identity before users receive route.
Worked AASA verification and closure
Retrieve https://shop.example.test/.well-known/apple-app-site-association with no redirect and preserve headers and body hash. Expected behavior is HTTPS response reachable without authentication, valid association JSON or signed format accepted by deployment, and app identifier matching Team ID plus bundle ID. On clean device with Associated Domains entitlement, tap approved URL; expected behavior is application route only for listed path. Tap excluded path and verify normal web handling or defined safe fallback.
Test hostile inputs: duplicate query values, encoded path separators, foreign object ID, expired session, and URL opened from another application. Expected application behavior is validation before navigation, normal sign-in where needed, and backend authorization before data appears. AASA association controls route handoff, not trust in identifier or caller. Edge case: CDN cache can retain old AASA during bundle or Team ID change; inspect cache headers and retest clean install rather than relying on browser fetch alone.
Close with entitlement extract, AASA response hash, device/OS output, approved and rejected URL captures, server authorization trace, owner, and retest event. Reopen after domain, team, bundle, path rule, CDN, or signing change.