Security Guide
Security is a fundamental concern in ObjectOS. This guide covers authentication, authorization, permissions, and security best practices for building secure applications.
Security Architecture
ObjectOS implements a multi-layered security model aligned with OWASP Top 10 recommendations:
- Authentication (
@objectos/auth): Verify user identity via Better-Auth - Authorization (
@objectos/permissions): Object, field, and record-level access control - Field-Level Security: Control access to specific fields via permission sets
- Record-Level Security: Filter records via SharingRuleEngine and RLSEvaluator
- Audit Logging (
@objectos/audit): Track all data changes with 34+ event types - Security Headers: OWASP-compliant HTTP headers via Hono
secureHeadersmiddleware
Authentication
Using Better-Auth
ObjectOS uses Better-Auth via the @objectos/auth plugin (BetterAuthPlugin), supporting multiple strategies:
- Local (email/password)
- OAuth 2.0 (Google, GitHub, Microsoft)
- SAML (Enterprise SSO)
- LDAP
- Magic Links
Setup Authentication
Authentication is configured as a plugin in objectstack.config.ts:
import { BetterAuthPlugin } from '@objectos/auth';
export default {
plugins: [
new BetterAuthPlugin({
// Optional security policies
securityPolicies: {
password: { minLength: 12, maxLength: 128 },
session: { expiresIn: '7d', updateAge: '1d' },
},
}),
// ... other plugins
],
};
Login Endpoint
All auth routes are under /api/v1/auth/*:
POST /api/v1/auth/sign-up/email
Content-Type: application/json
{
"name": "John Doe",
"email": "user@example.com",
"password": "securepassword"
}
POST /api/v1/auth/sign-in/email
Content-Type: application/json
{
"email": "user@example.com",
"password": "securepassword"
}
Session Management
Better-Auth manages sessions via HTTP-only cookies. The Admin Console (apps/web) uses better-auth/react for session handling:
import { createAuthClient } from 'better-auth/react';
export const authClient = createAuthClient({
baseURL: '/api/v1/auth',
});
Authorization
Object-Level Permissions
Control CRUD access to entire objects using YAML permission sets loaded by @objectos/permissions:
# permissions/contacts.permission.yml
name: contact-permissions
objectName: contacts
profiles:
admin:
allowRead: true
allowCreate: true
allowEdit: true
allowDelete: true
sales:
allowRead: true
allowCreate: true
allowEdit: true
allowDelete: false
guest:
allowRead: true
allowCreate: false
allowEdit: false
allowDelete: false
Permission Checking
ObjectOS automatically checks permissions on all data operations via kernel hooks (data.beforeCreate, data.beforeUpdate, data.beforeDelete, data.beforeFind):
// The PermissionsPlugin hooks into data.before* events automatically
// A user without the required profile will receive a 403 error:
// Error: Permission denied for delete on contacts (code: PERMISSION_DENIED)
Programmatic Permission Checks
Use the REST API to check permissions:
POST /api/v1/permissions/check
Content-Type: application/json
{
"userId": "user_123",
"profileName": "sales",
"objectName": "contacts",
"action": "delete"
}
Response:
{
"success": true,
"data": { "hasPermission": false }
}
Field-Level Security
Control access to specific fields:
fields:
salary:
type: currency
label: Salary
visible_to: ['hr', 'admin'] # Only HR and admin can see
editable_by: ['hr'] # Only HR can edit
social_security:
type: text
label: SSN
visible_to: ['hr'] # Only HR can see
editable_by: [] # No one can edit (read-only)
Behavior:
- Users without
visible_toroles cannot see the field in queries or forms - Users without
editable_byroles cannot update the field - Attempts to read/write restricted fields return 403 Forbidden
Record-Level Security (RLS)
The @objectos/permissions plugin provides RLSEvaluator and SharingRuleEngine for record-level security:
Organization-Wide Defaults
Define default access levels per object:
const rlsConfig = {
organizationWideDefaults: {
accounts: 'private', // Only owner can see
contacts: 'publicRead', // Everyone can read
opportunities: 'private',
},
};
Sharing Rules
Define sharing rules in YAML metadata:
# permissions/opportunities.permission.yml
sharingRules:
- name: Team Access
type: criteria
criteria:
team: $currentUser.teamId
accessLevel: readWrite
- name: Manager Access
type: owner
ownerField: reports_to
accessLevel: readOnly
The SharingRuleEngine evaluates these rules during data.beforeFind to inject record-level filters automatically.
Roles and Profiles
Defining Roles
Roles are defined in the auth system:
# objects/_roles.object.yml
name: _roles
label: Role
system: true
records:
- name: admin
label: Administrator
description: Full system access
- name: sales
label: Sales User
description: Sales team member
- name: support
label: Support User
description: Customer support
- name: guest
label: Guest
description: Limited read-only access
Assigning Roles
// Assign role to user
await kernel.update('users', userId, {
roles: ['sales', 'support'],
});
Role Hierarchy
Define role inheritance:
role_hierarchy:
admin:
inherits: [sales, support, guest]
sales:
inherits: [guest]
support:
inherits: [guest]
Input Validation & Sanitization
Automatic Validation
ObjectOS automatically validates:
- Required fields
- Data types
- Unique constraints
- Min/max values
- Pattern matching
Custom Validation
Add custom validation in hooks:
kernel.on('beforeInsert:contacts', async (ctx) => {
// Validate email domain
if (ctx.data.email && !ctx.data.email.endsWith('@company.com')) {
throw new Error('Only company email addresses allowed');
}
// Sanitize phone number
if (ctx.data.phone) {
ctx.data.phone = ctx.data.phone.replace(/[^0-9+]/g, '');
}
});
SQL Injection Prevention
ObjectOS drivers automatically parameterize queries:
// Safe - parameters are escaped
await kernel.find('contacts', {
filters: {
email: userInput, // Automatically sanitized
},
});
XSS Prevention
Sanitize HTML content:
import { sanitizeHtml } from '@objectos/security';
kernel.on('beforeInsert', async (ctx) => {
if (ctx.data.description) {
ctx.data.description = sanitizeHtml(ctx.data.description);
}
});
Audit Logging
The @objectos/audit plugin (AuditLogPlugin) automatically records all data operations and 34+ event types:
Configuration
import { AuditLogPlugin } from '@objectos/audit';
new AuditLogPlugin({
enabled: true,
trackFieldChanges: true,
excludedFields: ['password', 'token', 'secret'],
retention: {
enabled: true,
defaultRetentionDays: 90,
},
});
Querying Audit Events
GET /api/v1/audit/events?objectName=contacts&userId=user_123
Audit Trail for a Record
GET /api/v1/audit/trail/contacts/contact_123
Field History
GET /api/v1/audit/field-history/contacts/contact_123/status
Event Types Covered
- Data events:
data.create,data.update,data.delete,data.read - Auth events:
auth.login,auth.logout,auth.login_failed,auth.session_created,auth.password_changed, etc. - Authorization events:
authz.role_assigned,authz.permission_granted,authz.role_created, etc. - System events:
system.config_changed,system.plugin_installed, etc. - Security events:
security.access_denied,security.suspicious_activity, etc. - Job events:
job.enqueued,job.completed,job.failed, etc.
Rate Limiting
Prevent abuse with rate limiting:
import { RateLimitPlugin } from '@objectos/plugin-rate-limit';
kernel.use(
RateLimitPlugin({
windowMs: 60 * 1000, // 1 minute
maxRequests: 100, // 100 requests per window
perUser: true, // Rate limit per user
}),
);
CORS Configuration
CORS is configured in objectstack.config.ts:
export default {
server: {
cors: {
origin: process.env.CORS_ORIGINS?.split(',') || [
'http://localhost:5321',
'http://localhost:5320',
],
credentials: true,
},
},
};
For the Vercel serverless deployment, CORS is applied via Hono middleware in api/index.ts.
API Key Authentication
For server-to-server communication:
kernel.use(
ApiKeyPlugin({
keys: [
{
key: process.env.API_KEY_1,
name: 'Integration Server',
scopes: ['read:contacts', 'write:contacts'],
},
],
}),
);
Usage:
GET /api/data/contacts
X-API-Key: your-api-key-here
Security Headers (OWASP Compliance)
ObjectOS uses Hono's built-in secureHeaders middleware for OWASP-compliant security headers:
import { secureHeaders } from 'hono/secure-headers';
honoApp.use(
'/api/v1/*',
secureHeaders({
contentSecurityPolicy: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'"],
styleSrc: ["'self'", "'unsafe-inline'"],
imgSrc: ["'self'", 'data:', 'https:'],
connectSrc: ["'self'"],
fontSrc: ["'self'"],
objectSrc: ["'none'"],
frameAncestors: ["'none'"],
},
crossOriginResourcePolicy: 'same-origin',
referrerPolicy: 'strict-origin-when-cross-origin',
xContentTypeOptions: 'nosniff',
xFrameOptions: 'DENY',
}),
);
Response Headers Applied
| Header | Value | OWASP Reference |
|---|---|---|
Content-Security-Policy | default-src 'self'; ... | A05:2021 – Security Misconfiguration |
X-Frame-Options | DENY | Clickjacking protection |
X-Content-Type-Options | nosniff | MIME-type sniffing prevention |
Referrer-Policy | strict-origin-when-cross-origin | Information leakage prevention |
Cross-Origin-Resource-Policy | same-origin | Cross-origin isolation |
Encryption
Encrypting Sensitive Fields
import { encrypt, decrypt } from '@objectos/security';
kernel.on('beforeInsert', async (ctx) => {
// Encrypt SSN before storing
if (ctx.data.social_security) {
ctx.data.social_security = encrypt(ctx.data.social_security);
}
});
kernel.on('afterFind', async (ctx) => {
// Decrypt SSN when retrieving
ctx.result.forEach((record) => {
if (record.social_security) {
record.social_security = decrypt(record.social_security);
}
});
});
Environment Variables
Never hard-code secrets:
# .env
DATABASE_URL=postgresql://...
JWT_SECRET=your-secret-key-here
ENCRYPTION_KEY=your-encryption-key-here
API_KEY=your-api-key-here
// ❌ BAD
const secret = 'my-secret-key';
// ✅ GOOD
const secret = process.env.JWT_SECRET;
Security Checklist
Development
- Use environment variables for secrets
- Enable strict TypeScript mode
- Validate all user input
- Use parameterized queries (automatic with ObjectQL drivers)
- Implement proper error handling
- Don't expose stack traces to clients
Production
- Set OWASP security headers (
secureHeadersmiddleware) - Configure CORS with explicit origin allowlist
- Enable audit logging (34+ event types)
- Implement session management (Better-Auth cookies)
- Use HTTPS only
- Enable rate limiting
- Regular security audits
- Keep dependencies updated
- Enable database encryption at rest
Common Security Patterns
1. Multi-Factor Authentication
kernel.use(
MFAPlugin({
enabled: true,
methods: ['totp', 'sms'],
required_for_roles: ['admin'],
}),
);
2. Password Policies
kernel.use(
PasswordPolicyPlugin({
minLength: 12,
requireUppercase: true,
requireLowercase: true,
requireNumbers: true,
requireSpecialChars: true,
preventReuse: 5, // Can't reuse last 5 passwords
expiryDays: 90, // Force reset every 90 days
}),
);
3. Session Management
kernel.use(
SessionPlugin({
maxAge: 24 * 60 * 60 * 1000, // 24 hours
rolling: true, // Refresh on activity
secure: true, // HTTPS only
httpOnly: true, // Not accessible via JavaScript
sameSite: 'strict',
}),
);
Testing Security
ObjectOS includes a full Auth → Permissions → Data → Audit integration test suite:
// packages/audit/test/integration.test.ts
describe('Integration: Auth → Permissions → Data → Audit', () => {
it('should trace a full CRUD lifecycle in audit', async () => {
// 1. Login event → recorded in audit
await triggerHook(hooks, 'auth.login', {
userId: 'user-1',
userName: 'Alice',
ipAddress: '10.0.0.1',
});
// 2. Permission check → passes via data.beforeCreate hook
await triggerHook(hooks, 'data.beforeCreate', {
objectName: 'orders',
userId: 'user-1',
userProfiles: ['admin'],
});
// 3. Data operation → recorded in audit with field changes
await triggerHook(hooks, 'data.create', {
objectName: 'orders',
recordId: 'ord-1',
userId: 'user-1',
});
// 4. Verify complete audit trail
const trail = await audit.getAuditTrail('orders', 'ord-1');
expect(trail.length).toBeGreaterThan(0);
});
});
Performance Baseline
All CRUD operations are benchmarked to meet P95 < 100 ms:
// packages/audit/test/performance.test.ts
describe('Performance Baseline', () => {
it('data.create pipeline P95 should be < 100 ms', async () => {
// Runs 100 iterations through Permission + Audit pipeline
// Measures and asserts P95 latency
});
});
Security Resources
Related Documentation
- Logic Hooks - Implement custom security logic
- SDK Reference - API reference
- HTTP Protocol - Authentication headers