Skip to content

Releases: anasfik/FlutterGuard

FlutterGuard 0.3.4

Choose a tag to compare

@anasfik anasfik released this 02 Nov 20:14

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

Choose a tag to compare

@anasfik anasfik released this 01 Nov 04:06

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; lintVitalRelease passes.
  • 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 INTERNET permissions.
  • 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.txt is 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.so is AOT machine code (strings and symbols, not decompiled Dart); AABs are not scanned (expand to APKs first); signature checking is structural rather than a full apksigner verification.
  • 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

Choose a tag to compare

@anasfik anasfik released this 30 Oct 20:36

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,
    dex com/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, host maps.googleapis.com.
  • Local notifications — flutter_local_notifications,
    awesome_notifications, dex com/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 where NOTICES.Z or pubspec.yaml survives.
  • Standing limits, unchanged: candidate routes are recovered strings rather than
    a navigation graph, libapp.so is AOT machine code (strings and symbols, not
    decompiled Dart), AABs are not supported, signature checking is structural
    rather than a full apksigner verification.

v0.3.0 — corpus-driven detectors

Choose a tag to compare

@anasfik anasfik released this 30 Oct 20:24

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.so is
    AOT machine code: strings, symbols and routes, not decompiled Dart. AABs are not
    supported. Signature verification is structural, not a full apksigner check.

v0.2.0 — research workbench

Choose a tag to compare

@anasfik anasfik released this 29 Oct 22:42

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 scan entry, 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_SECURE on 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

Choose a tag to compare

@anasfik anasfik released this 28 Oct 23:09

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|id but the snapshot format already uses | as the field separator, so a diff could report - finding MAN-BACKUP without its severity and miss the pair entirely. Findings are now folded back into pairs on read, so existing .hist files 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

Choose a tag to compare

@anasfik anasfik released this 28 Oct 23:00

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.Z and kernel_blob.bin)
  • Manifest risk — permissions, exported components, cleartext, backup, deep links (aggregated per scheme)
  • Secrets — 20-rule corpus over dex, resources, assets, libapp.so and 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 with adb 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.