A machine principal has no role. Its authority is exactly the set of scopes its credential was granted. That is the whole authorisation model for a key: there is no fallback to the creator’s permissions, no implicit read access, and no scope that grants another. A key with scans:create and nothing else can queue a scan and cannot read the result.

Grantable scopes

Rather than trusting this table, ask the deployment:
The endpoint is served rather than documented-only so a client renders the real set instead of a copy that drifts. The ungrantable permissions are absent from it by construction. Four further read scopes exist alongside the eight above and are grantable in the same way:
  • policies:read — policies and their versions.
  • webhooks:read — endpoints and their delivery history. This does not include reading signing secrets, because nothing does.
  • credentials:read — registry credentials and their status. No route returns a stored secret at any permission, which is what makes this safe to grant to a machine while credentials:manage is withheld from every one.
  • identity:read — the SSO configuration, the claimed domains, the directory mappings and the identity audit trail. Grantable so a compliance pipeline can export who has access; the configuration holds no credential, and the client secret has no read path at all.
  • members:read — the organization’s members, their roles and how each membership arrived. Grantable because the list holds no credential, and “who has access and how did they get it” is the most commonly automated compliance export. Note what it does not include: an invitation’s token is never on any response at any permission, so a machine that can read the roster still cannot redeem anything.
  • workloads:read — the tenant’s non-human principals, their delegations and their standing. Grantable, and deliberately so: the list contains no credential, because a workload’s digest has no read path, and “what is acting in our tenant, on whose authority, and when did it last act” is the inventory question a security team automates. Granting it is what lets a monitoring workload watch the other workloads.
verdicts:request is separate from scans:read because asking for a verdict writes: a verdict is a persisted, immutable record of a decision, and a read-only credential should not create records even from data it can already see.

Why suppression is its own scope

findings:suppress is separate from findings:write on purpose, and the asymmetry is not cosmetic. Triage moves a finding around a queue everyone can still see. Suppression makes a real, confirmed vulnerability stop appearing in the backlog, stop counting towards the severity totals, and eventually stop blocking the release gate. It is the one action in the lifecycle whose misuse is invisible by design — that is what accepting risk means. Folding it into findings:write would hand every CI key the ability to silence its own failures. A build that can queue a scan has no business being able to make the scan’s findings go away, and the fastest route to a green pipeline must never be “suppress whatever is red”. Grant findings:suppress to a credential a person operates. Do not grant it to a runner. See Finding lifecycle.

Permissions no key may ever hold

Nine permissions exist for humans and are not grantable to an API key. Requesting one is a 400 at key creation, not a 403 later — the refusal happens where somebody is reading it. Each is a case where the credential could dismantle the control that bounds it.

apikeys:manage

A key that can mint keys never really expires. The whole value of a machine credential is that it is bounded: it has a scope list you chose and an expiry you cannot opt out of. A leaked credential holding apikeys:manage would issue itself a fresh one before anyone noticed, and revoking the original would achieve exactly nothing. That converts a bounded, expiring key into permanent access — the difference between an incident and a breach. Key management is therefore a human-only surface. GET /api-keys, POST /api-keys and DELETE /api-keys/{id} are all reachable only with a browser session. The cost is recorded honestly: apikeys:manage is all-or-nothing, so there is no scope that lets a tool list keys without also being able to mint them.

policies:manage

A build may not grade its own homework. The verdict endpoint exists so that a pipeline does not decide its own outcome — TRUSTIVAN’s policy does. A credential that could both request a verdict and rewrite the policy behind it would make every scan pass. That is not privilege escalation in the access sense; it is defeat of the control itself, which for a security product is worse. The cost here is real and recorded as debt: policy-as-code, where a repository is the source of truth for rules, needs a principal that can write policy. That is a future decision with its own controls, not something to arrive at by leaving a scope quietly grantable.

webhooks:manage

A webhook endpoint is exfiltration wearing configuration’s clothes. An endpoint is a destination every future matching event is copied to. A machine credential that could register one could arrange for a stream of an organisation’s security findings to be sent somewhere the organisation never chose — and the audit trail would read as an ordinary configuration change made by an ordinary CI key. Reading webhook configuration is a different matter and is grantable, as webhooks:read.

credentials:manage

The same argument as webhooks, and stronger. A registry credential decides what a tenant’s scans authenticate to. A machine credential that could store one could point scans at a registry the attacker controls; one that could rotate one could replace a working secret with their own. That turns a leaked CI key into control of what the customer’s scans actually pull and — through the images they then trust — into a supply-chain position. A machine credential is bounded, expiring and revocable precisely because it cannot reach this. Reading credential metadata is grantable as credentials:read, because no route returns a stored secret at any permission.

