Firebase security expert
Skill roedyrustam/claudevibeskills/src/firebase-security-expert
Koleksi 20 Claude Skills siap pakai untuk pengembangan SaaS, web modern, dan praktik rekayasa perangkat lunak tingkat lanjut.
npx -y skills add roedyrustam/claudevibeskills --skill firebase-security-expertAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
2 things to look at
- no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.
- 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Firebase Security Rules auditing, Service Account protection, and App Check integration. Use whenever the user is working with Firebase security — Firestore rules, Realtime Database rules, Storage rules, App Check, service account key management, or Firebase Auth security. Trigger on mentions of Firebase, Firestore, Firebase Security Rules, App Check, service account JSON, Firebase Admin SDK, or when the user asks to review or write Firebase rules. Also trigger for Firebase project security audits.
SKILL.md
10.6 KB, as published. Nobody here has run it
Firebase Security Expert
Comprehensive Firebase security: rules, App Check, service accounts, and auditing.
The #1 Firebase Security Mistake
Exposed service account JSON in client code or public repositories.
# ❌ DO NOT commit service account keys
firebase-admin-key.json
service-account.json
*-firebase-adminsdk-*.json
# Add to .gitignore immediately
echo "*.json" >> .gitignore # Too broad — use specific names
echo "service-account*.json" >> .gitignore
echo "*-adminsdk-*.json" >> .gitignore
# If already committed: rotate the key immediately in Firebase Console
# Google Cloud → IAM → Service Accounts → Your SA → Keys → Delete old, Add new
Firestore Security Rules
Rule Fundamentals
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
// Helper functions
function isAuth() {
return request.auth != null;
}
function isOwner(userId) {
return isAuth() && request.auth.uid == userId;
}
function hasRole(role) {
return isAuth() &&
get(/databases/$(database)/documents/users/$(request.auth.uid)).data.role == role;
}
function isOrgMember(orgId) {
return isAuth() &&
exists(/databases/$(database)/documents/orgs/$(orgId)/members/$(request.auth.uid));
}
// Users can only read/write their own profile
match /users/{userId} {
allow read: if isOwner(userId);
allow create: if isOwner(userId)
&& request.resource.data.keys().hasOnly(['name', 'email', 'createdAt'])
&& request.resource.data.email == request.auth.token.email;
allow update: if isOwner(userId)
&& request.resource.data.diff(resource.data).affectedKeys()
.hasOnly(['name', 'avatarUrl', 'updatedAt']); // can't change email/role via client
allow delete: if false; // no self-deletion — use Cloud Function
}
// Organization-scoped documents
match /orgs/{orgId} {
allow read: if isOrgMember(orgId);
allow create: if isAuth();
allow update: if isOrgMember(orgId) && hasRole('admin');
allow delete: if false;
// Sub-collection inherits org membership requirement
match /projects/{projectId} {
allow read: if isOrgMember(orgId);
allow write: if isOrgMember(orgId)
&& get(/databases/$(database)/documents/orgs/$(orgId)/members/$(request.auth.uid))
.data.role in ['admin', 'member'];
}
}
// Public read, authenticated write
match /posts/{postId} {
allow read: if resource.data.published == true || isOwner(resource.data.authorId);
allow create: if isAuth()
&& request.resource.data.authorId == request.auth.uid
&& request.resource.data.title is string
&& request.resource.data.title.size() > 0
&& request.resource.data.title.size() <= 200;
allow update: if isOwner(resource.data.authorId)
&& request.resource.data.authorId == resource.data.authorId; // can't change author
allow delete: if isOwner(resource.data.authorId);
}
// Deny everything else
match /{document=**} {
allow read, write: if false;
}
}
}
Data Validation in Rules
// Validate field types and values in rules
match /orders/{orderId} {
allow create: if isAuth()
&& validOrder(request.resource.data);
function validOrder(data) {
return data.keys().hasAll(['userId', 'items', 'total', 'status'])
&& data.userId == request.auth.uid
&& data.items is list
&& data.items.size() > 0
&& data.total is number
&& data.total > 0
&& data.status == 'pending'; // clients can only create pending orders
}
}
Firebase Storage Rules
rules_version = '2';
service firebase.storage {
match /b/{bucket}/o {
// User avatars — users can only write their own
match /avatars/{userId}/{allPaths=**} {
allow read: if true; // public avatars OK
allow write: if request.auth != null
&& request.auth.uid == userId
&& request.resource.size < 5 * 1024 * 1024 // 5MB max
&& request.resource.contentType.matches('image/.*');
}
// Private user documents
match /users/{userId}/documents/{docId} {
allow read, write: if request.auth != null && request.auth.uid == userId;
}
// Organization files
match /orgs/{orgId}/{allPaths=**} {
allow read: if isOrgMember(orgId);
allow write: if isOrgMember(orgId) && hasOrgRole(orgId, 'member');
function isOrgMember(orgId) {
return request.auth != null
&& firestore.exists(/databases/(default)/documents/orgs/$(orgId)/members/$(request.auth.uid));
}
function hasOrgRole(orgId, minRole) {
let member = firestore.get(/databases/(default)/documents/orgs/$(orgId)/members/$(request.auth.uid));
return member.data.role in ['admin', 'owner']; // simplified
}
}
// Deny everything else
match /{allPaths=**} {
allow read, write: if false;
}
}
}
App Check (Anti-Abuse)
App Check ensures only your legitimate apps can access Firebase.
Setup (Next.js + reCAPTCHA v3)
// lib/firebase/app-check.ts
import { initializeApp } from "firebase/app"
import { initializeAppCheck, ReCaptchaV3Provider } from "firebase/app-check"
const app = initializeApp(firebaseConfig)
// Client-side only
if (typeof window !== "undefined") {
if (process.env.NODE_ENV === "development") {
// Use debug token in dev
(window as any).FIREBASE_APPCHECK_DEBUG_TOKEN = process.env.NEXT_PUBLIC_APPCHECK_DEBUG_TOKEN
}
initializeAppCheck(app, {
provider: new ReCaptchaV3Provider(process.env.NEXT_PUBLIC_RECAPTCHA_SITE_KEY!),
isTokenAutoRefreshEnabled: true,
})
}
// Firestore rules: enforce App Check
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
// Require valid App Check token
allow read, write: if request.app.token.valid;
}
}
}
Service Account Best Practices
Use Application Default Credentials (ADC) — Not Key Files
// ❌ Don't do this in production
import admin from "firebase-admin"
const serviceAccount = require("./service-account.json") // KEY FILE
admin.initializeApp({ credential: admin.credential.cert(serviceAccount) })
// ✅ Use ADC (works on Cloud Run, GCE, Cloud Functions automatically)
admin.initializeApp()
// Or via environment variable:
// GOOGLE_APPLICATION_CREDENTIALS=/path/to/key.json (for local dev only)
// ✅ Or use individual env vars (safest for most Node.js hosts)
admin.initializeApp({
credential: admin.credential.cert({
projectId: process.env.FIREBASE_PROJECT_ID,
clientEmail: process.env.FIREBASE_CLIENT_EMAIL,
privateKey: process.env.FIREBASE_PRIVATE_KEY?.replace(/\\n/g, '\n'),
}),
})
Principle of Least Privilege
# In Google Cloud Console → IAM:
# Don't use "Owner" or "Editor" for service accounts
# Use specific roles:
roles/firebase.sdkAdminServiceAgent # Firebase Admin SDK
roles/datastore.user # Firestore read/write
roles/storage.objectAdmin # Storage admin
# NOT: roles/owner or roles/editor
Security Audit Checklist
Firestore Rules
- Rules are NOT in test mode (
allow read, write: if true) -
/{document=**}catch-all DENIES by default - All user-writable fields validated (type, size, allowed values)
- Users cannot write other users'
userIdfields - Sensitive fields (role, plan, stripeId) cannot be written by clients
- Rules deployed and tested with Firebase Emulator + Rules Playground
Authentication
- Email enumeration protection enabled (consistent error messages)
- Email verification required for sensitive actions
- Custom claims set server-side (never client-side)
- ID token verification on every Admin SDK call
Service Accounts
- No key files committed to git (
git log --all -- "*.json") - Minimum necessary IAM roles assigned
- Keys rotated regularly (or using ADC — no keys at all)
- Service account keys in secret manager (not plain env files)
App Check
- App Check enabled in Firebase Console
- Enforcement mode ON (not monitoring mode) for production
- Debug tokens for development only, not in production builds
Testing Rules with Firebase Emulator
# Install and run emulator
npm install -g firebase-tools
firebase emulators:start --only firestore,auth
# Run rules tests
npm install --save-dev @firebase/rules-unit-testing
// firestore.rules.test.ts
import { initializeTestEnvironment, assertFails, assertSucceeds } from "@firebase/rules-unit-testing"
const env = await initializeTestEnvironment({
projectId: "test-project",
firestore: { rules: fs.readFileSync("firestore.rules", "utf8") },
})
test("users can only read own profile", async () => {
const alice = env.authenticatedContext("alice")
const bob = env.authenticatedContext("bob")
await assertSucceeds(alice.firestore().doc("users/alice").get())
await assertFails(bob.firestore().doc("users/alice").get())
})
test("unauthenticated users cannot read any data", async () => {
const anon = env.unauthenticatedContext()
await assertFails(anon.firestore().doc("users/alice").get())
})
Key Rules
- NEVER
allow read, write: if truein production — this is the most common Firebase breach - Service account keys = production passwords — use ADC or secret manager
- Custom claims are server-side only — clients cannot set their own roles
- Validate data in rules — don't rely on client-side validation alone
request.resource.datavsresource.data— incoming vs existing data- Test with Firebase Emulator before deploying rules
- App Check in enforcement mode — monitoring mode does nothing to block abuse
- Catch-all deny rule at the bottom —
allow read, write: if false - Audit
get()calls in rules — they count toward your read quota - Rules Playground in Firebase Console — test rules without deploying