Skip to content
Solo ProTeamBusinessEnterprise

Access policies

Path-pattern permission grants that scope a service account's access — actions, wildcards, and evaluation.

A policy grants a service account a set of actions on a set of paths described by a pattern. A machine can do something only if at least one of its policies allows it.

Actions#

read · write · delete · list · rotate — or * for all. Each maps to a data-plane operation:

Action Operation
read GET a secret value / versions
write PUT (create or new version)
delete soft-delete a secret (and restore it)
list list a namespace's secrets
rotate re-key a secret: same value, fresh data key, new version
`write` does not include `delete`

write ⊇ rotate is the only containment between actions. Everything else is independent, so a permission granting read, write, list cannot delete a secret no matter how well its pattern matches the path — and the server checks the action before it looks at the pattern. That is why pulsvault delete, undelete and mv can be refused on a key that writes happily, and why no starter permission except <project>-manage-prod grants delete.

`rotate` without `write` is a useful narrowing

A rotation never changes a value — the server re-encrypts what is already stored, so the plaintext is never sent anywhere. A job that only needs to re-key credentials on a schedule can hold rotate alone and be unable to write a new value or read the current one.

write also satisfies rotate, since anyone who can write could rotate by writing the value back. The reverse does not hold.

Path patterns#

Patterns match against the full canonical path, whose four slots are the project, the environment, the namespace and the secret:

  • * matches exactly one segment.
  • ** matches zero or more segments.
  • No leading slash; segments use a–z, digits, _, ., -.

A permission may hold several paths. Separate them with commas — spaces around the comma are fine:

text
acme-web/dev/web/**, acme-web/local/web/**

"dev and local, nothing else" is one intent, so it is one permission rather than two.

Patterns start at the project

The project slug is the first segment of every path, so a pattern that begins with an environment matches nothing. prod/** is not "everything in prod" — it would only match a project literally named prod. Write */prod/** (every project) or acme-web/prod/** (one project).

Pattern Matches
acme-web/prod/** everything in that project's prod environment
*/prod/** prod in every project the account can reach
acme-web/*/product/web/** the web product in every environment of one project
acme-web/prod/product/web/db/* direct children of db in web prod
<own-project>/dev/** dev in whichever project the machine belongs to — see below
`**` can never be the environment

acme-web/** is refused, not accepted as "everything in the project": ** would swallow the environment slot, so the grant would silently include production. Name the environment (acme-web/prod/**), or tick Allow across every environment to get acme-web/*/** deliberately.

One permission, every project#

A literal pattern names exactly one project, and the only literal that spans them all — */dev/** — grants every project, which is rarely what anyone wants. Writing {project} in the first slot instead resolves to the machine's own project when the permission is evaluated:

text
{project}/dev/**

One definition is therefore correct in every project and still cannot reach across them: the substitution can only ever produce the holder's own project. On a machine with no project it resolves to nothing and grants nothing, rather than quietly becoming a wildcard.

`{project}` here is a real token

Elsewhere in these docs {project}/{env}/{namespace}/{secret} is notation for the four slots of a path. In a permission pattern, {project} typed literally in the first slot is a feature with the meaning above. It is valid in the first slot only.

Permissions without a path#

A permission may name an intent and leave the path out entirely — read-only = read + list, no path. That is a template. It grants nothing on its own; when you attach it to a machine you choose the environments and the path it takes on there, and each machine gets its own concrete grant.

Use one when the same intent applies at different paths on different machines. Use an ordinary permission when the path is always the same. | shared/*/** | read-only common secrets — see The Shared project |

Effect#

All policies are ALLOW at this phase (there is no DENY). Access is granted if any attached policy allows the requested action on the requested path; otherwise it's denied (403). As defense-in-depth, a policy can never grant access outside the service account's own space (organization or owner).

Testing a pattern#

The policy editor includes a live tester — type a candidate path (e.g. acme-web/prod/product/web/db/password) and see instantly whether the pattern would grant it. This runs entirely in your browser.

Least privilege

Grant the narrowest pattern and action set a pipeline needs. A deploy job that only reads two secrets should get read on those paths — not * on acme-web/prod/**.

When a path moves#

A permission points at a path, and paths get renamed. Nothing about a renamed namespace makes the permissions that named it illegal — they stay attached and keep evaluating, they just stop matching anything. Pipelines start failing on a path they are silently no longer granted, and the permission looks fine on every screen that shows it.

Two things close that gap.

At rename time. Renaming a namespace first asks what it would orphan. If any permission names the path being moved, you see which ones and which machines hold them, before anything is committed:

text
2 permissions will stop matching after this rename.
  read-web      ci-runner, deployer
    acme-web/dev/web/**  →  acme-web/dev/frontend/**

  [ Cancel ]  [ Rename only ]  [ Rename and update 2 ]

Only permissions that name the moving path are listed. A broader one like acme-web/dev/** covers the path before and after, so it is not affected and is not shown.

Updating is always a choice. A rewrite changes what a machine can reach, so it is never applied on your behalf — Rename only leaves the permissions exactly as they were.

Afterwards. The Health tab on the Permission library lists every permission that has stopped pointing at anything, worst first:

Status Meaning
Healthy reaches at least one existing secret
Broken it was granting access recently and now matches nothing — usually a rename
Never matched it has never matched anything, which normally means a typo in the path
Impossible it names an environment or project that does not exist, so it can never match
Nothing there yet the namespace exists but has no secrets — fine, not an error

Broken and Never matched both reach zero secrets today. They are separated because the fixes differ: one path used to work and moved, the other never existed. Rows also show denial counts from the last 24 hours, so a broken permission and the failures it is causing appear together rather than on two different screens.

Granting before the secret exists is fine

A permission that matches nothing is a warning, never a blocked save. Pipelines routinely create the secret after the permission that will read it.

Where a permission is created#

Which screen you use decides the permission's scope — there is no scope switch on the form.

Where Project slot
Belongs to one project that project's Permissions screen fixed to the project, not editable
Shared across projects DevOps → Permissions your choice, defaulting to the holder's own project

Most permissions belong to one project. Create one in the DevOps area only when the same grant is meant for machines in several projects.

A shared permission appears on every project's Permissions screen as Inherited, read-only, with a link back to the DevOps area — it is held by machines everywhere, so changing it from inside one project would be changing it for all the others without saying so. Only an organization administrator can create or change one.

A shared permission that names one project

If a shared permission's path names a specific project, it is listed everywhere but can only ever be granted in that one — other projects mark it not usable here. That is usually a sign it should have been created inside that project instead.

Requiring approval for cross-environment permissions#

A cross-environment permission spans dev, staging and production at once — the widest grant this system issues. DevOps → Permissions carries a space-level switch that restricts creating one to organization administrators.

Turning it on gates new permissions only; it never revokes what is already granted, and the screen tells you how many cross-environment permissions already exist so the decision is made with that number in view rather than after discovering it.

Was this page helpful?