Security & Authentication
Zebric includes built-in security features for authentication, authorization, and request protection.
Authentication
Section titled “Authentication”Enable email/password authentication:
[auth]providers = ["email"]trustedOrigins = ["http://localhost:3000"]Authentication is powered by Better Auth with server-side session management.
Page-Level Auth
Section titled “Page-Level Auth”Control authentication requirements per page:
[page."/dashboard"]title = "Dashboard"auth = "required" # Must be logged inlayout = "dashboard"
[page."/posts"]title = "Public Posts"auth = "none" # No login neededlayout = "list"
[page."/profile"]title = "My Profile"auth = "optional" # Shows user info if logged inlayout = "detail"Auth Pages
Section titled “Auth Pages”The auth layout renders sign-in and sign-up forms:
[page."/sign-in"]title = "Sign In"layout = "auth"Access Control
Section titled “Access Control”Entity-Level Access
Section titled “Entity-Level Access”Define CRUD permissions on entities:
[entity.Post.access]read = true # Anyone can readcreate = true # Anyone can createupdate = { authorId = "$currentUser.id" } # Only author can updatedelete = { "$currentUser.role" = "admin" } # Only admins can deleteCondition Operators
Section titled “Condition Operators”| Pattern | Description |
|---|---|
{ field = "value" } | Field equals value |
{ field = "$currentUser.id" } | Field matches current user |
{ "$currentUser.role" = "admin" } | Current user has role |
{ or = [...] } | Any condition matches |
{ and = [...] } | All conditions match |
Combined Conditions
Section titled “Combined Conditions”# Authors can see their own drafts; everyone sees published posts[entity.Post.access]read = { or = [{ status = "published" }, { authorId = "$currentUser.id" }] }Field-Level Access
Section titled “Field-Level Access”Restrict read/write on individual fields:
[entity.User]fields = [ { name = "email", type = "Email", access = { read = true, write = { "$currentUser.role" = "admin" } } }, { name = "salary", type = "Integer", access = { read = { "$currentUser.role" = "admin" }, write = false } }]API Keys
Section titled “API Keys”For agents, REST API clients, or external integrations. API keys apply to skill routes only (routes registered from [skill.*] actions) — standard page routes and /api/auth/* are unaffected.
[[auth.apiKeys]]name = "dispatch-agent"keyEnv = "DISPATCH_AGENT_API_KEY"| Field | Description |
|---|---|
name | Human-readable identifier for the key. Used as the actor ID in sessions, audit events, and logs. |
keyEnv | Name of the environment variable holding the secret value. The key itself is never stored in the blueprint. |
Set the env var before starting the server:
export DISPATCH_AGENT_API_KEY="sk-your-secret-key-here"If the env var isn’t set at startup, the key is skipped and a warning is logged — the server still starts normally.
API key requests skip CSRF validation but still go through access control. Send the key in the Authorization header:
curl -X POST http://localhost:3000/api/issues/01J.../status \ -H "Authorization: Bearer sk-your-secret-key-here" \ -H "Content-Type: application/json" \ -d '{"status": "in_progress"}'How It Works
Section titled “How It Works”- At startup, the runtime reads
auth.apiKeysfrom the blueprint, resolves eachkeyEnvfromprocess.env, and stores the resolved keys in memory. - When a request hits a skill route, the runtime checks the
Authorizationheader before falling back to session-based auth. - If the bearer token matches a configured key, a synthetic session is created with
user.idanduser.nameset to the key’snamefield. This session flows through to workflows, audit events, and access control like any regular user session:
{ "id": "apikey-dispatch-agent", "userId": "dispatch-agent", "user": { "id": "dispatch-agent", "name": "dispatch-agent", "email": "" }}So {{ variables.data.user.id }} in workflow templates resolves to the key name (e.g. dispatch-agent), which becomes the actorId in audit events.
Keys are checked via exact string match in memory — there’s no hashing step, so treat the env var as a secret. Rotate keys by changing the env var and restarting the server.
Built-In Protections
Section titled “Built-In Protections”Zebric applies these security measures automatically:
- CSRF protection on all state-changing requests (POST, PUT, DELETE)
- Security headers (CSP, X-Frame-Options, X-Content-Type-Options, Referrer-Policy)
- Open redirect prevention on login redirect flows
- Path traversal protection on file resolution
- HTML entity encoding to prevent XSS
- Workflow body filtering to prevent injection in workflow payloads
- Audit logging for security-relevant events
- Error sanitization to avoid leaking internal details
Session Configuration
Section titled “Session Configuration”[auth.session]duration = 86400 # Session duration in seconds (default: 24 hours)idle_timeout = 3600 # Idle timeout in seconds