flutter_auditor
A command-line project-health audit tool for Flutter projects. Point it at
a Flutter project and it scans the Android manifest, iOS Info.plist,
Dart source, and pubspec.yaml/pubspec.lock — covering security
misconfigurations, dependency hygiene, and project hygiene (unused assets,
unused dependencies) in one pass, one command, no project modification.
Why flutter_auditor?
flutter_auditor audits the parts of a Flutter project most single-purpose
tools don't look at together: platform configuration files
(AndroidManifest.xml, Network Security Config, Info.plist), Dart
source, and pubspec.yaml/pubspec.lock — in one pass, instead of
stitching together several tools each covering one slice.
- Platform-config aware, not just source-aware — most Dart analyzers
only look at
.dartfiles.flutter_auditoralso parsesAndroidManifest.xml, the Android Network Security Configuration, backup rules XML, and iOSInfo.plist— the files where most real-world Android/iOS misconfigurations actually live (allowBackup, exported components, ATS exceptions, missing usage descriptions). - No upload, no account, no server — everything runs against files on disk, locally. The only network calls are read-only lookups to the public pub.dev API for dependency metadata (outdated/discontinued/license checks); your source code never leaves your machine.
- Actionable, not just diagnostic — every finding ships with a severity, the exact file (and line, where applicable), a plain-language description, and a concrete fix — not just a rule ID to go look up.
- Multiple categories in one pass — security misconfigurations, dependency hygiene (outdated, discontinued, restricted-license, unused), and project hygiene (unused assets) today, with more categories planned, instead of needing a separate tool per concern.
- Suppressible without going silent — a
.flutter_auditor_ignore.yamlfile lets you accept known findings by audit, id, or file glob; the console always reports how many findings were suppressed, so nothing disappears quietly. - CI-friendly by default — a single exit code (
--fail-on) gates your pipeline; a shareable HTML report (--html) with charts is there when you want something to hand to a non-technical stakeholder;--jsonand--sarifcover machine consumption, with SARIF dropping straight into GitHub code scanning as PR-line annotations. - Adoptable on a legacy codebase without a big-bang cleanup —
--update-baselinesnapshots existing findings so CI only fails on newly introduced ones, instead of forcing a five-year-old app to fix everything before the tool can be turned on.
Installation
dart pub global activate flutter_auditor
Or run it from source without installing:
dart run bin/flutter_auditor.dart audit
Usage
Run from the root of the Flutter project you want to audit:
flutter_auditor audit
Options
| Flag | Description |
|---|---|
-v, --verbose |
Show full detail for low-risk and maintenance findings (collapsed by default). |
--fail-on <severity> |
Minimum severity that causes a non-zero exit code: critical, high (default), medium, low, info, or none. |
--html <path> |
Write an HTML report (with charts) to the given path, e.g. --html audit_report.html. |
--open |
Open the generated HTML report in the default browser after writing it. |
--json <path> |
Write a JSON report to the given path. |
--sarif <path> |
Write a SARIF 2.1.0 report to the given path — drop it straight into GitHub code scanning for PR-line annotations. |
--update-baseline |
Write current findings to .flutter_auditor_baseline.json as the accepted baseline, then exit. See Adopting on an existing project. |
Example:
flutter_auditor audit --html audit_report.html --open --fail-on medium
Adopting on an existing project
Running this against a project for the first time can surface a lot of pre-existing findings — enough that nobody wants to triage them all before turning on CI enforcement. Bootstrap a baseline instead:
flutter_auditor audit --update-baseline
This writes every current finding to .flutter_auditor_baseline.json as a
set of fingerprint strings, one per finding.
How a finding is classified as new vs. pre-existing
Each fingerprint is built from three things: <audit id>|<file path>|<description>
— e.g. android_debuggable|android/app/src/main/AndroidManifest.xml|The application is debuggable.... Deliberately not line number or byte
offset: those shift whenever someone edits unrelated code above the
finding, which would make the baseline silently stop matching findings it
should still cover.
On every subsequent run, each finding's fingerprint is computed the same
way and checked against the baseline set — if it's in the set, the
finding is pre-existing; if not, it's new. There's no timestamp
or git history involved, just a direct set-membership check against
whatever was true the last time someone ran --update-baseline.
One consequence worth knowing: if a pre-existing issue gets fixed and is
later reintroduced, it'll be classified as new again — the baseline
only remembers what existed at the moment it was written, not "seen once,
forever excused." Re-run --update-baseline deliberately whenever you
want to update what counts as known.
From then on, a normal flutter_auditor audit run treats baselined
findings as accepted (never failing the build a second time), but how
they're accepted depends on severity:
- Critical/high findings stay visible in the report — a serious
finding doesn't silently disappear just because it's pre-existing, so
it keeps getting seen every run until someone actually fixes it. Newly
introduced findings print first, unmarked; pre-existing ones follow
under a
── Pre-existing (baselined, non-blocking) — N ──divider, so the two are never mixed together. The summary line breaks this down too:High Risk : 2 (1 new, 1 pre-existing). - Medium/low/info findings are fully hidden, the same as a suppressed finding.
- Maintenance findings (outdated/unused dependencies, unused or
overlarge assets) always stay visible in the Maintenance section
regardless of severity, baselined or not — they never affect the exit
code either way, so there's nothing for the baseline to protect them
from, and hiding them would just make dependency/asset hygiene reports
vanish the moment you run
--update-baseline.
The console always reports how many were accepted
(ⓘ N pre-existing finding(s) accepted via .flutter_auditor_baseline.json (M critical/high still shown, non-blocking)), so the baseline's effect is
never a mystery. Commit the baseline file so CI and every contributor
share the same accepted state.
Suppressing findings
Add a .flutter_auditor_ignore.yaml file to the root of the project being
audited to suppress specific findings — by audit, by exact issue id, or by
a file glob (matched relative to the project root):
# .flutter_auditor_ignore.yaml
audits:
- android_release_signing # suppress every finding from this audit
ids:
- android.allow_backup # suppress one specific, stable finding id
files:
- test/fixtures/** # suppress every finding under this path
Suppressed findings are never silently dropped — the console output shows
how many were suppressed (ⓘ N finding(s) suppressed by .flutter_auditor_ignore.yaml) before the report.
What it checks
Android
android:allowBackupenabled- Cleartext (HTTP) traffic permitted
- Exported activities, services, receivers, and providers
android:debuggableenabled- Manifest permissions (high/medium risk)
- Network Security Configuration (cleartext traffic, trusted user certificates)
- Missing or misconfigured backup rules / data extraction rules
- Release builds signed with the debug key, hardcoded signing credentials in
build.gradle, and an un-gitignoredkey.properties
iOS
- App Transport Security exceptions (arbitrary loads, weak minimum TLS version)
UIFileSharingEnabledexposing the Documents directory- Missing, empty, or mismatched usage-description strings (camera, location, etc.)
Cross-platform (Dart source)
- Hardcoded secrets (API keys, tokens, credentials, private keys)
- Insecure network usage (cleartext URLs, disabled certificate validation, WebView SSL bypass)
- Insecure storage (sensitive data in
SharedPreferencesor unencrypted Hive boxes)
Dependencies
- Outdated, discontinued, or unlicensed/restricted-license packages
- An outdated Dart SDK constraint (pre-null-safety)
- Dependencies declared in
pubspec.yamlbut never imported
Assets
- Assets declared in
pubspec.yamlbut never referenced in Dart source - Assets large enough to meaningfully bloat the app bundle (≥1 MB medium, ≥5 MB high)
Example output
╔══════════════════════════════════════════════════════════════╗
║ FLUTTER APP SECURITY & DEPENDENCY AUDIT ║
╚══════════════════════════════════════════════════════════════╝
📁 Project : my_app
📄 Scanned : android/, ios/, lib/, pubspec.yaml
🕐 Date : 2026-07-29 12:52:25
──────────────────────────────────────────────────────────────────
SUMMARY
──────────────────────────────────────────────────────────────────
✗ High Risk : 2
⚠ Medium Risk : 3
⚠ Low Risk : 1
ⓘ Maintenance : 2 (outdated/unused deps, licenses)
✔ Passed Checks : 11
Overall Status: ⚠️ ACTION REQUIRED (high-risk issues present)
──────────────────────────────────────────────────────────────────
✗ HIGH RISK (2)
──────────────────────────────────────────────────────────────────
[1] Android Backup Enabled
📍 android/app/src/main/AndroidManifest.xml
⚠️ The application allows Android backups.
✅ Fix: Set android:allowBackup="false" in AndroidManifest.xml.
[2] Exported Activity
📍 android/app/src/main/AndroidManifest.xml
⚠️ Activity ".MainActivity" is exported and may be accessible by other applications.
✅ Fix: Ensure this Activity is exported only when required and protected with appropriate permissions.
──────────────────────────────────────────────────────────────────
✔ PASSED (11)
──────────────────────────────────────────────────────────────────
✔ Hardcoded Secrets Detection — no issues found
✔ Insecure Storage Detection — no issues found
✔ App Transport Security Audit — no issues found
...
══════════════════════════════════════════════════════════════════
Scan complete: 2 high, 3 medium, 1 low, 2 maintenance item(s).
Exit code: 1 (high-risk issues present — see --fail-on to adjust)
══════════════════════════════════════════════════════════════════
The --html report shows the same findings with a severity chart:

Exit codes
0 if no issue meets the --fail-on threshold, 1 otherwise. Maintenance
findings (outdated/unused dependencies) never affect the exit code.
License
MIT — see LICENSE.
Libraries
- audits/android/allow_backup_audit
- audits/android/backup_rules_audit
- audits/android/cleartext_traffic_audit
- audits/android/debuggable_audit
- audits/android/exported_components_audit
- audits/android/manifest_permission_audit
- audits/android/network_security_config_audit
- audits/android/release_signing_audit
- audits/assets/overlarge_asset_audit
- audits/assets/unused_asset_audit
- audits/audit_registry
- audits/dependencies/dependency_hygiene_audit
- audits/dependencies/unused_dependency_audit
- audits/ios/app_transport_security_audit
- audits/ios/file_sharing_audit
- audits/ios/usage_description_audit
- audits/network/insecure_network_audit
- audits/secrets/hardcoded_secrets_audit
- audits/storage/insecure_storage_audit
- cli/audit_command
- cli/command_runner
- data/android_permissions
- data/license_policy
- data/locked_dependency
- data/maintenance_audit_ids
- data/network_patterns
- data/pattern_rule
- data/permission_info
- data/permission_risk
- data/resolved_asset
- data/secret_pattern
- data/secret_patterns
- data/storage_patterns
- data/unused_dependency_ignore_list
- data/usage_description_rule
- data/usage_description_rules
- engine/audit_engine
- models/audit
- models/audit_result
- models/audit_run_result
- models/baseline_config
- models/ignore_config
- models/project_context
- models/security_issue
- models/severity
- reporter/console_reporter
- reporter/html_reporter
- reporter/json_reporter
- reporter/sarif_reporter
- scanner/project_scanner
- utils/android_manifest_helper
- utils/asset_resolver
- utils/baseline_helper
- utils/ignore_config_helper
- utils/info_plist_helper
- utils/path_utils
- utils/pub_dev_client
- utils/pub_lock_helper
- utils/pubspec_helper