Repository navigation
Releases: anasfik/FlutterGuard
Release list
FlutterGuard 0.3.4
FlutterGuard 0.3.4 — the brand pass.
Same offline auditor, now wearing the flutterguard.dev family: porcelain and
forest surfaces, mint for the one thing that matters, flat ruled rows instead
of stacked cards. The app bar carries the launcher mark and a GitHub action.
- Light and dark schemes rebuilt from the site tokens, so the app and the
landing page read as one product. - Every card shell became a ruled row: inventory, report sections, findings,
queue, banners and legal pages. - Chips lost their outlines; selected state is the container plus a check.
- The search action is a solid forest button, and the search field owns its
own surface instead of tinting the strip behind it.
Nothing changed in what a scan does: no network permission, no accounts, no
telemetry, reports stay on the device until you export or delete them.
Upgrading from the previous GitHub APK: this build uses a new signing key, so
Android will refuse to install it over 0.3.3. Export any reports you want to
keep, uninstall FlutterGuard, then install this APK. Uninstalling removes the
in-app reports, history and settings, and exports do not restore the history.
Play installs are re-signed by Google and are not affected by this change.
v0.3.3 — R8 hardening: 5× smaller APK, Play-ready
First GitHub release since v0.3.1, two app updates in one: the consent/settings wave (0.3.2, versionCode 7 — this is what is currently on Google Play) and the R8 hardening pass (0.3.3, versionCode 8 — the APK attached here).
Smaller and R8-obfuscated (0.3.3)
- Release builds now enable code shrinking and obfuscation (R8 + resource shrinking), answering the Google Play "DEX code optimization" gate that warns below 25%.
- APK went from 6.9 MB to 1.4 MB; the packed dex went from 13.5 MB + 5.1 MB to a single 2.4 MB classes.dex — less code held in memory during a scan and a faster start. An AAB of 3.4 MB is what Play receives.
- No behavior change. Same detectors, same reports, same grade thresholds.
Consent, settings, and legal docs (0.3.2)
- First-run consent dialog: the package-visibility permission (
QUERY_ALL_PACKAGES) is disclosed on launch and used only after you agree. Declining keeps the app usable through a single-APK file picker (pick or share any Flutter APK and get the same report). - Privacy policy and the security model readable in-app from bundled legal documents (Settings) — same text as gwhyyy.com/privacy/flutterguard.
- The picked-APK flow holds its namespace everywhere — package list, cache and stored reports — not just on disk.
- Batch review fixes across the consent/settings wave.
Google Play
The store listing is live on the Play side: Google Play listing (first product release in review). The GitHub APK and the Play APK both install as com.flutterguard.app; if you sideload and later install from Play you may need one uninstall because Play serves builds signed with its own key.
Verification
- 282 unit tests / 38 suites / 0 failures on a clean rerun;
lintVitalReleasepasses. - Signed with the same certificate as 0.2.0–0.3.1 (
7350a257df573946d83c1dfeb1124c3e35e8ca59eb175972300b2600ebbeb1bf), so it updates an existing install in place and keeps stored reports. - Zero
INTERNETpermissions. - On a physical device the R8 build was exercised end to end earlier: install, launch, a full headless scan writing an identical graded report (0/5/5/5/8 findings on a real Flutter app before and after obfuscation), report screen and launcher icon render. The release/publishing steps in this wave were done with the device disconnected; the artifact itself was not rebuilt or changed since that on-device check.
- R8
mapping.txtis generated for every release build and uploaded to Play with the release, so obfuscated traces stay deobfuscatable.
Known limits (unchanged)
- A matched package, host or key proves the string is in the APK — evidence, not proof that a code path runs or that a key is live.
- Candidate routes are recovered strings rather than a navigation graph;
libapp.sois AOT machine code (strings and symbols, not decompiled Dart); AABs are not scanned (expand to APKs first); signature checking is structural rather than a fullapksignerverification. - Some ROMs block APK reads of other apps; that is an OS policy, not a FlutterGuard setting.
v0.3.1 — modern Flutter third-party surface
Modern third-party coverage for Flutter apps, on top of 0.3.0's corpus-driven
detectors. Offline, no root, no INTERNET permission.
What changed in 0.3.1
Seven runtime surfaces that were invisible
Derived from the actual top-downloaded Flutter package table
(fluttertrends.dev/top/, ~230 real package names), then filtered to packages
that name a third party or a security-relevant capability. Generic plumbing
(dio, http, riverpod, go_router, connectivity_plus, package_info_plus, …) is
deliberately excluded — it identifies no vendor and would only add noise.
- Genkit (Dart AI agents) —
genkit,genkit_google_genai,
genkit_anthropic,genkit_openai,genkit_middleware. Agentic
orchestration with tool definitions and prompt templates shipped in the app. - Dart FFI / JNI bridge —
jni,jni_flutter,objective_c,jnigen,
dexcom/github/dart_lang/jni. Native code reachable from Dart; ABI and
memory-safety bugs live here. - Multicast DNS / LAN discovery —
multicast_dns,nsd,bonsoir,
flutter_nsd. Finds devices on the local network and exposes peer identity. - Crypto primitives (PointyCastle) —
pointycastle,cryptography,
cryptography_flutter,encrypt. Cryptography implemented in Dart, where
mode and padding choices are the app's own. - Location services —
geolocator,location,
flutter_background_geolocation, hostmaps.googleapis.com. - Local notifications —
flutter_local_notifications,
awesome_notifications, dexcom/dexterous/flutterlocalnotifications.
Includes full-screen and action intents. - Device capture and playback —
image_picker,file_picker,
video_player,just_audio,record,camera. Reads user files, camera and
microphone.
Existing detectors widened — Firebase now also recognises firebase_ai
(Firebase AI Logic: Gemini/Imagen called from the client, the modern successor
to google_generative_ai), firebase_vertexai, firebase_crashlytics,
firebase_remote_config; Datadog recognises datadog_flutter_plugin and
datadog_flutter_plugin_flutterflow.
The third-party table now holds 145 services, matched over four independent
evidence classes: bundled files, DEX class descriptors, Dart pub packages, and
network hosts. Every hit carries which class fired, so pub:pointycastle is
distinguishable from a shared host string.
Requirements
Android 8.0+ (API 26). Sideload the signed APK; no Play Services needed.
Verification
- 239 unit tests / 34 suites / 0 failures, including the guard that no two
services share a name (a duplicate would silently drop a detector, because
DEX hits are keyed by service name). - Signed with the same certificate as 0.2.0 and 0.3.0
(7350a257df573946d83c1dfeb1124c3e35e8ca59eb175972300b2600ebbeb1bf), so it
updates an existing install in place and keeps stored reports. - Installed and exercised on a physical Redmi Note 9 Pro (LineageOS, API 35).
Known limits
- A matched package or host proves the string is in the APK, not that the code
path runs or that the key is live. - Some harvested provider hosts have no service entry yet (
api.cerebras.ai,
api.novita.ai,api.poe.com,open.bigmodel.cn,dashscope.aliyuncs.com);
they appear in the host list but not as named providers. - Package-name evidence depends on the pubspec or the recovered Dart strings,
so it is strongest whereNOTICES.Zorpubspec.yamlsurvives. - Standing limits, unchanged: candidate routes are recovered strings rather than
a navigation graph,libapp.sois AOT machine code (strings and symbols, not
decompiled Dart), AABs are not supported, signature checking is structural
rather than a fullapksignerverification.
v0.3.0 — corpus-driven detectors
On-device Flutter app research workbench. Offline, no root, no INTERNET permission.
What changed in 0.3.0
This release is the result of auditing 26 real release-mode Flutter APKs (Conduit,
Anx Reader, AppFlowy, Gopeed, PiliPala, Kazumi, Finamp, Musify, namida, Cashew,
AuthPass, KitchenOwl, venera, jasmine, Openlib, BeeCount, OpenNutriTracker,
Harmony Music, BloomeeTunes, Echo-Loop, Ghosten Player, oneAnime, PixEz, Spotube,
FluffyChat, Primal) against what the tool reported, then closing the measured gaps.
AI provider inventory
A single lumped "OpenAI/LLM" rule became ~25 named providers, each matched by
host, DEX descriptor or pub package: OpenAI, Anthropic, Google Gemini, OpenRouter,
DeepSeek, Groq, Mistral, Cohere, Together, Fireworks, Perplexity, xAI, HuggingFace,
Replicate, AWS Bedrock, DashScope/Qwen, Moonshot, Zhipu GLM, MiniMax, ElevenLabs,
Stability, LangChain, MediaPipe GenAI, LiteRT — plus localhost:11434 (Ollama),
localhost:1234 (LM Studio) and ComfyUI :8188, because an app that talks to a
local model is a real and easily missed case.
The bug that hid them
Host harvesting only ran on strings containing ://. A Dart AOT snapshot stores a
provider base URL bare (api.anthropic.com on its own), so those hosts were never
collected. A closed provider-domain matcher with a lookbehind now catches them
(myopenai.com is not openai.com). Conduit went from 4 to 17 reported
providers — matching the 22 endpoints measured in its APK on a desktop.
Third-party SDKs
35 services → ~135, across four independent evidence classes (bundled file, DEX
descriptor, pub package, network host): ad networks and mediation, attribution,
engagement, identity (Auth0, Clerk, Appwrite, Stytch, WorkOS, Okta, Keycloak),
realtime (Agora, LiveKit, Sendbird, Stream, Jitsi, Daily, Vonage, MQTT), payments
(PayPal, Braintree, Paddle, LemonSqueezy, Razorpay, Adyen, Play Integrity),
backends (PocketBase, Nhost, Parse, GraphQL, gRPC, Vercel, Railway, Render, Fly,
Heroku, DigitalOcean, B2), vector/search (Pinecone, Qdrant, Weaviate, Typesense,
Meilisearch, Elastic), and on-device HTTP servers (shelf, dart_frog, dhttpd).
More secret families
25 new rules: Anthropic, OpenAI project keys, Groq, HuggingFace, Replicate,
OpenRouter, xAI, Fireworks, Postmark, Mailgun, Azure AccountKey=, GCP service
account JSON, DigitalOcean, GitHub PAT, GitLab, npm, Docker, Postman, Airtable,
Notion, Linear, Slack and Discord webhooks, bearer JWTs, AES/ECB. Root cause fixed
along the way: rule gates are matched against lowercased text, so any uppercase
gate (AccountKey=, PMAK-) made the rule unreachable.
Bundled asset inventory
New findings from file names alone, so nothing secret is read to produce them:
WEB-PAYLOAD (bundled HTML/JS rendered in a WebView, plus script count),
SDK-MARKER (root billing.properties, play-services-*.properties,
transport-*.properties, firebase-encoders*.properties name the SDK without any
DEX parsing), AST-CONFIG (bundled config, key material, databases).
Exact dependency versions when available
When an APK still ships assets/flutter_assets/pubspec.yaml the report now lists
real dependency names and constraints (FLUT-PUBSPEC). Measured: that file is
present in only 1 of 26 release APKs, so it is a bonus signal — the NOTICES.Z /
package: URI recovery stays the primary path.
Requirements
Android 8.0+ (API 26). Sideload the signed APK; no Play Services needed.
Verification
239 unit tests, 0 failures. Device-verified on a physical Redmi Note 9 Pro
(LineageOS, API 35) against Conduit: 28 third-party services, 17 AI providers,
23 provider hosts, WEB-PAYLOAD 2 pages / 7 scripts, SDK-MARKER 4,
AST-CONFIG 17, atomic report write clean.
This release is signed with the same certificate as 0.2.0, so it updates an
existing install in place and keeps stored reports.
Known limits
- A scan of a large APK is slow: Conduit (~38 MB
libapp.so) takes about five
minutes, ~2m45s of it in the single-threaded native string sweep, and the native
stage reports one progress line for all libraries. Per-library progress and a
cheaper sweep for very large snapshots are the next performance items. - Some provider hosts are harvested but not yet attributed to a named service
(api.cerebras.ai,api.novita.ai,api.poe.com,open.bigmodel.cn,
dashscope.aliyuncs.com), so they appear in the host list rather than as a
provider row. - Ad, attribution, auth, realtime, payment, backend and vector-store detectors are
coverage, not corpus findings: none of those matched in the 26 apps studied. - A matched host or key proves a string is in the APK, not that it is called,
reachable, or still valid. - Candidate routes are recovered strings, not a navigation path.
libapp.sois
AOT machine code: strings, symbols and routes, not decompiled Dart. AABs are not
supported. Signature verification is structural, not a fullapksignercheck.
v0.2.0 — research workbench
On-device Flutter app research workbench. Offline, no root, no INTERNET permission.
What changed in 0.2.0
Scanning that survives real use
- One foreground scan owner shared by the UI and the headless
am start -e scanentry, so a scan
keeps running when you leave the screen. Terminal notification when it finishes or fails. - Per-job identity: a timed-out or cancelled scan can never write into a newer report.
- Cooperative cancellation at every pipeline checkpoint, staged progress, 20-minute per-app bound.
- The installed-app inventory is cached in-process — going back from a report no longer reloads
the whole device list. - Atomic report and history writes (temp file, fsync, rename) with inflight markers, so a kill
leaves either the old report or the new one, never half of each.
A real report UI
- Navigation is now real routes: Home → Details → Section, with one Back handler, so Back from a
See-all list returns to the report, and Back from the report returns to the loaded inventory. - Three panes: Overview (grade with a stated reason, severity summary including zero rows,
artifact, extraction/Flutter-check metadata, signing, change card), Findings (expandable
rows, masked evidence, per-row reveal), Explore (endpoints, candidate route tree,
dependencies with pub.dev links, recovered paths, channels, APK artifact browser). - Long sections show a bounded window plus See all, which opens a searchable full-screen list
instead of an endless inline scroll. - Scan all Flutter apps queues every detected app behind a cost dialog, with a dedicated queue
screen: live per-app stage, determinate bar, cancel with confirmation, honest done/cancelled
counts. - Every scan is appended to a per-app, versioned, bounded scan log; change cards are computed from
it.
Evidence you can trust
- Sensitive values are masked by construction; full values live only in memory, are never
serialized, never logged, and reset when you switch apps. A report loaded from disk says
"rescan to reveal" rather than showing a dead control. - Revealing a value sets
FLAG_SECUREon that screen and marks clipboard copies sensitive. - Endpoints open exactly the URL shown, or are labelled not openable. Recovered strings that look
like concatenations are demoted, not presented as hosts. Candidate routes are grouped by prefix
and never drawn as a navigation graph. - The grade legend is derived from the actual counts: an F earned on weighted severity no longer
claims critical findings.
Exports
- Section-selected JSON or Markdown, default-redacted, with an explicit per-export opt-in for
sensitive values. Unique file names, and an unknown format is an error instead of a silent
Markdown fallback.
Design
- Semantic Material 3 tokens: one radius family, scheme-derived severity colours, one cyan accent,
a 4/8/12/16/24/32 spacing scale, 48 dp targets, light and dark both verified on device.
Requirements
Android 8.0+ (API 26). Sideload the signed APK; no Play Services needed.
Verification
217 unit tests green. Verified on a physical Redmi Note 9 Pro (LineageOS, API 35): inventory
caching, scan continuity across navigation, queue start/cancel, stored-report loading, endpoint
honesty, masked evidence, exports, light and dark rendering.
Known limits
Candidate routes are recovered strings, not a navigation path. libapp.so is AOT machine code:
you get strings, symbols and routes, not decompiled Dart. AABs are not supported. Signature
verification is structural, not a full apksigner check. Dependencies and APK artifacts still
filter inline in Explore rather than routing to their own full-screen search.
FlutterGuard v0.1.1
Follow-up to v0.1.0 with fixes found while putting the code under test.
Fixed
- Finding lines in "Changes since last scan" were malformed. The snapshot stored findings as
severity|idbut the snapshot format already uses|as the field separator, so a diff could report- finding MAN-BACKUPwithout its severity and miss the pair entirely. Findings are now folded back into pairs on read, so existing.histfiles keep working. - Diff card could overflow its line budget by one line when all four sections changed at once.
- APK signing now includes the v3 scheme (previously v2 only), which carries key-rotation proof for future key changes.
Added
- Unit tests for the four parsers that have no Android dependency:
DotEnv,X509,Notices,History— 10 tests,./gradlew testDebugUnitTest. The finding-pair bug above was caught by them on the first run.
Same app behaviour otherwise: identical reports, same signing identity, same report paths.
FlutterGuard v0.1.0
First public build of FlutterGuard as an Android app — the agent skill stays in skill/.
What it does
Audits any Flutter app installed on the same device, straight from the OS-exposed APK. No root, no network, no INTERNET permission.
- Artifact — package, version, SDK range, size, splits, ABIs, hashes, every native lib
- Flutter specifics — AOT vs JIT kernel, engine/Dart version, obfuscation, Dart dependency inventory (from
NOTICES.Zandkernel_blob.bin) - Manifest risk — permissions, exported components, cleartext, backup, deep links (aggregated per scheme)
- Secrets — 20-rule corpus over dex, resources, assets,
libapp.soand the Dart kernel; dotenv files get their own parser and CRITICAL when they are prod-named - Endpoints — hosts classified first-party / staging / internal / SaaS / IP, plus recovered API routes and Dart source paths
- Native surface — flutter_rust_bridge, JNI entry points, embedded X.509 identities, TLS bypass markers, proxy schemes
Verified against 12 real apps
Corpus and per-app methodology in CORPUS.md and docs/METHODOLOGY.md, every rule traced back to a manual observation:
| App | Grade | Notable |
|---|---|---|
| Kooul | F | .env.prod with 13 live credentials — CRITICAL |
| Cake Wallet | D | 9 endpoint-list YAMLs, embedded X.509, 123 JNI symbols |
| FluffyChat | F | ISRG Root X1/X2 reassembled from split PEM lines |
| Immich | D | R8-flattened network security config decoded |
| RustDesk | C | clean baseline, flutter_rust_bridge surfaced |
| Spotube, AppFlowy, Bztamy, LocalSend, Hiddify, Obtainium, Primal | — | false-positive and negative-control calibration |
Also this release
- Version diffs — every scan writes a snapshot, so re-scanning an updated app shows what actually changed (hosts, routes, findings, dependencies)
- Batch mode —
adb shell am start -n com.flutterguard.app/.MainActivity -e scan <package>writes JSON to app-specific external storage, pullable withadb pull - Reports are plain JSON + Markdown, shareable from the app
Install
Download app-release.apk below and install it (Android 8.0+). Reports go to Android/data/com.flutterguard.app/files/reports/.
Authorized testing only — audit apps you own or have permission to assess.