Write and validate Firestore Security Rules following the project's multi-tenancy Blueprint pattern. Use this skill when implementing collection-level security, Blueprint membership validation, role-based permissions, and data access controls. Ensures rules validate BlueprintMember status, check permissions array, enforce data isolation, and integrate with the three-layer architecture where Security Rules are the first line of defense.
Scanned 2/12/2026
Install via CLI
openskills install majiayu000/claude-skill-registry---
name: "Firestore Security Rules"
description: "Write and validate Firestore Security Rules following the project's multi-tenancy Blueprint pattern. Use this skill when implementing collection-level security, Blueprint membership validation, role-based permissions, and data access controls. Ensures rules validate BlueprintMember status, check permissions array, enforce data isolation, and integrate with the three-layer architecture where Security Rules are the first line of defense."
license: "MIT"
---
# Firestore Security Rules Skill
This skill helps create secure Firestore Security Rules following the project's multi-tenancy architecture.
## Core Principles
### Security-First Architecture
- **Security Rules are First Defense**: Never trust client-side checks alone
- **Multi-Tenancy**: Blueprint-based data isolation
- **Permission System**: Role + permissions array validation
- **Server-Side Validation**: All access validated at database level
### Blueprint Multi-Tenancy Model
```
User → Organization → Blueprint → Resources
↓
Team / Partner
```
- Blueprint defines permission boundaries (not data boundaries)
- BlueprintMember is dedicated collection for membership
- All resources belong to a Blueprint
- Access controlled via BlueprintMember role + permissions
## Core Helper Functions
### Global Authentication
```javascript
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
/**
* Check if user is authenticated
*/
function isAuthenticated() {
return request.auth != null;
}
/**
* Get current user ID
*/
function getCurrentUserId() {
return request.auth.uid;
}
/**
* Check if user is system admin
*/
function isSystemAdmin() {
return isAuthenticated() &&
get(/databases/$(database)/documents/users/$(getCurrentUserId())).data.role == 'admin';
}
}
}
```
### Blueprint Membership Functions
```javascript
/**
* Check if user is Blueprint member
*/
function isBlueprintMember(blueprintId) {
let memberId = getCurrentUserId() + '_' + blueprintId;
return exists(/databases/$(database)/documents/blueprintMembers/$(memberId));
}
/**
* Get user's role in Blueprint
*/
function getBlueprintMemberRole(blueprintId) {
let memberId = getCurrentUserId() + '_' + blueprintId;
let member = get(/databases/$(database)/documents/blueprintMembers/$(memberId));
return member.data.role;
}
/**
* Check if user is Owner or Admin
*/
function isBlueprintOwnerOrAdmin(blueprintId) {
let role = getBlueprintMemberRole(blueprintId);
return role in ['owner', 'admin'];
}
/**
* Check if user has specific permission
*/
function hasPermission(blueprintId, permission) {
let memberId = getCurrentUserId() + '_' + blueprintId;
let member = get(/databases/$(database)/documents/blueprintMembers/$(memberId));
return permission in member.data.permissions;
}
/**
* Check if member is active
*/
function isMemberActive(blueprintId) {
let memberId = getCurrentUserId() + '_' + blueprintId;
let member = get(/databases/$(database)/documents/blueprintMembers/$(memberId));
return member.data.status == 'active';
}
```
### Data Validation Functions
```javascript
/**
* Validate task data structure
*/
function validateTaskData(data) {
return data.keys().hasAll(['blueprintId', 'title', 'status']) &&
data.title is string &&
data.title.size() > 0 &&
data.title.size() <= 200 &&
data.status in ['pending', 'in-progress', 'completed', 'archived'];
}
```
## Collection Rules Pattern
### Resource Collection (with Blueprint)
```javascript
match /tasks/{taskId} {
// Blueprint members can read tasks
allow read: if isAuthenticated() &&
isBlueprintMember(resource.data.blueprintId) &&
isMemberActive(resource.data.blueprintId);
// Members with task:create permission can create
allow create: if isAuthenticated() &&
isBlueprintMember(request.resource.data.blueprintId) &&
isMemberActive(request.resource.data.blueprintId) &&
hasPermission(request.resource.data.blueprintId, 'task:create') &&
validateTaskData(request.resource.data);
// Members with task:update permission or assignee can update
allow update: if isAuthenticated() &&
isBlueprintMember(resource.data.blueprintId) &&
isMemberActive(resource.data.blueprintId) &&
(hasPermission(resource.data.blueprintId, 'task:update') ||
resource.data.assignedTo == getCurrentUserId()) &&
validateTaskData(request.resource.data);
// Only members with task:delete permission can delete
allow delete: if isAuthenticated() &&
isBlueprintMember(resource.data.blueprintId) &&
isMemberActive(resource.data.blueprintId) &&
hasPermission(resource.data.blueprintId, 'task:delete');
}
```
### Nested Subcollections
```javascript
match /blueprints/{blueprintId} {
allow read: if isAuthenticated() && isBlueprintMember(blueprintId);
// Nested tasks subcollection
match /tasks/{taskId} {
allow read: if isAuthenticated() &&
isBlueprintMember(blueprintId) &&
isMemberActive(blueprintId);
allow create: if isAuthenticated() &&
isBlueprintMember(blueprintId) &&
isMemberActive(blueprintId) &&
hasPermission(blueprintId, 'task:create') &&
validateTaskData(request.resource.data) &&
request.resource.data.blueprintId == blueprintId;
}
}
```
### Membership Collection
```javascript
match /blueprintMembers/{memberId} {
// Blueprint members can read member list
allow read: if isAuthenticated() &&
isBlueprintMember(resource.data.blueprintId);
// Only Owner/Admin can add/modify/remove members
allow create, update: if isAuthenticated() &&
isBlueprintOwnerOrAdmin(resource.data.blueprintId);
// Only Owner can delete members (soft delete recommended)
allow delete: if isAuthenticated() &&
isBlueprintOwnerOrAdmin(resource.data.blueprintId);
}
```
### User Collection
```javascript
match /users/{userId} {
// Users can only read their own data
allow read: if isAuthenticated() && getCurrentUserId() == userId;
// Users can update their own data (but not role)
allow update: if isAuthenticated() &&
getCurrentUserId() == userId &&
!request.resource.data.diff(resource.data).affectedKeys().hasAny(['role']);
// Only system admin can create/delete users
allow create, delete: if isSystemAdmin();
}
```
### Audit Logs (Write-Only)
```javascript
match /auditLogs/{logId} {
// Only system admin can read logs
allow read: if isSystemAdmin();
// Any authenticated user can write logs
allow create: if isAuthenticated();
// Logs cannot be modified or deleted
allow update, delete: if false;
}
```
## Performance Optimization
### Minimize get() Calls
```javascript
// ❌ BAD: Multiple get() calls
function hasMultiplePermissions(blueprintId) {
let memberId = getCurrentUserId() + '_' + blueprintId;
let member = get(/databases/$(database)/documents/blueprintMembers/$(memberId));
let canCreate = 'task:create' in member.data.permissions;
let member2 = get(/databases/$(database)/documents/blueprintMembers/$(memberId)); // Duplicate!
let canUpdate = 'task:update' in member2.data.permissions;
return canCreate && canUpdate;
}
// ✅ GOOD: Single get() call
function hasMultiplePermissions(blueprintId) {
let memberId = getCurrentUserId() + '_' + blueprintId;
let member = get(/databases/$(database)/documents/blueprintMembers/$(memberId));
return 'task:create' in member.data.permissions &&
'task:update' in member.data.permissions;
}
```
### Use exists() When Possible
```javascript
// ✅ GOOD: exists() is faster than get()
function isBlueprintMember(blueprintId) {
let memberId = getCurrentUserId() + '_' + blueprintId;
return exists(/databases/$(database)/documents/blueprintMembers/$(memberId));
}
// Only use get() when you need data
function getBlueprintMemberRole(blueprintId) {
let memberId = getCurrentUserId() + '_' + blueprintId;
let member = get(/databases/$(database)/documents/blueprintMembers/$(memberId));
return member.data.role;
}
```
## Testing Security Rules
### Using Firebase Emulator
```bash
# Start emulator
firebase emulators:start
# Run security rules tests
npm run test:rules
```
### Test Template
```javascript
describe('Task Collection Security Rules', () => {
it('should allow authenticated blueprint member to read tasks', async () => {
const db = testEnv.authenticatedContext('user1').firestore();
// Setup: Create member
await testEnv.withSecurityRulesDisabled(async (context) => {
await context.firestore().doc('blueprintMembers/user1_blueprint1').set({
blueprintId: 'blueprint1',
userId: 'user1',
role: 'member',
status: 'active',
permissions: ['task:read']
});
});
// Test: Read should succeed
await assertSucceeds(db.doc('tasks/task1').get());
});
it('should deny unauthenticated access', async () => {
const db = testEnv.unauthenticatedContext().firestore();
await assertFails(db.doc('tasks/task1').get());
});
});
```
## Checklist
When writing Security Rules:
- [ ] All collections have security rules
- [ ] Multi-tenancy data isolation implemented
- [ ] BlueprintMember membership checked
- [ ] Permissions array validated
- [ ] Member status checked (active/suspended)
- [ ] Data validation functions complete
- [ ] Minimize get() calls for performance
- [ ] Use exists() when only checking existence
- [ ] Audit logs are write-only
- [ ] Cross-blueprint access prevented
- [ ] Tests cover all scenarios
- [ ] Both positive and negative tests
## Integration with Architecture
### Three-Layer Security
```
1. UI Layer: Permission checks for UX (can hide buttons)
2. Service Layer: Business logic validation
3. Security Rules: Server-side enforcement (MUST)
```
### Repository + Security Rules
```typescript
// Repository - No permission checks
async deleteTask(taskId: string): Promise<void> {
await this.taskRepository.delete(taskId);
// Security Rules will validate permissions
}
```
```javascript
// Security Rules - Permission enforcement
match /tasks/{taskId} {
allow delete: if isAuthenticated()
&& isBlueprintMember(resource.data.blueprintId)
&& hasPermission(resource.data.blueprintId, 'task:delete');
}
```
## References
- [Security Rules Instructions](.github/instructions/ng-gighub-security-rules.instructions.md)
- [Architecture Guide](.github/instructions/ng-gighub-architecture.instructions.md)
- [Firebase Documentation](https://firebase.google.com/docs/firestore/security/get-started)
No comments yet. Be the first to comment!