92 lines
3.1 KiB
Markdown
92 lines
3.1 KiB
Markdown
---
|
|
title: Default to Locked Rules, Open Explicitly
|
|
impact: CRITICAL
|
|
impactDescription: Defense in depth, prevents accidental data exposure
|
|
tags: api-rules, security, defaults, best-practices
|
|
---
|
|
|
|
## Default to Locked Rules, Open Explicitly
|
|
|
|
New collections should start with locked (null) rules and explicitly open only what's needed. This prevents accidental data exposure and follows the principle of least privilege.
|
|
|
|
**Incorrect (starting with open rules):**
|
|
|
|
```javascript
|
|
// Dangerous: copying rules from examples without thinking
|
|
const collection = {
|
|
name: 'user_settings',
|
|
listRule: '', // Open - leaks all user settings!
|
|
viewRule: '', // Open - anyone can view any setting
|
|
createRule: '', // Open - no auth required
|
|
updateRule: '', // Open - anyone can modify!
|
|
deleteRule: '' // Open - anyone can delete!
|
|
};
|
|
|
|
// Also dangerous: using auth check when ownership needed
|
|
const collection = {
|
|
name: 'private_notes',
|
|
listRule: '@request.auth.id != ""', // Any logged-in user sees ALL notes
|
|
viewRule: '@request.auth.id != ""',
|
|
updateRule: '@request.auth.id != ""', // Any user can edit ANY note!
|
|
};
|
|
```
|
|
|
|
**Correct (locked by default, explicitly opened):**
|
|
|
|
```javascript
|
|
// Step 1: Start locked
|
|
const collection = {
|
|
name: 'user_settings',
|
|
listRule: null, // Locked - superusers only
|
|
viewRule: null,
|
|
createRule: null,
|
|
updateRule: null,
|
|
deleteRule: null
|
|
};
|
|
|
|
// Step 2: Open only what's needed with proper checks
|
|
const collection = {
|
|
name: 'user_settings',
|
|
// Users can only see their own settings
|
|
listRule: 'user = @request.auth.id',
|
|
viewRule: 'user = @request.auth.id',
|
|
// Users can only create settings for themselves
|
|
createRule: '@request.auth.id != "" && @request.body.user = @request.auth.id',
|
|
// Users can only update their own settings
|
|
updateRule: 'user = @request.auth.id',
|
|
// Prevent deletion or restrict to owner
|
|
deleteRule: 'user = @request.auth.id'
|
|
};
|
|
|
|
// For truly public data, document why it's open
|
|
const collection = {
|
|
name: 'public_announcements',
|
|
// Intentionally public - these are site-wide announcements
|
|
listRule: '',
|
|
viewRule: '',
|
|
// Only admins can manage (using custom "role" field on auth collection)
|
|
// IMPORTANT: Prevent role self-assignment in the users collection updateRule:
|
|
// updateRule: 'id = @request.auth.id && @request.body.role:isset = false'
|
|
createRule: '@request.auth.role = "admin"',
|
|
updateRule: '@request.auth.role = "admin"',
|
|
deleteRule: '@request.auth.role = "admin"'
|
|
};
|
|
```
|
|
|
|
**Rule development workflow:**
|
|
|
|
1. **Start locked** - All rules `null`
|
|
2. **Identify access needs** - Who needs what access?
|
|
3. **Write minimal rules** - Open only required operations
|
|
4. **Test thoroughly** - Verify both allowed and denied cases
|
|
5. **Document decisions** - Comment why rules are set as they are
|
|
|
|
**Security checklist:**
|
|
- [ ] No empty string rules without justification
|
|
- [ ] Ownership checks on personal data
|
|
- [ ] Auth checks on write operations
|
|
- [ ] Admin-only rules for sensitive operations
|
|
- [ ] Tested with different user contexts
|
|
|
|
Reference: [PocketBase API Rules](https://pocketbase.io/docs/api-rules-and-filters/)
|