Skip to content
Kutrion
Browse docs

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 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 jti cannot 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.