identity:manage

This one is not escalation within the authorization model; it replaces it. A credential that could point authentication at a provider the attacker controls does not gain a permission — it decides who the users are. Every other control in TRUSTIVAN is downstream of “who is this”, so this is the strongest permission in the system and no machine holds it. Among people only owners hold it: an admin who could configure the identity provider could have it assert the owner’s address and sign in as the owner. Reading identity configuration is grantable as identity:read, so a compliance pipeline can export who has access without being able to change it.

members:manage

A credential that can change a membership can grant itself a colleague. members:manage invites people, changes roles and removes members. A machine holding it could invite an address its operator controls, accept from that account, and hold a second, human session inside the tenant — one that survives revoking the key, because the membership it created is not the credential that created it. The escalation outlives the thing that performed it, which is precisely the property that makes a bounded credential bounded. Directory-sourced membership is the supported way to automate this. SCIM provisioning writes memberships under its own constrained path: it cannot demote an owner and cannot overwrite a manual grant. A tenant that wants a robot managing members configures the directory, rather than handing a general credential the authority to reshape who has access. members:read is grantable and sits in the table above, because reading the roster dismantles nothing.

workloads:manage

A credential that can delegate a workload can launder authority. workloads:manage delegates a non-human principal and revokes one. A machine holding it could mint a principal bounded by its delegator’s role, and then another bounded by that one — chaining machines until the chain’s origin is forgotten. That is the confused-deputy shape, and it defeats the single property the workload model exists to guarantee: that every non-human action traces back to a named human who chose to confer it. The service refuses a machine here before the permission is even consulted. That belt-and-braces ordering is deliberate — this is the containment property that makes delegation safe rather than a way to launder authority, so it does not rest on the scope list alone. workloads:read is grantable and sits in the table above, because reading an inventory delegates nothing.

organization:manage

The organisation’s name is part of how people decide whom to trust. organization:manage renames the organisation the request acts in (PATCH /api/v1/orgs/current). The name is what an invitation shows the person it is sent to and what every member reads when choosing which organisation to act in, so a credential that could change it could make an invitation — or the organisation menu — read as coming from somebody else. It is admin and above for people, and no machine holds it. Nothing else about the organisation is reachable from a key either: creating an organisation and choosing which one to act in are human-only routes, and a key always acts in the organisation that issued it.

organization:delete

The most terminal permission in TrustIvan, and the only one with no undo. organization:delete asks for the organisation and everything in it to be erased: every finding, asset, policy, scan, credential and audit row, with no restore path. It is owner-only among people — an admin who could reach it could end the organisation that appointed them — and no machine holds it, because a leaked CI credential that could delete the tenant turns a key rotation into a recovery exercise with nothing left to recover. Holding it is necessary but not sufficient. The request is refused for your personal organisation whatever your role, it requires you to type the organisation’s name back, and it takes effect only after a grace period during which any owner can cancel it. See Deleting an organisation.

A caller may not grant a scope they do not hold

Key creation is delegation, not manufacture. If you do not hold findings:suppress, you cannot issue a key that does. Without that rule an API key would be an escalation primitive: the cheapest route to a permission your role denies you would be to mint a credential that has it. The rule applies to the whole request, so a create that names one disallowed scope among five valid ones is refused entirely rather than issuing a quietly weaker key. Unknown scope names are refused for the same reason. Silently dropping one would issue a key that is less capable than the caller asked for, and they would discover it as a 403 in a pipeline at some later date.

What a scope failure looks like

A 403 from a scope check carries the two fields a client needs to report the problem rather than restate it:
principal is api_key or user, which distinguishes “this key was issued too narrowly” from “this person’s role does not permit it” — two problems with different fixes.

Choosing scopes for a pipeline

Start from what the job actually does. assets:write is separate from scans:create even though both change asset rows, because they change different things. A scan CREATES an asset as a side effect of looking at it; assets:write RETIRES one and records judgements about it. Archiving is the quiet verb — suppressing a finding hides one finding and leaves a justification, while archiving the asset beneath it silences every future scan of that asset — so a CI key that may request scans does not thereby gain the ability to remove things from the estate. Archiving is recorded in the administrative audit trail with the reason the caller gave. Note what the first row does not include: findings:read. A gating job does not need to read individual findings, because the verdict already carries every rule with its match count. A pipeline that never has to interpret findings is one that cannot interpret them wrongly.