State-changing action invoked with an auto-execute / bypass-confirmation flag
ID |
dart.platform.deeplink_autoexecute |
Severity |
critical |
Remediation Complexity |
trivial |
Remediation Risk |
low |
Remediation Effort |
low |
Resource |
Platform |
Language |
Dart |
Description
A state-changing navigation or constructor is invoked with an auto-execute / auto-confirm / bypass-confirmation flag set (autoExecute: true, autoSubmit: true, autoConfirm: true, bypassConfirmation: true, requireConfirmation: false, etc.). When the call site is a deeplink handler, ANY app or website that can launch the URI can trigger this action against the current user.
Rationale
The classic Flutter deeplink-CSRF pattern: an app registers a custom
URI scheme (e.g. myapp://transfer?to=…&amount=…), and the
deeplink handler pushes a screen with the parameters AND an
autoExecute: true flag that skips the confirmation step. A
malicious app or website calls
Intent.parseUri("myapp://transfer?to=Attacker&amount=99999"),
the OS hands the link to your app, your handler fires the
pre-confirmed transfer, and the money moves.
The same anti-pattern shows up for:
-
One-tap account deletion / data wipe.
-
One-tap purchase / subscription activation.
-
One-tap "trust this device" / second-factor enrollment.
-
One-tap administrative actions in dev / admin flows.
Auto-execute flags are dangerous regardless of the call site, but the deeplink scenario makes them attacker-reachable from outside the app. The rule fires on the flag itself rather than on a path- from-deeplink-to-flag (which would need taint mode) — accept some false positives in exchange for catching the pattern at all.
The following code illustrates a vulnerable pattern detected by this rule:
class TransferScreen extends StatelessWidget {
const TransferScreen({super.key, this.recipient, this.amount, this.autoExecute = false});
final String? recipient;
final String? amount;
final bool autoExecute;
@override
Widget build(BuildContext context) => const Placeholder();
}
class DeeplinkHandlers {
void handleTransfer(BuildContext context, Uri uri) {
final String? to = uri.queryParameters['to'];
final String? amount = uri.queryParameters['amount'];
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => TransferScreen(
recipient: to,
amount: amount,
// VULNERABLE: State-changing action invoked with an auto-execute / bypass-confirmation flag
autoExecute: true,
),
),
);
}
}
Remediation
Deeplinks must only navigate or pre-fill. State-changing actions
require an explicit user confirmation gesture and, for high-value
operations, re-authentication via local_auth (with
biometricOnly: true for biometric-only).
// Vulnerable
Navigator.push(context, MaterialPageRoute(
builder: (_) => TransferScreen(
recipient: to,
amount: amount,
autoExecute: true, // any caller of the deeplink wins
),
));
// Fixed
Navigator.push(context, MaterialPageRoute(
builder: (_) => TransferScreen(
recipient: to,
amount: amount,
autoExecute: false, // user must press Confirm
requireBiometric: true, // and re-authenticate
),
));
References
-
OWASP Top 10 2025 - A01 : Broken Access Control.
-
OWASP Top 10 2025 - A06 : Insecure Design.
-
CWE-862 — Missing Authorization on a state-changing action
-
CWE-639 — Authorization Bypass Through User-Controlled Key (URI parameter)
-
Talsec — OWASP Mobile Top 10 for Flutter M4 — Improper platform usage, with deeplink-CSRF scenarios