Debug / developer / admin artifact reachable from a release build

ID

dart.config.debug_artifacts_in_production

Severity

high

Remediation Complexity

trivial

Remediation Risk

low

Remediation Effort

low

Resource

Config

Language

Dart

Description

A debug, developer, admin, internal, or backdoor screen is reachable from a Navigator route in source, or a visual debug flag (debugShowCheckedModeBanner: true, showSemanticsDebugger: true, showPerformanceOverlay: true, debugPaintSizeEnabled = true) is enabled. Security-by-obscurity does not protect these features from a reverse-engineer who dumps the Dart snapshot.

Rationale

The common pattern: developers add a "hidden" admin or developer-menu screen that’s reachable via a undocumented gesture (five taps on the account balance, long-press the logo, specific debug deeplink). The assumption is that "users won’t find it." Reverse engineering blows that assumption away:

  • Static analysis: tools like blutter and analyze_snapshot extract all class names, method names, and string literals from a shipped Dart snapshot. A screen called DevMenuScreen or AdminPanelScreen is immediately visible in the dump.

  • Trace the entry path: the discovered class name is referenced from one Navigator.push(…​ ⇒ DevMenuScreen()) site; tracing that backwards reveals the gesture or condition that triggers it.

  • Reproduce: the attacker performs the gesture (or replays the deeplink) and accesses the menu — usually with permissions that should never have shipped (delete user, dump database, flip feature flags, view API keys).

Visual debug flags are a smaller-impact variant: they tell the attacker the build flavor (debugShowCheckedModeBanner and showPerformanceOverlay only render in debug-built Flutter), hinting at insufficient release-build discipline.

The following code illustrates a vulnerable pattern detected by this rule:

class DevMenuScreen extends StatelessWidget {
  const DevMenuScreen({super.key});
  @override
  Widget build(BuildContext context) => const Placeholder();
}

class Pages {
  void devMenu(BuildContext context) {
    Navigator.of(context).push(
      // VULNERABLE: Debug / developer / admin artifact reachable from a release build
      MaterialPageRoute(builder: (context) => const DevMenuScreen()),
    );
  }
}

Remediation

Gate developer / admin code paths behind kDebugMode from package:flutter/foundation.dart. The Dart tree-shaker removes unreachable code in release builds, so the screens disappear from the snapshot entirely.

import 'package:flutter/foundation.dart';

// Vulnerable: DevMenuScreen present in the release snapshot
if (_tapCount >= 5) {
  Navigator.of(context).push(
    MaterialPageRoute(builder: (_) => const DevMenuScreen()),
  );
}

// Fixed: tree-shaken out of release builds
if (kDebugMode && _tapCount >= 5) {
  Navigator.of(context).push(
    MaterialPageRoute(builder: (_) => const DevMenuScreen()),
  );
}

// Visual flags off in production
MaterialApp(
  debugShowCheckedModeBanner: false,
  showPerformanceOverlay: false,
  ...
);

Configuration

This detector does not need any configuration.

References

© 2026 Xygeni. All rights reserved