Skip to content

Security: Mackachoo/TLDRNews

Security

docs/security.md

Security model

How auth, data access, and API keys are protected — and what's intentionally not secret.

Threat model in one paragraph

Every Flutter app ships its config to end users; anyone can extract strings from an APK / IPA / web bundle. The repo being public adds nothing to the attack surface as long as runtime keys are restricted in Google Cloud and Firestore rules enforce access. Hiding identifiers in source code is theatre. Hiding unrestricted API keys in source is malpractice — until rotation, those keys are usable by anyone who finds them.

Secrets handling

What lives in .env (gitignored)

  • FIREBASE_WEB_API_KEY, FIREBASE_ANDROID_API_KEY, FIREBASE_IOS_API_KEY
  • YOUTUBE_API_KEY

Consumed at build time via --dart-define-from-file=.env (locally) or per-key --dart-define=KEY=... (CI). The Dart code reads them as compile-time constants via String.fromEnvironment(...) in lib/firebase_options.dart and lib/src/services/config_service.dart.

.env is not loaded at runtime and not bundled as a Flutter asset — earlier iterations of this repo used flutter_dotenv, which ships .env to build/web/assets/.env (publicly fetchable). Compile-time defines avoid that footgun. The values still end up in the minified JS / native binary, so GCP key restrictions remain the real safeguard (see below).

What lives in Firebase Remote Config

The YouTube API key has a secondary home in Remote Config (youtube_api_key). On mobile, ConfigService.getYouTubeApiKey() prefers .env but falls back to Remote Config — so the key can be rotated in production without shipping a new build.

What lives in gitignored platform config files

android/app/google-services.json and ios/Runner/GoogleService-Info.plist are regenerated by flutterfire configure (or downloaded from the Firebase console). They contain the same Firebase API key as .env, plus OAuth client IDs and Android signing-cert SHA-1 hashes. *.example templates are committed for shape reference only.

What's intentionally not secret

projectId, messagingSenderId, appId, authDomain, storageBucket, measurementId, iosBundleId, the Android package name. Firebase docs classify these as identifiers; they appear in every built binary regardless. They're committed as plain string literals in firebase_options.dart.

Required GCP key restrictions

Before flipping the repo public — or shipping any production build — set these in GCP Credentials:

Key Application restriction API restriction
FIREBASE_WEB_API_KEY HTTP referrers: production domain + localhost:* Identity Toolkit, Firebase, Firestore, Identity Platform, Token Service, Secure Token
FIREBASE_ANDROID_API_KEY Android apps: com.tldrnews.app + debug + release SHA-1 Same set as web
FIREBASE_IOS_API_KEY iOS apps: bundle ID com.tldrnews.app Same set as web
YOUTUBE_API_KEY Whichever platform calls it (admin tooling) YouTube Data API v3 only

The YouTube key is the most dangerous — quota is billable. Restrict it tightly and monitor usage.

Firestore rules summary

firebase/firestore.rules:

Path Read Write
accounts/{uid} self only self only
meta/{uid} any authenticated user admin only
channels/{cid} public admin only
{allPaths=**} admin admin

isAdmin() reads meta/{request.auth.uid}.admin. The wildcard rule grants admins blanket access; specific rules grant additional access to non-admins. Firestore evaluates rules as a logical OR, so combining specific + wildcard does what you'd expect.

Known caveat: if a user has no meta/{uid} doc at all, the get(...) inside isAdmin() will error, denying the request. That's safe by default but means new sign-ups need a meta/{uid} doc created before they can read anywhere that depends on isAdmin(). Bootstrap your first admin manually in the Firebase console.

App Check

firebase_app_check is in pubspec.yaml but not activated in code yet. Before production launch:

  1. Register attestation providers (Play Integrity on Android, DeviceCheck/AppAttest on iOS, reCAPTCHA Enterprise on web).
  2. Call FirebaseAppCheck.instance.activate(...) in main.dart after Firebase init.
  3. Enable enforcement in the Firebase console once you've verified non-attested clients are being rejected as expected.

App Check is the only mitigation that meaningfully reduces abuse from a stolen API key on rooted/jailbroken devices.

Going public — pre-flight checklist

  • All four keys rotated (.env updated, Firebase Remote Config updated, old keys deleted in GCP).
  • All four keys restricted (API + application restrictions).
  • Firestore rules deployed (firebase deploy --only firestore:rules).
  • No AIza strings in tracked files: git ls-files | xargs grep -nE 'AIza[0-9A-Za-z_-]{35}' returns nothing.
  • .firebase/ build cache is gitignored.
  • Fresh clone test: clone to /tmp, follow setup.md, confirm app builds without any of the gitignored files present in the repo.

Reporting issues

For security vulnerabilities, please email the maintainer rather than opening a public issue. Include a proof-of-concept and a suggested fix if you have one.

There aren't any published security advisories