Auth and API gates
Updated
What a caller may do, and what the gates refuse. The office keeps the
JWT in HttpOnly cookies; mobile and agents still send Authorization: Bearer
from the same JSON body.
Production posture#
ZuloOne:CoreDevelopmentMode=false turns on the fallback policy: every
endpoint needs an authenticated user unless it is [AllowAnonymous] (login,
setup, health, branding, agent enroll, workspace zip with a ticket).
RequirePermission and RequireDeveloperOnMutation skip while CoreDevMode
started enabled, so the test rig stays open.
Closed#
/api/data/{table}refuses EF-owned tables (MetaUsers,MetaSessions, …) with 404. Same for export. A name that is not a dictionary still has no dictionary matrix (documents, registers) — that is not a platform table.- Agent / job admin.
AgentsControllerandJobsControllerrequireAdmin. An agent or run JWT may only call/api/agent/*and/hubs/agent. - Metadata writes. Designer controllers take
RequireDeveloperOnMutation(GET stays open for the office). Compile requires Developer. - Script Enforce.
Type.GetType,Activator.CreateInstance,MethodInfo.Invoke, andSystem.Reflectionare flagged the same way asProcess/File. - Agent invoke.
AgentRuntimeInvokeris an allowlist (platform data services includingIMetadataService, plusZuloOne.Services.Contracts.*). Not permissions, schema, SQL, or wipe. SameScriptServices.EnsureAlloweddeny-list as a task script. The run JWT is bound to the enroll session of that agent: assemblies / invoke / complete requireX-ZuloOne-Agent-Session. A stolen run token without that machine's session is 401. - Sessions. Refresh is SHA-256 in
MetaSessions.SessionToken(current[:previous]). Replay of the previous hash revokes the user's whole family.POST /api/auth/logoutends the presented refresh; password change / admin lock / deactivate do the same. - Office cookies. Login / refresh / first-run setup set
zuloone.access(path/) andzuloone.refresh(path/api/auth), both HttpOnly,SameSite=Lax. The SPA sendscredentials: includeandX-ZuloOne-Client: office. A cookie-authenticated POST/PUT/PATCH/DELETE without that header is 403 (a cross-site form can send Lax cookies, not a custom header).Authorization: Bearerstill wins and skips the CSRF gate — mobile, agents, and the test suite keep the JSON token. The office never writes the JWT tolocalStorage. - First-run setup.
POST /api/auth/setupruns in aSerializabletransaction; a concurrent second admin isDbUpdateException→ already completed. - Passwords.
VerifyPasswordis BCrypt only. A leftover Base64 hash does not authenticate. - Attachments. List / upload / download / delete resolve the owner row
through
IDataService.GetByIdAsync(RLS). Platform tables 404. - Designer writes (second pass). Print forms, EDTs, resources, analytics, layers, total drivers, virtual totals, widget assignments/placements, report views, guides, localization, metadata registries — mutations need Developer. Custom report run stays a business POST; create/update/delete do not. Personal form layouts stay the caller's.
- Bulk data.
/api/data-transferand/api/data-packagesneed Developer (apply/recreate wipe and restock tables)./api/email/queueneeds Admin (bodies and addresses). - Agent SQL.
ISqlServiceis not on the invoke allowlist. In-process scripts still have it; a run token cannotExecuteAsyncarbitrary SQL. - Print RLS.
BuildForFormAsyncchecks the owner row before a data script runs. Hidden record → 404, not a rendered page. - Zip ticket. Payload is
rid|exp|jti. The first full GET spendsjti. A Range resume (bytes=N-, N > 0) works for 2 minutes after that spend. A second full GET is 401. - OData catalog.
/odataand/$metadatalist only entities the caller may Read. Collection GET already had ACL + RLS. - Hubs. Client
SendMessage/SendNotificationToUserdo not broadcast. Record/table subscribe require a visible business row. Studio and metadata groups need Developer. - Agent enroll.
POST /api/agent/sessionuses the same lockout counters as login (agent:{name}), without captcha — agents cannot solve Turnstile.
Left open on purpose#
- Empty object-permission matrix is still public for business objects
(
PermissionPolicy.AllowOnEmptyMatrix). Changing that locks unfinished tenants. /healthstill reportsdeveloperTools/workspaceAgent— the SPA reads them on the login screen.
Trap#
localhost and 127.0.0.1 are different cookie hosts. The Vite origin in
Cors:AllowedOrigins must be the same host name the browser uses, or the
office cookie is set and never sent.
Where next#
- Remote task agents — enroll keys and the job runner.
- The dev workspace agent — signed download URL.
- Script security — Enforce and reflection.
Was this page helpful?