Security

Permissions & roles

How Alaf Server decides who can do what — organizations, the four roles, and per-resource grants, all enforced by one permission plane.

Every request into Alaf Server passes through one gate — the permission plane — that answers a single question: is this person allowed to do this thing to this resource? Think of it like the front desk of a building: your keycard says which floors you can reach, and the desk checks it every single time, not just once at the door.

Access is decided by three things working together: which organization a resource belongs to, your role in that organization, and — for the tightest role — the specific grants you've been given.

One gate, no side doors

Alaf Server refuses to boot if any API route forgets to declare a permission. There is no "unprotected by accident" route — the safe default is enforced at startup, not left to reviewers to catch.

Organizations

An organization (or "org") is the container that owns everything: projects, servers, domains, backups, billing. You're a member of one or more orgs, and your access is decided per organization — being an owner of one org says nothing about your role in another.

When you make a request, Alaf Server figures out which org you mean from the X-Organization-Id header (the dashboard and other API clients set this) or, as a fallback, your session's default org. Every resource carries its own organization_id, so the platform can always trace a project or domain back to the org that owns it and check your membership there.

The four roles

Your role in an org sets your baseline access. From most to least powerful:

RoleWhat they can do
ownerEverything, including billing. Usually the person who created the org.
adminEverything except billing — including managing members and invitations, and reading the audit log.
memberFull access — read, write, and delete — to all resources except billing and the audit log. Can't manage other members.
restrictedNo access by default. Can only reach the specific resources they're explicitly granted.

Owner vs admin, in plain words

An owner holds the keys to the money — plans, payment, and cancellation. An admin runs the day-to-day: they can invite teammates, deploy, add domains, and read the audit log, but they can't touch billing. A member is a trusted teammate who ships work but doesn't manage the team or see billing. Restricted starts with an empty keyring and gets keys one at a time.

Permission tags: resource:action

Under the hood, each route declares a short tag that names a resource and an action, like project:read or domain:write. Sub-resources add a middle segment — project:service:write means "edit a service that lives inside a project."

The action is the verb, and it lines up with the HTTP method:

ActionHTTP methodMeaning
readGET (one resource)View a single thing by id.
writePOST / PUT / PATCHCreate or change something.
adminDELETE (and other destructive actions)Remove, reset, or otherwise destroy it.
listGET (a collection)List things in the org — no specific id.

You'll see these tags in the API reference next to each endpoint, so you always know which action a call performs.

Restricted members and resource grants

A restricted member is the interesting one. They start with zero access. To let them do anything, an owner or admin gives them grants — small records that say "this person may read / write / admin that resource."

Each grant lists one or more of these levels. When Alaf Server checks an action, a higher level also satisfies the lower ones:

  • read — view and list the resource.
  • write — create and change it (this also satisfies a read check).
  • admin — delete and fully manage it (this also satisfies write and read).

A grant targets a resource by its id, or uses a wildcard * to mean every resource of that type in the org. So a wildcard write grant on Projects lets someone deploy to all of them, while a single project id keeps them to just one.

Grants flow downhill

You grant on a top-level resource, and the access inherits down its tree — you don't grant each child separately:

Grant on a……also covers
Projectits deployments, domains, services, and environment variables
Backup destinationits backup policies, runs, and restores
Serverterminal access and runtime operations (logs, restarts, start/stop) on that server
Mail serverwebmail, branding, and mail settings

What you can grant

The resource picker offers these grantable types:

TypeCovers
ProjectsApps and everything inside them (deployments, domains, env vars).
ServersConnected machines and their runtime/terminal access.
Mail serversEmail hosting and webmail.
Backup destinationsWhere backups are stored, and their runs.
BillingPlans and payment (owner-level territory).
Audit logThe record of who did what.
GitHub orgsEvery repo under one GitHub installation.
GitHub reposA single repository.

Which types you'll see

The picker adapts to your instance. A self-hosted instance shows Servers and Mail servers; Alaf Server Cloud shows Billing in their place. Projects, Backup destinations, the Audit log, and GitHub are offered on both.

Inviting people and setting roles

Team management lives under Settings → Team.

Open the invite dialog

Go to Settings → Team and press Invite member. Enter the person's email address.

Screenshot

Settings → Team tab, member list with the Invite member button. (screenshot pending)

Choose a role

Pick Member, Admin, or Restricted. (Owner is the org creator's role — you don't hand it out from the invite dialog.)

  • Member and Admin get their access immediately on joining.
  • Restricted opens a resource picker on the right, where you choose exactly what the invite unlocks.

Screenshot

The Invite member dialog with the three role cards (Member, Admin, Restricted) and, for Restricted, the resource picker pane on the right. (screenshot pending)

Send it

Alaf Server emails a join link. When the invitee accepts, their membership is created and any pending grants are applied. You can always widen or narrow a restricted member's grants later from their row in the team list.

Access tokens follow the same rules

For scripts, the CLI, and AI agents, you use a personal access token (a revocable, per-user key sent as Authorization: Bearer opsh_pat_...). A token resolves to your user and org and then obeys the exact same permission model a browser session does.

Tokens can be tightened two ways:

  • Read-only — the token rejects every mutation (POST/PUT/PATCH/DELETE).
  • Scoped — the token carries its own grants and is treated as a restricted principal, limited to exactly those grants even if you're an owner. A scoped token can never do more than it was scoped for.

A scoped token can't exceed its grants

This is deliberate: hand a scoped, read-only token to a script or agent and the worst it can do is read the handful of resources you granted — regardless of how much power your own account has.

Denied looks like "not found"

When you try to touch something you're not allowed to, Alaf Server returns 404 Not Found, not 403 Forbidden.

Why 404 instead of 403?

A 403 quietly confirms the resource exists — you just can't see it. A 404 gives nothing away. This protects against IDOR (Insecure Direct Object Reference — guessing ids to probe for resources you shouldn't know about). If a project id isn't yours, the platform behaves as if it simply isn't there.

What next?

On this page