Documentation Index
Fetch the complete documentation index at: https://docs.snackbase.dev/llms.txt
Use this file to discover all available pages before exploring further.
SnackBase provides a comprehensive security model with role-based access control, field-level permissions, and a powerful rule engine. This guide explains the security architecture, authorization flows, and best practices.
Overview
SnackBase security operates on multiple layers to ensure data protection:
| Layer | Purpose | Mechanism |
|---|
| Authentication | Verify user identity | JWT tokens, Argon2id password hashing |
| Account Isolation | Separate tenant data | Row-level filtering via account_id |
| Authorization | Control user actions | RBAC + Permission system |
| Field-Level Security | Hide sensitive data | Field-level access control |
| Audit Logging | Track all actions | Immutable audit logs (coming soon) |
Authentication vs Authorization
Understanding the distinction is critical:
| Aspect | Authentication | Authorization |
|---|
| Question | Who are you? | What can you do? |
| Mechanism | JWT tokens, passwords | Roles, permissions, rules |
| Timing | Once per session | Every request |
| Failure Result | 401 Unauthorized | 403 Forbidden |
Example Scenario
Authentication (Who are you?):
├── User provides credentials
├── System verifies identity
└── Result: "You are [email protected]"
Authorization (What can you do?):
├── User requests DELETE /api/v1/posts/123
├── System checks permissions
├── User has "editor" role
├── Editor role does NOT have "delete" permission
└── Result: 403 Forbidden - "You cannot delete posts"
Role-Based Access Control (RBAC)
SnackBase uses RBAC as the foundation of authorization.
RBAC Hierarchy
Default Roles
| Role | Description | Typical Permissions |
|---|
| admin | Full administrative access | All operations on all collections |
| editor | Content creator/manager | Create, Read, Update on specific collections |
| viewer | Read-only access | Read on specific collections |
Custom Roles
You can create custom roles for any purpose:
{
"name": "moderator",
"description": "Can moderate user-generated content",
"permissions": [
{
"collection": "comments",
"create": false,
"read": true,
"update": true,
"delete": true
},
{
"collection": "users",
"create": false,
"read": true,
"update": false,
"delete": false
}
]
}
Permission System
Permissions define what operations a user can perform on which collections.
Permission Matrix
For a role with permissions:
| Collection | Create | Read | Update | Delete |
|---|
| posts | ✅ | ✅ | ✅ | ❌ |
| comments | ✅ | ✅ | ✅ | ✅ |
| users | ❌ | ✅ | ❌ | ❌ |
Permission Structure
{
"id": "perm_abc123",
"role_id": "role_editor",
"collection": "posts",
"create": true,
"read": true,
"update": true,
"delete": false,
"fields": ["title", "content", "status"],
"rules": {
"create": "@has_role('editor')",
"update": "@owns_record() or @has_role('admin')"
}
}
Wildcard Collections
Use * to grant permissions on all collections:
{
"role": "admin",
"collection": "*",
"create": true,
"read": true,
"update": true,
"delete": true
}
This grants admin full access to ALL collections, including future ones.
Permission Caching
Permissions are cached for 5 minutes to improve performance:
┌──────────────────┐
│ First Request │
│ Check permissions│
│ from database │
└────────┬─────────┘
│
▼
┌──────────────────┐
│ Cache for 5 min │
│ Subsequent │
│ requests use │
│ cached perms │
└────────┬─────────┘
│
▼
┌──────────────────┐
│ After 5 min or │
│ permission change│
│ Cache invalidated│
└──────────────────┘
Rule Engine
SnackBase includes a powerful rule engine for fine-grained access control.
Rule Syntax
Rules use a custom DSL (Domain Specific Language):
# Simple comparisons
user.id == "user_abc123"
user.email == "[email protected]"
# Role checks
@has_role("admin")
@has_any_role(["admin", "moderator"])
# Record ownership
@owns_record()
# Field comparisons
status in ["draft", "published"]
priority >= 3
# Logical operators
@has_role("admin") or @owns_record()
@has_role("editor") and status == "draft"
not status == "archived"
# Complex expressions
(@has_role("admin") or @owns_record()) and not status == "locked"
Built-in Functions
| Function | Description | Example |
|---|
@has_role(role) | User has specific role | @has_role("admin") |
@has_any_role([roles]) | User has any of these roles | @has_any_role(["admin", "moderator"]) |
@owns_record() | User created this record | @owns_record() |
@is_superadmin() | User is superadmin | @is_superadmin() |
Rule Evaluation Context
Rules have access to:
| Variable | Description | Example |
|---|
user | Current user object | user.id, user.email |
record | Record being accessed | record.created_by, record.status |
context | Request context | context.account_id |
Permission Rules Example
{
"collection": "posts",
"update": true,
"rules": {
"update": "(@owns_record() and status in ['draft', 'pending']) or @has_role('admin')"
}
}
Translation: Users can update posts if:
- They created the post AND status is draft/pending, OR
- They have admin role
Field-Level Security
SnackBase supports field-level access control to hide sensitive data.
Field Visibility
Restrict which fields a role can see:
{
"role": "viewer",
"collection": "users",
"read": true,
"fields": ["name", "email"],
"excluded_fields": ["phone", "ssn", "salary"]
}
Users with this role will receive:
// Response (excluded fields filtered out)
{
"id": "user_abc123",
"name": "Alice Johnson",
"email": "[email protected]"
// phone, ssn, salary NOT included
}
Field-Level Rules
Apply rules to specific fields:
{
"collection": "users",
"field_rules": {
"salary": {
"read": "@has_role('admin') or @owns_record()",
"write": "@has_role('hr') or @is_superadmin()"
},
"email": {
"read": "true",
"write": "@has_role('admin')"
}
}
}
Account Isolation
Account isolation is the foundation of SnackBase security.
Multi-Tenant Isolation
All data is automatically isolated by account_id:
-- User from AB1001 requests posts
SELECT * FROM posts WHERE account_id = 'AB1001';
-- User from XY2048 requests posts
SELECT * FROM posts WHERE account_id = 'XY2048';
Users cannot see or access data from other accounts.
Enforcement Layers
Account isolation is enforced at multiple layers:
| Layer | Mechanism | Example |
|---|
| Database | account_id column in WHERE clause | WHERE account_id = ? |
| Repository | Automatic filtering in queries | posts.find_all(context) |
| API Middleware | Validates account in token | Token contains account_id |
| Hooks | Built-in account_isolation_hook | Cannot be disabled |
Cross-Account Access Prevention
Attempting to access another account’s data:
# User from AB1001 tries to access XY2048 data
GET /api/v1/posts?account_id=XY2048
# Result: 403 Forbidden
# The account_id filter is overridden and reset to AB1001
The system ignores malicious account_id parameters.
Security Best Practices
1. Principle of Least Privilege
Grant minimum required permissions:
// ❌ Too permissive
{
"role": "viewer",
"collection": "*",
"delete": true // Viewers shouldn't delete!
}
// ✅ Correct
{
"role": "viewer",
"collection": "posts",
"read": true,
"create": false,
"update": false,
"delete": false
}
2. Use Rules for Fine-Grained Control
Leverage the rule engine for complex scenarios:
{
"rules": {
"update": "@owns_record() or @has_role('admin')",
"delete": "@has_role('admin') and not record.status == 'locked'"
}
}
3. Implement Field-Level Security
Hide sensitive fields by default:
{
"collection": "users",
"excluded_fields": ["password_hash", "ssn", "salary"]
}
4. Regular Permission Audits
Periodically review and update permissions:
- Remove unused roles
- Tighten overly permissive rules
- Document permission rationale
- Use audit logs (when available) to track access
5. Use Wildcards Carefully
Wildcard permissions (*) are powerful but dangerous:
// ⚠️ Use with caution
{
"collection": "*",
"delete": true // Can delete from ALL collections!
}
// ✅ Prefer explicit collections
{
"collection": "posts",
"delete": true
}
6. Test Permission Changes
Always test permission changes in development:
def test_editor_cannot_delete_posts():
editor_user = create_user(role="editor")
client = login_as(editor_user)
response = client.delete("/api/v1/posts/123")
assert response.status_code == 403
7. Monitor and Alert
Monitor for suspicious activity:
- Repeated failed authorization attempts
- Unusual access patterns
- Permission escalation attempts
- Cross-account access attempts
Common Security Scenarios
Scenario 1: User Can Only Edit Their Own Posts
{
"role": "author",
"collection": "posts",
"create": true,
"read": true,
"update": true,
"delete": true,
"rules": {
"update": "@owns_record()",
"delete": "@owns_record() and not status == 'published'"
}
}
{
"role": "moderator",
"collection": "comments",
"create": false,
"read": true,
"update": true,
"delete": true,
"field_rules": {
"author_ip": {
"read": "@has_role('admin')"
}
}
}
Scenario 3: Public Read, Private Write
{
"role": "anonymous",
"collection": "posts",
"read": true,
"create": false,
"update": false,
"delete": false,
"excluded_fields": ["draft_notes", "internal_status"]
}
SnackBase implements defense-in-depth security by automatically setting HTTP security headers on all responses.
All responses include the following security headers:
| Header | Value | Purpose |
|---|
| X-Content-Type-Options | nosniff | Prevents MIME type sniffing attacks |
| X-Frame-Options | DENY | Prevents clickjacking by blocking iframe embedding |
| X-XSS-Protection | 1; mode=block | Enables browser XSS protection (legacy browsers) |
| Strict-Transport-Security | max-age=31536000; includeSubDomains | Enforces HTTPS (production only) |
| Content-Security-Policy | Configurable | Prevents XSS and injection attacks |
| Permissions-Policy | Configurable | Restricts browser features |
| Referrer-Policy | strict-origin-when-cross-origin | Controls referrer information |
Content Security Policy (CSP)
The default CSP is designed for maximum security while supporting the Admin UI:
default-src 'self';
script-src 'self';
style-src 'self' 'unsafe-inline';
img-src 'self' data:;
font-src 'self';
connect-src 'self';
frame-ancestors 'none'
Key Directives:
default-src 'self': Only load resources from same origin
script-src 'self': Block inline scripts and external scripts
style-src 'self' 'unsafe-inline': Allow inline styles (for React)
img-src 'self' data:: Allow images from same origin and data URIs
frame-ancestors 'none': Prevent iframe embedding
Customization
Customize security headers via environment variables:
# Customize CSP for external CDN
SNACKBASE_CSP_POLICY="default-src 'self'; script-src 'self' https://cdn.example.com"
# Adjust HSTS max-age
SNACKBASE_HSTS_MAX_AGE=63072000 # 2 years
# Customize Permissions Policy
SNACKBASE_PERMISSIONS_POLICY="geolocation=(), camera=(), microphone=()"
Environment-Aware Behavior
Security headers adapt to the environment:
| Environment | HSTS Header | HTTPS Redirect |
|---|
| Development | ❌ Not set | ❌ Disabled |
| Production | ✅ Set with max-age | ⚙️ Optional (configurable) |
This prevents HSTS issues during local development while enforcing HTTPS in production.
Summary
| Concept | Key Takeaway |
|---|
| Security Layers | Authentication → Account Isolation → Authorization → Field-Level Security → Audit |
| Authentication vs Authorization | Authentication = Who are you? Authorization = What can you do? |
| RBAC | Users → Roles → Permissions → Collections |
| Permission System | CRUD permissions per collection, wildcard support, 5-minute cache |
| Rule Engine | Custom DSL for fine-grained control with built-in functions |
| Field-Level Security | Hide sensitive fields, field-specific rules |
| Account Isolation | Automatic via account_id, enforced at multiple layers |
| Best Practices | Least privilege, use rules, hide sensitive data, audit permissions |