Nobody can grant what they do not hold
44 permissions, three built-in roles, private products isolated to named members, and a clamp that makes privilege escalation structurally impossible rather than merely discouraged.
Every workspace member holds one role, and a role is a list drawn from 44 named permissions across 11 resource groups. Three roles are seeded when the workspace is created: Owner with all 44, Admin with 42, and Viewer with 11 read-only ones. Below the workspace, a product can be marked private, which limits it to an explicit list of members regardless of their role. API keys carry a separate scope map that is clamped to the permissions of whoever created them, and re-clamped automatically whenever that person's role changes. Permissions are workspace-scoped rather than per-environment: the unit of isolation below the workspace is the product, not the environment.
The model, exactly
Including where the boundary is authorization rather than cryptography, because the difference matters when someone leaves.
- 44 / 11
- Namespaced as resource:action, covering workspace, members, keys, billing, roles, products, projects, environments, variables, versions and webhooks. The same catalogue ships in the CLI and is readable over the API.
- 44 / 42 / 11
- Seeded in the same transaction that creates the workspace. Admin holds everything except deleting the workspace and managing billing. System roles are immutable: update and delete both return role.immutable.
- 25
- Names are 2 to 40 characters and unique case-insensitively. Two permissions can never be granted to a custom role at all, workspace:delete and billing:manage, and the validation refuses them for every caller including Owners.
- clamped
- Creating a role, editing a role, inviting a member or changing someone's role is refused when the result would exceed the permissions you personally hold, and the error names the exact offending permission strings.
- 404
- Private-product access is re-checked server-side on every read and write beneath the product, across 21 controllers, and a denied request answers 404 rather than 403, so a restricted product is indistinguishable from one that does not exist.
- 1 transaction
- Soft-deletes the membership, hard-revokes every API key they hold in that workspace, revokes their CLI tokens, and drops their private-product grants. The key middleware independently re-checks membership on every request.
Permissions across resource groups
Owner, Admin, Viewer
Custom roles per workspace
You cannot grant above yourself
Returned by a private product you lack
To remove a member completely
Setting a team up
Start from the catalogue, build the role you actually mean, then isolate anything that should not be workspace-wide.
- 1
Read the catalogue first
All 44 permissions are enumerable from the CLI, the API and the SDK, each with its resource and a flag saying whether a custom role may hold it. Build roles from this list rather than from memory.
envless role permissions envless role permissions --json - 2
Create a role that means one thing
A role is just a permission list, so name it after the job. The clamp applies here: you cannot include a permission you do not hold yourself, which means a compromised Admin still cannot mint an Owner.
envless role create "Deploy bot" \ --description "Read-only access for CI" \ --permissions variables:read,versions:read,environments:read,projects:read - 3
Invite into that role
Invites are bound to both the email and the role, expire after three days, and can never assign Owner. Acceptance is refused unless the signed-in account's email matches the invited address exactly.
envless member invite dev@acme.inc --role "Deploy bot" envless member list - 4
Isolate what should not be workspace-wide
Marking a product private takes it out of the default view entirely and limits it to members you name. This is the finest isolation boundary that exists, and it covers the whole product, project and environment subtree beneath it.
envless product update billing-platform --private envless access grant ann@acme.inc bob@acme.inc --product billing-platform envless access list --product billing-platform - 5
Mint keys narrower than the person holding them
A key carries a scope map over 11 resources at none, read or write, defaulting to none, plus an optional project allowlist. It cannot exceed its creator's permissions, and demoting that person silently narrows every key they already issued.
const key = await envless.keys.create({ name: 'ci-reader', scopes: { variables: 'read', environments: 'read', projects: 'read' }, projectIds: ['<project-uuid>'] }) console.log(key.key) // shown exactly once, stored only as a SHA-256 hash
Read the details
Roles, members, product access and key scopes, each with its own reference page.
- Roles and permissions The full catalogue, custom roles, and what the built-in roles hold.
- Members Inviting, updating, removing, and what removal revokes.
- Private product access Marking a product private and granting it to named members.
- API keys Scopes, the project allowlist, rotation, and the step-up gate.
- API authentication How a scope requirement is declared per route and what a miss returns.
- Workspaces How membership, not the URL, is what grants access to anything.
Team access control FAQ
Straight answers about how this works in practice.
Not through roles, and this is the most important limitation to understand. Permissions are workspace-scoped, so variables:read applies to every environment in the workspace at once. There is no per-environment ACL. What you can do is put the sensitive work in its own product, mark that product private, and grant it only to the people who should reach it, which isolates the entire project and environment subtree beneath it. For machines, a key can additionally be pinned to a project allowlist.
No, and it is important not to imply otherwise. There is one encryption key per workspace, derived from one shared passphrase. Private products and role checks are server-side authorization: they stop new requests, they do not make previously readable ciphertext unreadable. Anyone who ever held the passphrase can still decrypt anything they kept a copy of. Rotating the workspace passphrase is what actually retires that access.
No. Any role holding products:manage_private bypasses grant checks entirely, and both built-in Owner and Admin hold it. Private products protect against broad workspace-wide visibility, not against your own administrators. If you need a boundary that administrators cannot cross, that is a separate workspace.
A clamp on every path that could widen a permission set: creating a role, editing a role, inviting a member, changing a member's role, and creating an API key. Each is refused when the result would exceed what you personally hold, with the offending permissions named. Two permissions are additionally hard-blocked from custom roles for everyone, including Owners: deleting the workspace and managing billing.
They are hard-revoked in the same transaction as the removal, along with their CLI tokens and their private-product grants. That matters more than it sounds: a key outliving its owner is the classic way access survives an offboarding. The API-key middleware also re-checks workspace membership on every request, so there is a second line of defence.
Exactly five, all in the dashboard: deleting an API key, rotating an API key, deleting a webhook, rotating a webhook secret, and revealing a webhook secret. Envless emails a six-character code, valid ten minutes, capped at five attempts, and one success elevates you for ten minutes. It does not gate reading variables, deleting environments, removing members or changing roles, and it is not enforced on the public API surface at all.
Team members are plan-gated: Free and Starter are single-seat, and Business allows up to 100. Both inviting and accepting count active members plus pending invites against that ceiling, so an invite that would exceed it is refused at send time rather than at acceptance.
Not today, and it is a gap worth knowing about. Member changes, role changes, key changes and CLI-token changes all emit workspace events that reach your webhook endpoint. Granting and revoking product access does not emit an event and has no webhook type. If you need that trail, record it on your side when you make the call.
Explore the rest of Envless
Every capability, broken down the same way.
AI agents
Give a coding agent working secrets with no plaintext .env in the repository for it to read or commit.
TerminalThe CLI
24 commands, browser device login, local encryption, and JSON output on every list.
TypeScriptThe SDK
Zero-dependency typed client, full API coverage enforced by CI, with encryption and webhook helpers.
EncryptionEnd-to-end encryption
AES-256-GCM on your machine, a key the server never sees, and an API that structurally refuses plaintext.
HistoryVersioning and history
Numbered snapshots in one locked transaction, attributed per-variable history, and signed events for every change.
EnvironmentsEnvironment variables
Typed, per-environment variables with server/client splitting, .env import and export, and a real diff.
Public feedsVirtual environments
Serve an environment as an encrypted JSON feed at a public URL that needs no SDK, CLI or API key.
SetupGetting set up
Install, sign in, bind the directory, unlock, run. What each step writes, and what it does not.
CloningOne-click duplicate
Clone an environment, project or whole product with every variable, in one transaction, without decrypting anything.
Ship secrets, not chaos.
Start free today and discover why developers trust Envless for end-to-end encrypted, versioned secrets across every environment.