api-integrations
CI pipelines without stored secrets
Federate your CI provider's OIDC identity with Kutrion so a pipeline exchanges its own job token for a short-lived agent, and holds no Kutrion credential at all.
Updated Oct 3, 2026
CI is usually the worst-governed credential store an organization has: long-lived secrets pasted into CI variables, scoped far wider than any single job needs, rarely rotated, and invisible in an audit trail. Kutrion lets a pipeline avoid holding one entirely.
GitLab CI and GitHub Actions both mint a short-lived, signed OIDC token for every job, carrying the project, ref and pipeline identity. You register a trust describing which of those tokens you accept and what they may do. The job exchanges its token for a short-lived agent principal and holds nothing afterwards.
Register a trust
An org admin registers one trust per pipeline identity you want to accept:
POST /agents/oidc/trusts
{
"name": "deploy pipeline",
"issuer": "https://gitlab.example.com",
"audience": "vaultterm",
"subject_pattern": "project_path:group/repo:*",
"scopes": ["vault.list", "vault.use", "ssh.exec"],
"ttl_minutes": 30,
"egress_class": "lan"
}
The trust — not the token — decides everything that matters. Issuer, audience, scopes, lifetime and the accountable owner all come from what you registered; a token cannot ask for more than the trust allows, and cannot nominate its own issuer.
A trust can also carry a resource_scope, the same vault and connection restriction an agent
carries (see Connect an AI agent for how the two lists read). Change it
later with PUT /agents/oidc/trusts/{id}/resource-scope. Any org admin can remove ids from it, so
an incident can be contained without the owner; only the trust’s owner can add ids or widen it. Ids
an edit adds must be reachable by the trust’s owner, and each change is audited with before and
after. The scope is
copied into each principal at exchange, so an edit applies to the next pipeline run, not to
principals already issued.
The subject pattern is the important field. It is what binds a grant to a specific project, and
optionally a specific ref. project_path:group/repo:* accepts any ref of that project;
project_path:group/repo:ref_type:branch:ref:main accepts only main. A pattern that matches
everything is refused at registration — it would let any project at that issuer mint a principal.
Use it in a pipeline
The work itself goes in a kutrion.yml in your repo. It names connections, never credentials, and
carries no secret — so it is reviewable like any other source:
name: deploy
connections:
api: prod-api-01
steps:
- name: restart the service
exec: { connection: api, command: "systemctl restart api" }
expect: { exit_code: 0 }
GitLab, requesting a token and exchanging it:
deploy:
id_tokens:
KT_ID_TOKEN:
aud: vaultterm
script:
- export KT_AGENT_TOKEN=$(kt ci-login --trust $KT_OIDC_TRUST_ID)
- kt run kutrion.yml --reporter junit --out report.xml
artifacts:
reports:
junit: report.xml
GitHub Actions, where the job requests the token itself:
permissions:
id-token: write
steps:
- run: |
export KT_AGENT_TOKEN=$(kt ci-login --trust ${{ vars.KT_OIDC_TRUST_ID }})
kt run kutrion.yml --reporter junit --out report.xml
kt run is the command a pipeline principal drives, and the only one. The interactive commands —
kt exec, kt sftp and the rest — act as the human session stored in your own keychain, and an
exchanged principal is not one; they will tell you to log in. That is deliberate rather than a gap:
a pipeline’s steps run through the agent gateway, where your organization’s tool policy decides what
a principal may do at all and every step is recorded against the pipeline’s identity.
kt run --dry-run validates a job file offline and needs no principal, so a merge request can check
its own kutrion.yml before anything is exchanged.
The trust id is not a secret — it identifies which trust you are exchanging against, and naming it is what stops the exchange being ambiguous when several organizations federate the same CI host. Put it in a plain CI variable.
What the exchange enforces
- Signature, issuer and audience are verified against the issuer’s published keys. RS256 only — unsigned and HMAC-signed tokens are refused outright.
- The subject must match the trust’s pattern. A token from the right issuer for the wrong repository is refused.
- One token, one principal. A job token can be lifted out of a compromised job, so replaying it
yields nothing. A token without a
jticannot be replay-protected and is refused. - Freshness. A token minted hours ago is refused even if its expiry is generous.
- Every failure answers identically, so a prober cannot map which projects you trust.
What you get back
A normal agent principal, with the trust’s scopes and lifetime, named for the pipeline that asked — so your audit trail attributes a run to a project, ref and pipeline rather than to a shared service account. Everything else in the platform then applies unchanged: redacted output, the flight recorder, plan limits and the kill switch.
Revoking a trust stops all future exchanges immediately. Credentials already issued run out their own short lifetime, or can be revoked individually or in bulk from the console.