Key takeaways
- Custom URI schemes (
myapp://) are not verifiable. Two apps can register the same scheme, and the OS doesn't (and can't) distinguish the legitimate one — the foundation of deep link hijacking. - Universal Links (iOS) and App Links (Android) solve this via domain verification: a cryptographic handshake between the app's bundle ID and a
.well-knownfile on a real HTTPS domain. - For enterprise apps in regulated industries (fintech, health, gov), URI-scheme deep links are increasingly treated as a compliance liability — they can leak OAuth tokens, session IDs, and PII to a malicious sibling app on the same device.
- A managed platform like Ulinkly absorbs the operational security work: hosted AASA /
assetlinks.jsonfiles on a CDN, automated TLS, opaque slugs that keep PII out of URLs, and tamper monitoring on the verification files. - Migrating off URI schemes isn't just a marketing win — it's a defensive-posture upgrade.
The unseen back door
Mobile apps ship with extensive perimeter defense — biometric auth, certificate pinning, encrypted local storage, server-side hardening. And yet a quietly common attack surface lives in a place many security reviews still miss: the deep link.
For years, developers used custom URI schemes (mybank://, myhealth://) as a convenient way to route users between flows or hand off from web to app. As mobile attack tooling has matured, those legacy schemes have become a primary vector for deep link hijacking — interception of sensitive data passed in URLs by a malicious app that registers the same scheme.
This guide compares URI schemes with modern Universal Links and App Links, explains the hijacking mechanics, and lays out what enterprise teams should require from their deep-linking infrastructure.
1. Custom URI schemes — the legacy liability
The first generation of deep linking. A developer registers a unique prefix (paypal://, internal-hr://) and the OS opens the corresponding app whenever a link with that prefix is triggered.
The fundamental flaw: no ownership
There is no global registry for URI schemes. Nothing prevents another app from claiming the same scheme. If you ship an app that responds to vault://, a malicious app can ship that registers vault:// too. On iOS, the behavior in conflicts has historically been undefined; on Android, it can prompt the user to choose, or default to the more recently installed app.
How URI-scheme hijacking actually works
A textbook attack:
- The bait. A malicious app — disguised as a free utility, game, or wallpaper — convinces the user to install it.
- The squat. The malicious app registers the same URI scheme as your legitimate app (e.g.
mybank://). - The interception. The user clicks a password-reset link, an OAuth callback, or a session-resume link from an email or SMS. The OS resolves the URI scheme and opens an app — possibly the malicious one.
- The theft. Whatever sensitive data is passed in the URL — OAuth tokens, single-use auth codes, session IDs, account identifiers — is now in the attacker's hands.
This isn't a theoretical attack. It's been the basis of multiple real-world disclosed vulnerabilities in finance and consumer apps over the past decade.
Where URI schemes still have a role
URI schemes aren't uniformly bad — they're the right tool for internal routing within your own app, where you control both the registration and the receiver. The dangerous pattern is using them as the front door for any link a third-party (web, email, SMS) might construct. (Background: what is deep linking?)
2. Universal Links and App Links — the security standard
Apple introduced Universal Links and Google introduced App Links to fix the squatting problem at the root: a deep link should be tied to a domain you provably control.
The verification handshake
Both standards work the same way:
- App-side declaration. The app is built with a list of "Associated Domains" (iOS) or
intent-filterentries (Android) declaring which domains it's allowed to handle. - Server-side declaration. The domain hosts a verification file:
https://yourdomain.com/.well-known/apple-app-site-associationfor iOShttps://yourdomain.com/.well-known/assetlinks.jsonfor Android
- OS verification. The OS fetches the verification file (over TLS, against the domain's actual certificate) and confirms it lists the bundle ID / package name + SHA-256 of the installed app.
If the handshake fails, the OS does not open the app — the link falls back to the web. A malicious app cannot hijack the link, because it cannot control the cryptographic handshake against your domain. Universal Links and App Links are, by design, hijack-resistant.
What this means in practice
- The same
https://yourdomain.com/pathURL works as a deep link and a web fallback. No more dead links if the app isn't installed. - The link travels safely through email, SMS, web shares, and most messaging apps without being intercepted by sibling apps.
- A standard HTTPS URL is also more compatible with link previews, accessibility tooling, and bot scanners than a
myapp://URI.
3. Why enterprise security teams are moving to managed platforms
Universal Links and App Links solve the protocol problem. They don't solve the operational problem of running a deep-linking infrastructure: hosting the AASA / assetlinks.json files reliably, rotating TLS certs, monitoring for tampering, and keeping PII out of URLs.
A managed platform like Ulinkly absorbs that operational work.
Hosted verification files on a CDN
Ulinkly hosts your AASA and assetlinks.json on a globally distributed CDN with auto-renewing TLS. A misconfigured Cache-Control header, an expired cert, or a Cloudflare rule change won't silently break the verification handshake — common failure modes when teams self-host.
Opaque slugs and PII protection
A frequent enterprise mistake: passing PII in the URL itself. A link like https://yourapp.com/reset?email=user@example.com&token=abcd puts sensitive identifiers into browser history, server access logs, and any in-flight log aggregator.
Ulinkly's pattern is opaque slugs: the public link contains a short, randomized identifier (e.g. https://go.yourbrand.com/r/k4f9q). The actual destination and parameters are resolved server-side, with the sensitive payload kept out of the URL entirely.
Tamper monitoring
Ulinkly continuously verifies that your domain's .well-known files resolve correctly with the expected content and signature. If the file becomes unreachable or its content changes unexpectedly, you're alerted. The same monitoring catches certificate-rotation problems before users see broken links.
Security event logging
For teams that need it, Ulinkly can emit security-relevant events to your SIEM via webhooks: spikes in failed verifications, unusual click patterns, geographic anomalies that may indicate token-replay attempts.
4. The compliance angle: GDPR, HIPAA, and beyond
For regulated industries, deep-linking choices are also compliance choices.
A few patterns that auditors look for:
- Sensitive identifiers should not appear in URLs. Passing a patient ID, an account number, or a transaction ID directly in a query string can be flagged as inadequate data handling under multiple frameworks. Opaque slugs that resolve server-side address this directly.
- The deep-linking layer should be verifiable. Universal Links / App Links are cryptographically tied to a domain. URI schemes aren't. For regulated workloads, the verifiable layer is the only acceptable answer.
- Failover behavior should be documented. When the app isn't installed or the verification handshake fails, what happens? A managed platform with documented fallback behavior is easier to audit than a custom redirector.
Ulinkly supports both EU and US data-residency configurations and standard enterprise compliance documentation requests. (Enterprise + compliance conversations.) For the broader privacy posture under ATT and Privacy Sandbox, see privacy-first attribution. The patterns in this article are particularly load-bearing for fintech apps, where deep-link security is also a regulatory line item.
5. The migration: from URI schemes to verified deep linking
A pragmatic migration path:
- Audit URI-scheme links. Find every place a
myapp://URL is referenced — emails, SMS, OAuth callbacks, share-sheet handlers, web-to-app handoffs. - Categorize. Some are internal-only (safe to keep) and some are public-facing (must migrate). Public-facing are the priority.
- Add Universal Link / App Link entitlements. Register the domain in your app's capability config, host AASA /
assetlinks.json(or let Ulinkly host them). - Rewrite the public links to your verified
https://domain with opaque slugs. - Soft-deprecate the URI schemes. Keep them resolving for a window so older email clients and edge cases don't break — then sunset.
Most teams complete this in a week or two of focused work. The defensive-posture improvement is immediate.
FAQ
Are URI schemes always insecure?
Not always — but for any flow that carries sensitive data and originates outside your app, they're the wrong primitive. Use URI schemes for in-app routing where you control both ends; use Universal Links / App Links for everything that crosses the public internet.
Has Apple or Google formally banned URI schemes?
Neither has banned them outright — they remain part of both platforms' SDKs. Both platforms have, however, increasingly nudged developers toward verified deep linking and have made certain sensitive flows (OAuth, App Clips, etc.) require Universal Links rather than URI schemes.
What if my app uses URI schemes for OAuth callbacks?
Migrate to Universal Links / App Links if you can; this is one of the highest-risk URI-scheme patterns and most major identity providers have moved to support https:// callback URLs. PKCE helps mitigate the residual risk in flows that can't migrate yet.
Can Ulinkly keep the legacy URI scheme working during a migration?
Yes. Ulinkly links can fall back to a registered URI scheme as a last resort while you transition off it. The recommendation is to use that as a soft-deprecation tool, not a long-term plan.
What about deep link hijacking on Android specifically?
Android has stronger native protections than iOS for verified App Links, but the older URI-scheme behavior can still surface on older OS versions or misconfigured intent filters. The fix is the same: prioritize verified App Links, scope intent filters tightly.
Does Ulinkly support PII-free URL patterns?
Yes — opaque slugs are the default. Sensitive payloads are resolved server-side after the handshake, not embedded in the URL.
Audit your deep-linking surface for hijacking risk. Talk to the Ulinkly security team about a hands-on review of your enterprise deep-linking posture, or start with the docs.
