Permissions & Identity
Authentication, authorization, and record- and field-level access control in ObjectStack — a cross-protocol capability enforced by the ObjectStack runtime and declared as ObjectQL security metadata.
Permissions & Identity
This module covers authentication, authorization, and record- and field-level access control. It is a cross-protocol capability: enforced by the ObjectStack runtime, declared as ObjectQL security metadata.
The model has five concepts, one reading each (ADR-0090): additive
permission sets are the only capability container; flat positions
(岗位) distribute sets to people; the business-unit tree (plus the manager
chain) is the one hierarchy and decides how deep you see; each object's
Organization-Wide Default + sharing set the record baseline (sharing only
widens); row-level security is the expert escape hatch that narrows.
Field-level security masks and enforces field rules server-side (fields
without a declared rule stay visible by default — see the
FLS posture note).
Object access scopes go from own through own_and_reports, unit,
unit_and_below, to org. There is no Profile concept and the word "role"
is reserved (D3) — if you knew the v1 model, start at
Profiles (removed) and
Positions.
Access rules are metadata like everything else — this is a real sharing rule from the CRM example app:
export const HighValueOpportunitySharingRule = defineSharingRule({
type: 'criteria',
name: 'share_high_value_opps_with_managers',
label: 'High-Value Deals → Sales Managers',
description: 'Automatically share opportunities over $100,000 with all Sales Managers.',
object: 'crm_opportunity',
condition: 'record.amount > 100000',
accessLevel: 'edit',
sharedWith: { type: 'position', value: 'sales_manager' },
active: true,
});Because AI agents act through the same permission-aware surface, these rules bound agent access exactly as they bound users (Actions as Tools).
Implementation status — Permission Model v2 (ADR-0090) is live. REST → ObjectQL propagates a populated
ExecutionContext(userId, tenantId, positions, permissions, principalKind) into the SecurityPlugin middleware, so CRUD / FLS / RLS checks fire on every authenticated request; authenticated principals implicitly hold theeveryoneposition and anonymous principals holdguest(D9). Themember_defaultbaseline is additive (D5 — no fallback cliff) and its owner-write policies are domained toorg_memberholders. The defaultmember_defaultset ships the wildcard tenant-isolation RLS (organization_id == current_user.organization_id) with the same canonical-name contract as before; SecurityPlugin is the sole authority for tenant isolation, and analytics auto-bridges tosecurity.getReadFilter. Anonymous traffic is denied by default (ADR-0056 D2), and public forms self-authorize via a declaration-derivedpublicFormGrant. An unset OWD fails closed toprivate(D1) and the D7 publish linter makes it a build error. Criteria sharing rules (withposition/unit_and_subordinatesrecipients) are live and dogfood-proven; owner-type rules andgrouprecipients are declared but not enforced — the seeder skips them with a warning (Sharing Rules). RBAC-table writes are governed by the delegated-admin gate (D12), and thesecurityservice answersexplain(request)per evaluation layer (D6). The Studio RLS visual editor, per-user×org permission cache, and audit UI for denied access are queued. See Implementation Status for the latest matrix.
What's in this module
- Administrator Guide — the task-first manual for customer system administrators
- Authentication
- Social & Enterprise SSO
- Authorization Architecture
- Profiles (removed)
- Permission Sets
- Positions
- Sharing Rules
- Field-Level Security
- Permission Metadata
- Security Permissions Matrix
- Access Recipes
Spec: Security & Access Control · Schema reference: Security, Identity
Security Architecture
Authentication is the first layer. This guide covers authorization — who can see and do what once signed in (RBAC, RLS, FLS, sharing). Harden authentication first — password policy, enforced MFA, account lockout, session controls, and IP allowlist — in Enterprise Authentication Hardening.
ObjectStack implements a multi-layered security model:
┌─────────────────────────────────────┐
│ Application Visibility │ ← Which apps can users access?
├─────────────────────────────────────┤
│ Object Permissions │ ← CRUD on objects
├─────────────────────────────────────┤
│ Record-Level Security │ ← Which records can be accessed?
│ - Organization-Wide Defaults │
│ - Scope-Depth Grants (readScope) │
│ - Sharing Rules │
│ - Manual Sharing │
├─────────────────────────────────────┤
│ Field-Level Security │ ← Read/Write specific fields
└─────────────────────────────────────┘Security Layers
- Object Permissions: Control create, read, update, delete on entire objects
- Record-Level Security: Control access to specific records
- Field-Level Security: Control visibility and editability of fields
- Row-Level Security: Control access based on data values
Best Practices
1. Permission Sets
✅ DO:
- Keep each set a coherent capability ("Advanced Reporting", "HR Self-Service")
- Start restrictive — the model is additive, so grant by adding sets
- Put org-wide defaults on the
everyoneanchor (low-privilege only) - Remove grants when no longer needed
❌ DON'T:
- Author "subtraction sets" — restriction is done by not granting
- Grant
viewAllRecords/modifyAllRecords"just in case" - Ship a
'*'wildcard superuser in a package (the linter rejects it)
2. Positions
✅ DO:
- Model job functions (岗位), not departments — keep the list short and flat
- Bind capability through sets; anchor assignments to a business unit
- Use delegated
adminScopes so subsidiaries manage their own assignments
❌ DON'T:
- Recreate a reporting tree on positions — the hierarchy lives on business units
- Use positions as sharing groups — that is what teams are for
3. Business Units & Depth
✅ DO:
- Mirror your organizational structure on the BU tree
- Widen visibility with
readScope/writeScopedepth grants - Review the tree after reorgs
❌ DON'T:
- Create overly deep hierarchies
- Grant
orgdepth whenunit_and_belowsuffices
4. Sharing Rules
✅ DO:
- Start with most restrictive OWD
- Use sharing rules to open up access
- Document business justification
- Test thoroughly
❌ DON'T:
- Set OWD to Public unless necessary
- Create redundant sharing rules
- Grant more access than needed
5. Field-Level Security
✅ DO:
- Protect sensitive data (SSN, salary, etc.)
- Use read-only for calculated fields
- Document security classifications
- Audit regularly
❌ DON'T:
- Hide required fields
- Restrict access unnecessarily
- Forget about API access
Security Checklist
Initial Setup
- Harden authentication (password policy, enforced MFA, lockout, session controls, IP allowlist) — see Authentication Hardening
- Model the business-unit tree and positions (岗位)
- Author permission sets and bind them to positions
- Set each object's organization-wide default (unset = private, and a build error)
- Configure field-level security
- Create sharing rules
- Commit
access-matrix.jsonso capability changes become reviewable diffs
Ongoing Maintenance
- Review user access quarterly
- Audit permission changes
- Remove inactive users promptly
- Update sharing rules as needed
- Monitor security health checks
Compliance
- Document security model
- Maintain access request process
- Log security changes
- Conduct security reviews
- Train users on security policies
Real-World Example
Complete security setup for a sales team:
// 1. Organization-Wide Defaults (one OWDModel value per object)
const OrganizationDefaults = {
account: 'private',
opportunity: 'private',
contact: 'controlled_by_parent',
};
// 2. Positions (flat — the hierarchy lives on business units)
const positions = [
{ name: 'sales_manager', label: 'Sales Manager' },
{ name: 'sales_rep', label: 'Sales Rep' },
];
// 3. Permission set (bound to the sales_rep position at install/setup)
const SalesRepAccess = {
name: 'sales_rep_access',
objects: {
account: { allowCreate: true, allowRead: true, allowEdit: true, allowDelete: false, readScope: 'unit' },
opportunity: { allowCreate: true, allowRead: true, allowEdit: true, allowDelete: false },
},
fields: {
'account.annual_revenue': { readable: true, editable: false }, // Read-only
},
};
// 4. Sharing Rule (criteria type with a CEL condition)
const AccountSharingRule = {
name: 'high_value_accounts',
type: 'criteria',
object: 'account',
// Share high-value accounts with sales reps
condition: P`record.annual_revenue >= 1000000`,
sharedWith: { type: 'position', value: 'sales_rep' },
accessLevel: 'read',
};
// 5. Permission Set (systemPermissions is a string array)
const AdvancedReportingPermissionSet = {
name: 'advanced_reporting',
objects: {},
// For sales analysts
systemPermissions: ['run_reports', 'export_reports', 'view_all_data'],
};Next: Automation →