Deeplink / URI query parameter consumed without validation (forced unwrap)
ID |
dart.platform.deeplink_unvalidated |
Severity |
low |
Remediation Complexity |
trivial |
Remediation Risk |
low |
Remediation Effort |
low |
Resource |
Platform |
Language |
Dart |
Description
A deeplink / URI parameter is consumed with a forced unwrap (!) and no visible validation: uri.queryParameters['k']!, state.uri.queryParameters['k']!, Uri.parse(link!), .fragment!. An attacker who triggers the deeplink with crafted parameters can crash the app, drive open-redirect behaviour, or push unvalidated values into downstream sinks.
Rationale
Custom URI schemes, App Links, Universal Links, and notification
deeplinks are an open-to-the-world entry point: any app, web page,
or pasted link on the device can trigger them. Forced-unwrapping
the query / path parameter (! after queryParameters[..]) is a
signal that the developer treats the input as "obviously present" —
but the attacker decides what’s in the URI, so the value can be
null, empty, or arbitrarily long.
Concrete impacts:
-
Crash / DoS:
Null check operator used on a null valueif the parameter is missing; can be triggered by any external app. -
Open redirect / phishing: the param is used as the target of a Navigator push,
launchUrl, or WebView load (combine withunrestricted_webview_load). -
State manipulation: the param is passed to a constructor that sets app state (transfer amount, recipient, plan tier) without further validation.
The rule narrows to forced-unwrap because that idiom strongly
signals trust without validation. ? / ?? / Uri.tryParse are
the safe alternatives.
The following code illustrates a vulnerable pattern detected by this rule:
class DeepLinks {
void uriQuery(Uri uri) {
// VULNERABLE: Deeplink / URI query parameter consumed without validation (forced unwrap)
final next = uri.queryParameters['next']!;
// VULNERABLE: Deeplink / URI query parameter consumed without validation (forced unwrap)
final mode = uri.queryParametersAll['mode']!;
// VULNERABLE: Deeplink / URI query parameter consumed without validation (forced unwrap)
final id = uri.pathParameters['id']!;
// VULNERABLE: Deeplink / URI query parameter consumed without validation (forced unwrap)
final frag = uri.fragment!;
print('$next $mode $id $frag');
}
}
Remediation
Validate every deeplink parameter against an explicit allow-list /
type-conversion before use. Use ?? for nullable defaults rather
than ! to force-unwrap. For navigation, never push to a route
derived from a deeplink without a hardcoded allow-list.
// Vulnerable
final id = state.pathParameters['id']!; // crashes if missing
final url = uri.queryParameters['next']!; // open redirect sink
final u = Uri.parse(link!); // crashes on null
// Fixed
final id = state.pathParameters['id'] ?? '';
final url = uri.queryParameters['next'] ?? '/';
if (!_safeRedirectAllowList.contains(url)) return;
final u = Uri.tryParse(link ?? '');
if (u == null || !_acceptedSchemes.contains(u.scheme)) return;
References
-
OWASP Top 10 2025 - A06 : Insecure Design.
-
CWE-601 — URL Redirection to Untrusted Site ("Open Redirect")
-
Talsec — OWASP Mobile Top 10 for Flutter M4 — Insufficient input/output validation, with concrete deeplink attack scenarios
-
package:app_links (pub.dev) — App Links / Universal Links plugin — common source of these forced-unwrap patterns
-
package:go_router (pub.dev) —
state.pathParameters/state.uri.queryParametersare common deeplink sinks