Security Guide¶
Comprehensive security features for ORMDB applications.
Overview¶
ORMDB provides multiple layers of security:
- Row-Level Security (RLS) - Filter data access per user
- Capability Tokens - Fine-grained operation permissions
- Field Masking - Hide or redact sensitive fields
- Audit Logging - Track all data access
Row-Level Security (RLS)¶
Enabling RLS¶
Defining Policies¶
Policies filter which rows a user can access:
// Users can only see their own data
let policy = RlsPolicy::new("User", "user_own_data")
.with_filter(|ctx| {
FilterExpr::eq("id", ctx.user_id())
});
// Users can see posts from users they follow
let policy = RlsPolicy::new("Post", "followed_posts")
.with_filter(|ctx| {
FilterExpr::in_subquery("author_id",
"SELECT followed_id FROM Follow WHERE follower_id = ?",
vec![ctx.user_id()])
});
schema.add_policy(policy);
Policy Types¶
| Type | Description |
|---|---|
select |
Filter read queries |
insert |
Validate inserts |
update |
Filter and validate updates |
delete |
Filter deletes |
// Read-only policy
let policy = RlsPolicy::new("User", "public_profiles")
.select_only()
.with_filter(|_| FilterExpr::eq("public", Value::Bool(true)));
// Full access policy
let policy = RlsPolicy::new("Post", "own_posts")
.all_operations()
.with_filter(|ctx| FilterExpr::eq("author_id", ctx.user_id()));
Context Variables¶
Policies have access to request context:
let policy = RlsPolicy::new("Order", "tenant_orders")
.with_filter(|ctx| {
FilterExpr::and(vec![
FilterExpr::eq("tenant_id", ctx.get("tenant_id")),
FilterExpr::or(vec![
FilterExpr::eq("user_id", ctx.user_id()),
FilterExpr::eq("role", ctx.get("role")),
])
])
});
Client Usage¶
Capability Tokens¶
Fine-grained access control using cryptographic tokens.
Token Structure¶
pub struct CapabilityToken {
pub entity: String,
pub operations: Vec<Operation>,
pub filter: Option<Filter>,
pub fields: Option<Vec<String>>,
pub expires_at: i64,
pub signature: [u8; 32],
}
pub enum Operation {
Read,
Insert,
Update,
Delete,
}
Creating Tokens¶
// Read-only token for specific user's posts
let token = CapabilityToken::new("Post")
.allow_read()
.with_filter(FilterExpr::eq("author_id", user_id))
.expires_in(Duration::from_secs(3600))
.sign(&secret_key)?;
// Limited field access
let token = CapabilityToken::new("User")
.allow_read()
.with_fields(vec!["id", "name", "avatar_url"]) // No email
.expires_in(Duration::from_secs(3600))
.sign(&secret_key)?;
Using Tokens¶
Token Validation¶
Field Masking¶
Hide or redact sensitive fields based on context.
Defining Masks¶
// Completely hide field
let mask = FieldMask::new("User", "password_hash")
.hide();
// Redact to fixed value
let mask = FieldMask::new("User", "ssn")
.redact("***-**-****");
// Partial mask
let mask = FieldMask::new("User", "email")
.partial_mask(|value| {
let email = value.as_str()?;
let parts: Vec<&str> = email.split('@').collect();
if parts.len() == 2 {
let masked = format!("{}***@{}", &parts[0][..2], parts[1]);
Some(Value::String(masked))
} else {
None
}
});
// Conditional mask
let mask = FieldMask::new("User", "phone")
.when(|ctx| ctx.get("role") != "admin")
.redact("(***) ***-****");
schema.add_mask(mask);
Mask Types¶
| Type | Description | Example |
|---|---|---|
hide |
Field not returned | Field omitted from response |
redact |
Fixed replacement | "***-**-****" |
partial |
Custom transformation | "jo***@example.com" |
hash |
One-way hash | "a1b2c3..." |
Client Usage¶
// Without permissions: email is masked
const user = await client.query("User", {
filter: { field: "id", op: "eq", value: userId },
});
// user.email = "al***@example.com"
// With admin context: email is visible
const user = await client.query("User", {
filter: { field: "id", op: "eq", value: userId },
context: { role: "admin" },
});
// user.email = "alice@example.com"
Audit Logging¶
Track all data access and modifications.
Enabling Audit Logs¶
Audit Log Format¶
{
"timestamp": "2024-01-15T12:00:00Z",
"event": "query",
"user_id": "550e8400-...",
"entity": "User",
"operation": "read",
"filter": {"field": "status", "op": "eq", "value": "active"},
"result_count": 42,
"duration_ms": 5,
"ip_address": "192.168.1.100"
}
Audit Events¶
| Event | Description |
|---|---|
query |
Data read |
insert |
Entity created |
update |
Entity modified |
delete |
Entity deleted |
schema_change |
Schema modified |
auth_success |
Successful authentication |
auth_failure |
Failed authentication |
policy_violation |
RLS policy blocked access |
Querying Audit Logs¶
# Recent events
ormdb admin audit --since 1h
# Specific user
ormdb admin audit --user-id 550e8400-...
# Specific entity
ormdb admin audit --entity User --operation delete
Authentication Integration¶
JWT Authentication¶
// Validate JWT and extract claims
let claims = validate_jwt(&token, &public_key)?;
let ctx = SecurityContext::new()
.with_user_id(claims.sub)
.with_claims(claims.custom);
client.query_with_context(query, ctx).await?;
API Key Authentication¶
// Middleware example
async fn auth_middleware(req: Request, client: &Client) -> Result<SecurityContext> {
let api_key = req.header("X-API-Key")?;
let key_info = client.validate_api_key(api_key).await?;
Ok(SecurityContext::new()
.with_user_id(key_info.user_id)
.with_claims(key_info.permissions))
}
Best Practices¶
1. Principle of Least Privilege¶
// Bad: Broad access
let token = CapabilityToken::new("User")
.allow_all();
// Good: Specific access
let token = CapabilityToken::new("User")
.allow_read()
.with_fields(vec!["id", "name", "avatar_url"])
.with_filter(FilterExpr::eq("public", true));
2. Always Use RLS for Multi-Tenant Apps¶
// Ensure tenant isolation
let policy = RlsPolicy::new("*", "tenant_isolation")
.all_operations()
.with_filter(|ctx| FilterExpr::eq("tenant_id", ctx.get("tenant_id")));
3. Mask Sensitive Data by Default¶
// Mask PII fields
for field in ["email", "phone", "ssn", "address"] {
schema.add_mask(FieldMask::new("User", field)
.when(|ctx| !ctx.has_permission("view_pii"))
.partial_mask(mask_pii));
}
4. Rotate Capability Tokens¶
// Short-lived tokens
let token = CapabilityToken::new("Post")
.expires_in(Duration::from_secs(300)) // 5 minutes
.sign(&secret_key)?;
5. Monitor Audit Logs¶
# Alert on suspicious patterns
ormdb admin audit --since 1h \
--filter 'result_count > 1000 OR operation = "delete"' \
--alert slack
Security Hardening¶
ORMDB includes built-in protections against common attack vectors.
Query Budget Limits¶
Prevent resource exhaustion via complex queries:
# ormdb.toml
[security]
# Query depth limits by capability level
anonymous_max_depth = 2
authenticated_max_depth = 5
admin_max_depth = 20
# Entity limits per query
anonymous_max_entities = 100
authenticated_max_entities = 10000
admin_max_entities = 1000000
The server enforces budgets based on the client's capability level:
| Level | Max Depth | Max Entities | Max Edges |
|---|---|---|---|
| Anonymous | 2 | 100 | 500 |
| Authenticated | 5 | 10,000 | 50,000 |
| Privileged | 10 | 100,000 | 500,000 |
| Admin | 20 | 1,000,000 | 5,000,000 |
Queries exceeding these limits return a BUDGET_EXCEEDED error.
Message Size Limits¶
The protocol enforces a 4 MB maximum message size to prevent memory exhaustion attacks:
Parser Recursion Limits¶
The query parser enforces a maximum recursion depth of 100 to prevent stack overflow attacks from deeply nested expressions:
Field Count Limits¶
Entity payloads are limited to 10,000 fields to prevent denial-of-service attacks:
Error Sanitization¶
Internal error details are logged server-side but not exposed to clients:
This prevents information disclosure while maintaining debuggability.
Authentication Methods¶
ORMDB supports multiple authentication methods:
TLS Configuration¶
Enable TLS for encrypted connections:
# ormdb.toml
[tls]
enabled = true
cert_path = "/etc/ormdb/cert.pem"
key_path = "/etc/ormdb/key.pem"
ca_path = "/etc/ormdb/ca.pem" # For client cert verification
require_client_cert = false
Rate Limiting¶
Protect against abuse with rate limiting:
# ormdb.toml
[security]
rate_limit_enabled = true
rate_limit_requests_per_second = 1000
rate_limit_burst = 100
Connection Limits¶
Limit concurrent connections:
Security Checklist¶
- [ ] Enable RLS for multi-tenant applications
- [ ] Define policies for all sensitive entities
- [ ] Use capability tokens for external API access
- [ ] Mask PII fields (email, phone, SSN, etc.)
- [ ] Enable audit logging in production
- [ ] Rotate secrets and tokens regularly
- [ ] Use HTTPS/TLS for all connections
- [ ] Validate all user input
- [ ] Review security policies periodically
- [ ] Configure appropriate query budget limits
- [ ] Enable rate limiting in production
- [ ] Set reasonable connection limits
- [ ] Monitor audit logs for suspicious activity
Next Steps¶
- Multi-Tenant SaaS Example - Complete security implementation
- Operations Guide - Production deployment
- Performance Guide - Optimize secure queries