cometchat-flutter-v6-production — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited cometchat-flutter-v6-production (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Ground truth:cometchat_chat_uikit: ^6.0— pub-cache source +ui-kit/flutter. Official docs: https://www.cometchat.com/docs/fundamentals/user-auth · Docs MCP:claude mcp add --transport http cometchat-docs https://www.cometchat.com/docs/mcp(or fetch the URL directly without MCP). Verify symbols against the installed package/source before relying on them.
Everything you need to move a CometChat Flutter app from development to production. Covers authentication, platform configuration, environment management, and security hardening.
CometChat supports two authentication modes. Understanding the difference is critical before shipping.
The authKey is embedded in client code and lets any user log in by UID alone. Convenient for prototyping, but anyone who decompiles your app can impersonate any user.
// ✅ Dev mode — fine for prototyping, NEVER ship this
final settings = (UIKitSettingsBuilder()
..appId = 'APP_ID'
..region = 'us'
..authKey = 'AUTH_KEY' // Client-side secret — dev only
..subscriptionType = CometChatSubscriptionType.allUsers)
.build();
await CometChatUIKit.init(uiKitSettings: settings);
// Login with authKey (SDK uses the key from UIKitSettings internally)
await CometChatUIKit.login('user_uid',
onSuccess: (user) => debugPrint('Logged in: ${user.name}'),
onError: (e) => debugPrint('Login failed: ${e.message}'),
);Your backend generates a short-lived authToken for each authenticated user. The client never sees the authKey.
// ✅ Production — authKey is NOT in client code
final settings = (UIKitSettingsBuilder()
..appId = 'APP_ID'
..region = 'us'
// No authKey here — tokens come from your server
..subscriptionType = CometChatSubscriptionType.allUsers)
.build();
await CometChatUIKit.init(uiKitSettings: settings);
// Login with server-provided token
final authToken = await yourBackend.getCometChatToken(currentUserId);
await CometChatUIKit.loginWithAuthToken(authToken,
onSuccess: (user) => debugPrint('Logged in: ${user.name}'),
onError: (e) => debugPrint('Login failed: ${e.message}'),
);Rule: If authKey appears anywhere in your production build, you have a security vulnerability.
┌──────────┐ 1. Authenticate ┌──────────────┐
│ Flutter │ ──────────────────────> │ Your Server │
│ App │ │ (Backend) │
│ │ 4. Return authToken │ │
│ │ <────────────────────── │ │
└──────────┘ └──────────────┘
│ │
│ 5. loginWithAuthToken(token) │ 2. Verify user identity
│ │ 3. POST /auth-tokens
v v
┌──────────┐ ┌──────────────┐
│ CometChat│ │ CometChat │
│ SDK │ │ REST API │
└──────────┘ └──────────────┘Your server calls the CometChat REST API with the authKey (which stays server-side):
curl -X POST "https://<APP_ID>.api-<REGION>.cometchat.io/v3/users/<UID>/auth_tokens" \
-H "apikey: YOUR_REST_API_KEY" \
-H "Content-Type: application/json"Response:
{
"data": {
"uid": "user_uid",
"authToken": "user_uid_1a2b3c4d5e6f7a8b9c0d1e2f",
"createdAt": 1700000000
}
}Replace <APP_ID> with your CometChat app ID and <REGION> with your region code (us, eu, or in). The full host becomes:
<APP_ID>.api-us.cometchat.io<APP_ID>.api-eu.cometchat.io<APP_ID>.api-in.cometchat.ioUse YOUR_REST_API_KEY from the CometChat dashboard (API & Auth Keys). Do not confuse it with the client authKey.
Your Flutter app calls your own backend (after the user authenticates with your auth system), and your backend returns the CometChat authToken.
Future<void> loginWithToken(String uid) async {
// 1. Call YOUR backend to get a CometChat auth token
final response = await http.post(
Uri.parse('https://your-api.com/cometchat/token'),
headers: {'Authorization': 'Bearer ${yourJwt}'},
body: jsonEncode({'uid': uid}),
);
final authToken = jsonDecode(response.body)['authToken'];
// 2. Login to CometChat with the token
CometChatUIKit.loginWithAuthToken(authToken,
onSuccess: (user) {
// CometChatUIKit.loggedInUser is now set
debugPrint('Logged in as ${user.name}');
},
onError: (e) {
debugPrint('CometChat login failed: ${e.message}');
},
);
}Note: loginWithAuthToken populates CometChatUIKit.loggedInUser before calling onSuccess — same as login() and init(). No need to call getLoggedInUser() afterward.
final user = User(
uid: 'user_123',
name: 'Jane Doe',
avatar: 'https://example.com/avatar.png',
);
await CometChatUIKit.createUser(user,
onSuccess: (created) => debugPrint('Created: ${created.uid}'),
onError: (e) => debugPrint('Create failed: ${e.message}'),
);final user = User(
uid: 'user_123',
name: 'Jane Smith', // Updated name
);
await CometChatUIKit.updateUser(user,
onSuccess: (updated) => debugPrint('Updated: ${updated.name}'),
onError: (e) => debugPrint('Update failed: ${e.message}'),
);createUser() and updateUser() require the authKey (set in UIKitSettings). In production:
loginWithAuthToken() — never create or update users directlyServer-side user creation:
curl -X POST "https://<APP_ID>.api-<REGION>.cometchat.io/v3/users" \
-H "apikey: YOUR_REST_API_KEY" \
-H "Content-Type: application/json" \
-d '{"uid": "user_123", "name": "Jane Doe"}'These are required for CometChat UIKit v6 on Android. Without them, debug builds may work but release builds will crash.
Ensure these are in android/gradle.properties:
android.useAndroidX=true
android.enableJetifier=trueenableJetifier resolves old Android Support Library conflicts from transitive dependencies in the CometChat SDK.
In android/app/build.gradle (Groovy) or build.gradle.kts (Kotlin DSL):
// build.gradle.kts
android {
defaultConfig {
minSdk = 26 // Required by cometchat_calls_sdk
}
}// build.gradle (Groovy)
android {
defaultConfig {
minSdkVersion 26
}
}If your minSdk is lower than 26, the build will fail with a manifest merger error referencing cometchat_calls_sdk.
Create android/app/proguard-rules.pro with these exact contents:
# CometChat — prevent R8 from stripping SDK classes used via reflection
-keep class com.cometchat.** { *; }
-keep interface com.cometchat.** { *; }
# Suppress warnings for Calls SDK classes referenced cross-module
-dontwarn com.cometchat.calls.CometChatRTCView$CometChatRTCViewBuilder
-dontwarn com.cometchat.calls.CometChatRTCView
-dontwarn com.cometchat.calls.CometChatRTCViewListener
-dontwarn com.cometchat.calls.model.AnalyticsSettings
-dontwarn com.cometchat.calls.model.RTCCallback
-dontwarn com.cometchat.calls.model.RTCReceiverReference the ProGuard file in your release build type. In android/app/build.gradle.kts:
android {
buildTypes {
release {
isMinifyEnabled = true
isShrinkResources = true
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
}Or in android/app/build.gradle (Groovy):
android {
buildTypes {
release {
minifyEnabled true
shrinkResources true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
}Without these rules, release builds crash with `ClassNotFoundException` for CometChat classes. Debug builds work fine because R8/ProGuard only runs on release.
CometChat requires minSdk 26, so multidex is not needed (it's automatic above API 21). If you have a multi-module setup where another module targets lower, ensure the app module still sets minSdk = 26.
Add these to ios/Runner/Info.plist inside the top-level <dict>:
<!-- Camera access (video calls, sending photos) -->
<key>NSCameraUsageDescription</key>
<string>$(PRODUCT_NAME) needs camera access for video calls and sending photos</string>
<!-- Microphone access (voice/video calls, audio messages) -->
<key>NSMicrophoneUsageDescription</key>
<string>$(PRODUCT_NAME) needs microphone access for voice and video calls</string>
<!-- Photo library access (sending images from gallery) -->
<key>NSPhotoLibraryUsageDescription</key>
<string>$(PRODUCT_NAME) needs photo library access to send images</string>Without these, the app crashes when the user taps the camera/gallery/mic button — iOS terminates apps that access protected APIs without a usage description.
If using CometChat calling with CallKit (incoming call notifications when app is backgrounded), add to Info.plist:
<key>UIBackgroundModes</key>
<array>
<string>voip</string>
<string>remote-notification</string>
</array>Or enable via Xcode: Target → Signing & Capabilities → + Background Modes → check "Voice over IP" and "Remote notifications".
CometChat uses HTTPS by default, so no ATS exceptions are needed. If you're loading user avatars or media from HTTP URLs (not recommended), add:
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>Avoid this in production — it disables all transport security. Instead, ensure all media URLs use HTTPS.
In ios/Podfile, ensure:
platform :ios, '13.0'CometChat UIKit v6 requires iOS 13.0+. If your Podfile has a lower target, pod install will fail.
Never hardcode credentials in source code. The AppCredentials pattern used in development:
// ❌ DON'T ship this — credentials visible in decompiled binary
class AppCredentials {
static const String appId = '26580020f03ff346';
static const String region = 'in';
static const String authKey = '4152b0366478871f0fa8d19a287dd6f5ed5f8eff';
}Pass credentials at build time:
flutter run \
--dart-define=COMETCHAT_APP_ID=your_app_id \
--dart-define=COMETCHAT_REGION=usRead them in code:
class CometChatConfig {
static const appId = String.fromEnvironment('COMETCHAT_APP_ID');
static const region = String.fromEnvironment('COMETCHAT_REGION');
// No authKey in production builds — use authToken flow
}For release builds:
flutter build apk \
--dart-define=COMETCHAT_APP_ID=your_app_id \
--dart-define=COMETCHAT_REGION=us
flutter build ipa \
--dart-define=COMETCHAT_APP_ID=your_app_id \
--dart-define=COMETCHAT_REGION=usdart pub add flutter_dotenvCreate .env (add to .gitignore):
COMETCHAT_APP_ID=your_app_id
COMETCHAT_REGION=usLoad in code:
import 'package:flutter_dotenv/flutter_dotenv.dart';
Future<void> main() async {
await dotenv.load(fileName: '.env');
runApp(const MyApp());
}
class CometChatConfig {
static String get appId => dotenv.env['COMETCHAT_APP_ID'] ?? '';
static String get region => dotenv.env['COMETCHAT_REGION'] ?? 'us';
}Note: .env files bundled in the app asset can still be extracted. For true secret protection, fetch config from your backend at runtime.
For apps with dev/staging/prod environments:
enum Environment { dev, staging, prod }
class CometChatConfig {
final String appId;
final String region;
final bool useAuthToken; // true for staging/prod
const CometChatConfig._({
required this.appId,
required this.region,
required this.useAuthToken,
});
static CometChatConfig of(Environment env) {
switch (env) {
case Environment.dev:
return const CometChatConfig._(
appId: 'dev_app_id',
region: 'us',
useAuthToken: false,
);
case Environment.staging:
return const CometChatConfig._(
appId: 'staging_app_id',
region: 'us',
useAuthToken: true,
);
case Environment.prod:
return const CometChatConfig._(
appId: 'prod_app_id',
region: 'us',
useAuthToken: true,
);
}
}
}authKey is NOT in any client-side code for production buildsCometChatUIKit.loginWithAuthToken() is used instead of CometChatUIKit.login()minSdk = 26 in android/app/build.gradleandroid.enableJetifier=true in gradle.propertiesproguard-rules.pro created with CometChat keep rulesproguard-rules.proisMinifyEnabled = true and isShrinkResources = true for releaseNSCameraUsageDescription in Info.plistNSMicrophoneUsageDescription in Info.plistNSPhotoLibraryUsageDescription in Info.plist.env files are in .gitignoreCometChatUIKit.init() called before any login or component usagesubscriptionType set in UIKitSettingsBuilder (or presence events won't fire)region is lowercase ('us', 'eu', 'in')CometChatUIKit.logout() and clears local stateCometChatUIKit.loggedInUser)The authKey allows anyone to:
If it's in your APK/IPA, it can be extracted in minutes with standard decompilation tools.
// ❌ SECURITY VULNERABILITY — authKey in client code
final settings = (UIKitSettingsBuilder()
..appId = 'APP_ID'
..region = 'us'
..authKey = 'AUTH_KEY_VISIBLE_TO_ATTACKERS')
.build();
// ✅ SECURE — no authKey, use server-minted tokens
final settings = (UIKitSettingsBuilder()
..appId = appId // App ID is not secret (it's in network requests anyway)
..region = region
..subscriptionType = CometChatSubscriptionType.allUsers)
.build();// ❌ WRONG — auth tokens in logs
debugPrint('Token: $authToken');
debugPrint('User: ${user.toJson()}'); // May contain tokens
// ✅ CORRECT — log only non-sensitive identifiers
debugPrint('Logged in as uid: ${user.uid}');In release builds, consider disabling debug prints entirely:
// In main.dart for release
if (kReleaseMode) {
debugPrint = (String? message, {int? wrapWidth}) {};
}The ProGuard rules in Section 4 keep CometChat classes intact (required for the SDK to work), but R8 will still obfuscate your own application code. This makes reverse engineering harder.
Ensure isMinifyEnabled = true is set for release builds — it enables both code shrinking and obfuscation.
CometChat auth tokens don't expire by default, but you can configure token expiry in the CometChat dashboard. If you enable expiry:
CometChatUIKit.loginWithAuthToken(authToken,
onSuccess: (user) {
// Token accepted, proceed
},
onError: (e) {
if (e.code == 'ERR_AUTH_TOKEN_NOT_FOUND' || e.code == 'AUTH_ERR_AUTH_TOKEN_NOT_FOUND') {
// Token expired or invalid — fetch a new one from your backend
refreshAndRetryLogin();
}
},
);NSAllowsArbitraryLoads to Info.plist unless absolutely necessary*.cometchat.io*.cometchat.com// Always logout when user signs out of your app
Future<void> signOut() async {
// 1. Logout from CometChat
CometChatUIKit.logout(
onSuccess: (_) => debugPrint('CometChat logout success'),
onError: (e) => debugPrint('CometChat logout failed: ${e.message}'),
);
// 2. Clear your own auth state
await yourAuthService.signOut();
// 3. Navigate to login screen
navigator.pushReplacementNamed('/login');
}Don't just navigate away — always call CometChatUIKit.logout() to clear the SDK session, disconnect WebSocket, and remove cached credentials.
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.