Authorization
Two properties drive authorization in Quonfig:
- Every environment is either protected or not.
- Every flag and config has an access level —
support,standard,protected-env, orprotected-all-envs.
Cross the two and you get a matrix of edit decisions. Roles are how you grant someone cells of that matrix: each role is a row. That is the whole model — the rest of this page is the matrix, plus a few footnotes.
Environments: protected or not
Each environment carries a protected bit. Marking production protected is the typical move, but nothing is hard-coded: mark prod-eu or a compliance environment protected and every rule on this page treats it exactly the same way.
Marking an environment protected does not lock every config in it. It changes the verdict only where the matrix says it does — most visibly for protected-env configs, which lock in protected environments and stay open everywhere else.
Configs: four access levels
Every flag and config carries an access field. Default if omitted: standard.
access | Meaning | Typical use |
|---|---|---|
support | Editable by the support team as well as engineers. | Account-level overrides, manual feature grants |
standard | Editable by engineers, in any environment. | Most flags and configs |
protected-env | Locked in every protected environment; open everywhere else. | "Engineers change it freely in dev and staging; only the inner circle changes it in production" |
protected-all-envs | Locked down everywhere. | Pricing, billing, compliance-sensitive configs |
Hierarchy from lowest to highest: support < standard < protected-env < protected-all-envs.
Roles
Three roles grant config editing. Each one is a row of the matrix below:
| Role | What it grants |
|---|---|
| Support | Edit support configs, in every environment. |
| Engineer | Edit support and standard configs everywhere, and protected-env configs in non-protected environments. |
| Protected Engineer | Edit everything, everywhere. |
Everyone also holds Member, the read-only floor: view workspaces, configs, and history. Roles are assigned at the organization scope and apply uniformly to every workspace in the org, and they combine — an Engineering Manager might hold Engineer plus Admin, a senior IC Protected Engineer alone.
The matrix
Columns are each access level crossed with where your edit lands: a non-protected environment, a protected environment, or the default value (what an environment serves when none of its own rules matched).
The same matrix in text
Each cell reads non-protected environment / protected environment / default value:
| Role | support | standard | protected-env | protected-all-envs |
|---|---|---|---|---|
| Member | lock / lock / lock | lock / lock / lock | lock / lock / lock | lock / lock / lock |
| Support | edit / edit / edit | lock / lock / lock | lock / lock / lock | lock / lock / lock |
| Engineer | edit / edit / edit | edit / edit / edit | edit / lock / cond | lock / lock / lock |
| Protected Engineer | edit / edit / edit | edit / edit / edit | edit / edit / edit | edit / edit / edit |
Three things to know when reading it:
- A save is judged per section you touched. Editing only your staging rules never asks for permission in a protected environment — the protected column applies only when your edit actually lands there.
- Creating and deleting a config, and metadata changes (name, description, tags, the
accessfield) are not scoped to any environment — a new flag or a deletion changes what every environment serves — so they are judged at the strictest context. Nobody can lower a config'saccessin order to edit it. - The one
condcell is the default value of aprotected-envconfig. It moves as the config's own rules change:
When is a default value locked, and what to do about it
A config's default value is what an environment serves when none of that environment's own rules matched. Editing the default is therefore an edit to your protected environments — unless they can never fall through to it:
The default value is unreachable — and unlocks — when every protected environment ends in a terminal catch-all: a rule with no criteria, or one whose criteria are all "always true". Then editing the default cannot change what those environments serve.
Concretely, with production marked protected:
DEFAULT IS REACHABLE -> locked
production:
if plan is "pro" -> on (targeted; can fail to match)
(no catch-all: falls through)
default:
always -> off <- production serves this
DEFAULT IS UNREACHABLE -> editable
production:
if plan is "pro" -> on
always -> off (terminal catch-all)
default:
always -> off <- production never reaches it
Reachability is computed on the stored document, not the one being written — so removing the protected environment's catch-all to unlock the default doesn't work: that removal is itself an edit to the protected environment, which is exactly what the fence denies.
If the editor tells you the default is locked, you have two ways forward:
- Ask a Protected Engineer to add an explicit rule in the protected environment. Once every protected environment ends in a rule that always matches, the default unlocks — permanently, until someone removes that rule.
- Set the value in your own environment instead. Usually what you actually wanted: an explicit rule in
stagingbeats the default for staging traffic and leaves the protected environments alone.
Additional roles: fencing people out of protected environments
The roles above lose access in a protected environment only where the config's access level says so — an Engineer still edits standard configs in production. Some teams also want the opposite: people who can work freely in staging and dev but can never touch a protected environment at all. Two opt-in roles do that.
Support and Engineer, non-protected environments only
| Role | Slug | What it grants | Where |
|---|---|---|---|
| Support (non-protected envs) | support_nonprotected | Edit support configs | Non-protected environments only |
| Engineer (non-protected envs) | engineer_nonprotected | Edit support, standard, and protected-env configs | Non-protected environments only |
The fence is the environment's protected bit — the same bit used everywhere on this page, not a hard-coded "production". And unlike the unscoped roles, here it is absolute: a holder is locked out of a protected environment at every access level, including support and standard.
Their rows of the matrix (same cell format as above — non-protected environment / protected environment / default value):
| Role | support | standard | protected-env | protected-all-envs |
|---|---|---|---|---|
| Support (non-protected envs) | edit / lock / cond | lock / lock / lock | lock / lock / lock | lock / lock / lock |
| Engineer (non-protected envs) | edit / lock / cond | edit / lock / cond | edit / lock / cond | lock / lock / lock |
cond is the same conditional default described above: editable only while every protected environment ends in a catch-all rule. If a holder finds the default locked, the two ways forward are the same too — an explicit rule in their own environment, or a Protected Engineer adds a catch-all in the protected one.
Beyond the environment fence, these roles are narrower in a second, independent way: a whole class of writes has no environment for the fence to open on, so it stays closed regardless of how your environments are configured:
- Creating or deleting a config. A new flag defines what every environment serves, including protected ones. Ask an engineer to create the skeleton, then edit it.
- Metadata — name, description, tags, the
accessfield. - Segments, schemas, and restore-from-history — whole-file writes with no environment to name.
- SDK keys — workspace plumbing, not a per-environment config edit.
One wire-level note: the conditional default exists only when saving a whole document (the editor's Save, qfg push). An env-targeted write — PATCH /v1/flags/{key}/environments/{env}, MCP set_flag — names a real environment and can never address the default value at all.
These two roles are not self-serve — they don't appear in your role picker until switched on for your organization. Contact us and we'll enable them; everything else on this page works out of the box.
Members and admins
Member is the read-only floor everyone holds automatically: view workspaces, configs, and history.
Admin manages workspace plumbing — invites, role assignment, billing, environments, SDK keys, integrations. It grants no config editing and stacks with the roles above: an EM can manage the team without being able to edit pricing, and a senior IC can hold Protected Engineer without being on the hook for billing.
SDK-key management is the one place the axes meet: an Admin manages keys in any environment; otherwise the config role decides — Protected Engineer anywhere, Engineer in non-protected environments, Support not at all.
Where authorization is enforced
Everything on this page is enforced in Quonfig's application layer, and every write path converges on it: the app UI, both CLI push engines (qfg push and qfg migrate --push), the public API, and MCP. The layer sits between all of those and your workspace's git repo — and your deployed SDKs receive only what's committed to that repo.
This placement is what makes local editing harmless. A local checkout of your workspace is plain text — anyone can open the JSON in an editor and change anything, and nothing stops them, because nothing needs to: those edits change nothing until they're pushed, and the push lands in the authorization layer before it can touch the repo. Direct pushes to the underlying git repo bypass these tier checks; they're rare, bounded by repo-level access, and structurally validated, but per-file authorization at the git layer is not enforced today.