scans:create and nothing else can queue a scan and cannot
read the result.
Grantable scopes
Rather than trusting this table, ask the deployment:
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 whilecredentials:manageis 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 a400 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 holdfindings: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
A403 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.
