Lab 02 — Android Deep-Link Security Validator
Difficulty: 3/5 | Runs locally: yes
Pairs with the Phase 04 WARMUP Chapter 7 (intents, pending intents, deep links).
Why This Lab Exists (Purpose & Goal)
A deep link is a URL that opens a specific screen in your app — myapp://pay?to=… — and it is one of
the most under-appreciated attack surfaces on mobile. A deep-link handler is an exported entry point
that takes attacker-controlled parameters from outside the app, and developers routinely treat those
parameters as trusted. The goal of this lab is to build the validator that makes deep links safe:
exact host/path matching, single-valued parameters, tenant-bound object IDs, allowed actions, and
explicit confirmation for anything sensitive.
The Concept, In the Weeds
The two foundational problems:
- Custom-scheme links have no ownership proof. Any app can register to handle
myapp://, so a custom-scheme deep link can be hijacked by a malicious app. The fix is Android App Links — verifiedhttps://links backed by aassetlinks.jsondigital-asset-links file on your domain — which cryptographically prove your app owns the domain. The validator therefore rejects custom-scheme fallback for sensitive flows. - Parameters are untrusted input. A deep link must validate exactly like an HTTP request body: reject duplicate parameters (the same parser-differential idea as Phase 01/02), enforce single-valued parameters, bind object IDs to the authenticated tenant (no cross-tenant references), allow only known actions, and never initiate a sensitive action (a payment, a transfer) without in-app confirmation and server-side authorization tied to the authenticated user.
The canonical bug the validator prevents: a payment deep link that lets an attacker set an arbitrary recipient or cross a tenant boundary. Treat the deep link as navigation that may suggest, never as authority to act.
Why This Matters for Protecting the Company
Deep links are advertised, embedded in emails, and shared — so a malicious one reaches users through trusted-looking channels. A weak deep-link handler turns "the user tapped a link" into account takeover, fraudulent transactions, or cross-tenant data access. When you can build and review deep-link validation — origin proof via App Links, strict parameter handling, no silent sensitive actions, server-side authorization — you protect every user-facing flow the mobile app exposes through URLs.
Build It
Implement the validator: exact host/path, single-valued parameters, tenant-bound object IDs, allowed actions, and explicit confirmation for sensitive workflows. Reject custom-scheme fallback, duplicate parameters, open redirects, and cross-tenant references.
LAB_MODULE=solution pytest -q
# integrate the same cases into Android instrumentation tests
Secure Implementation Patterns
The anti-pattern. A handler that reads parameters straight into an action:
val to = intent.data?.getQueryParameter("to") // attacker-controlled
pay(to, intent.data?.getQueryParameter("amount")) // no origin proof, no tenant check, no confirm
The secure pattern — exact origin, single-valued params, tenant binding (mirrors solution.py):
def validate_link(url, *, tenant) -> dict:
p = urlsplit(url)
if p.scheme != "https" or p.hostname != "field.example": # App Link origin, not a custom scheme
raise ValueError("untrusted-origin")
if p.path not in {"/work-order/open", "/inspection/view"}: # allowlisted paths only
raise ValueError("unsupported-path")
params = parse_qs(p.query, keep_blank_values=True)
if any(len(v) != 1 for v in params.values()): # reject DUPLICATE params (smuggling)
raise ValueError("duplicate-parameter")
flat = {k: v[0] for k, v in params.items()}
if flat.get("tenant") != tenant: # bind to the authenticated tenant
raise ValueError("tenant-mismatch")
oid = flat.get("id", "")
if not oid.startswith(tenant + "_") or len(oid) > 80: # object id scoped to tenant + bounded
raise ValueError("invalid-object")
if "redirect" in flat: # no open-redirect parameter
raise ValueError("redirect-not-supported")
return {"path": p.path, "id": oid, "confirmation": "required"} # NEVER auto-acts
Note the return: confirmation: required. A deep link may navigate and pre-fill; it must never
initiate a sensitive action without in-app confirmation and server-side authorization.
Production practices to carry forward:
- Use verified Android App Links (
https://+assetlinks.json+autoVerify) for origin proof; custom schemes are claimable by any app. - Treat every parameter as untrusted input — same validation as an HTTP request body (single-valued, typed, length-bounded, tenant-scoped).
- The real authorization is server-side, tied to the authenticated user — the client check is defense in depth, not the boundary.
- No
redirect=/open-redirect parameters; reject them outright.
Validation — What You Should Be Able to Do Now
- Review a payment/sensitive deep link and name what's wrong (untrusted params, hijackable scheme, no confirmation, no server-side authZ) and the fixes.
- Explain why App Links (verified
https://+ assetlinks.json) beat custom schemes for ownership. - Treat deep-link parameters as untrusted input subject to the same validation as an HTTP request.
The Broader Perspective
This lab reinforces a theme you have now seen at the web layer (Phase 02 IDOR/SSRF) and will see again at the agent layer (Phase 11): any path that lets the outside world trigger an action with parameters is an authorization surface, and the real decision belongs server-side, tied to an authenticated identity. Client-side handling is convenience, not a boundary. Recognizing deep links, intents, webhooks, and tool calls as instances of the same "externally-triggered action" pattern is what lets you secure them consistently instead of treating each as a novel special case.
Interview Angle
- "Review
myapp://pay?to=X&amount=Y." — Exported entry point with attacker-controlled params and a hijackable scheme. Fix: App Links for origin proof, validate every param, never initiate payment without in-app confirm + server-side authZ tied to the authenticated user/tenant.
Extension (Stretch)
Wire the validated cases into the vulnerable/hardened app variants as instrumentation tests, and
implement the assetlinks.json verification path.
References
- Phase 04 WARMUP Chapter 7; Android App Links / Digital Asset Links documentation.
- OWASP MASVS (platform interaction, deep linking).