Stories · Season 1 — Meridian · Episode 09
The Leaked CI Token
In which a deploy credential appears in a public build log, and the fix is to stop having deploy credentials.

The story
The disclosure email arrived from a stranger doing Meridian a favor: a
security researcher browsing public CI logs had found a Meridian API key —
snt_-prefixed, helpfully greppable, printed in a debug line by a build
script that echoed its environment. The repository had gone public two weeks
earlier as part of Meridian’s open-source gesture; its build logs had gone
public with it.
The incident response was almost anticlimactic, which was itself a small victory. Sentinel stores API keys as digests, so the leak was the token itself, not a database’s worth; the key was owner-capped, so it could do no more than the service account it belonged to — deployment status updates, as it turned out, not patient data; and revocation was one admin call, with the ledger recording who revoked what and when. Total exposure: fourteen days, one low-privilege surface, zero evidence of use.
But Ana had read the incident the way she read all of them now: as a category, not an event. The category was stored machine secrets — every CI pipeline, cron job and internal service holding a long-lived credential that could leak through logs, forks, laptops, or the next helpful debug line. Grepping the org turned up nine of them.
The fix was to stop the category. GitHub’s CI already issued each workflow
run a short-lived OIDC token signed by GitHub, stating repository, branch
and run. Sentinel’s workload federation consumes exactly that: Ana
registered a trust — issuer pinned to GitHub’s token service, subject
pattern scoped to repo:meridian-health/*, claim rules requiring the exact
ref of the main branch — and the deploy pipeline began exchanging its
ephemeral GitHub token for an ephemeral Sentinel token, minted with a
wtrust claim naming the trust that vouched for it, so every action traces
to a specific federation decision.
Nine secrets became zero secrets. There is nothing left in the CI environment worth printing.
Why this is hard the traditional way
Machine credentials are where secret hygiene goes to die, because machines don’t follow rotation policy — people do, on behalf of machines, which is to say rarely. A stored secret’s threat surface is everywhere it has ever been: environment variables, log lines, shell history, forked repos, the laptop of whoever set it up. And its lifetime defaults to forever, because rotating it means finding everything that holds a copy — the exact knowledge nobody retained.
Federation is the known answer, but hand-rolling a token-exchange endpoint
is a security project with teeth: validating external JWTs against remote
JWKS (with caching that neither hammers the issuer nor trusts stale keys
forever), refusing alg=none and audience confusion, and mapping claims to
identity without wildcard rules that quietly trust more than intended. Most
teams, reasonably, keep the nine secrets.
How Sentinel changes the ending
Workload federation ships as a first-class flow. A trust pins an issuer and
its JWKS, constrains subjects with segment-aware wildcards
(Wildcard_subject_pattern_accepts_the_org_and_rejects_outsiders), and
layers claim rules — wildcard repository, exact ref
(Claim_rules_enforce_wildcard_repository_and_exact_ref). The validation
path has the paranoid cases pinned in tests: unknown issuers, wrong
audiences, expired tokens, and alg=none with a perfect payload are all
denied; JWKS documents are cached, refreshed on TTL, and served stale only
when a refetch fails rather than failing open. What comes out is a
short-lived Sentinel access token carrying the wtrust audit claim
(Matching_trust_mints_a_sentinel_access_token_with_wtrust_audit_claim) —
the exchange, and everything done with its proceeds, is attributable.
Where long-lived keys are still the right tool — a partner’s server, a kiosk — they stay owner-capped, digest-stored and individually revocable. The principle generalizes past CI: any workload with a platform-issued identity (GitHub, Kubernetes, cloud runtimes) can be trusted as claims rather than provisioned as secrets. The next repository Meridian open-sourced had nothing in its environment but public configuration — the researcher, had she looked, would have found only a build log with nothing to disclose.
What it costs you to ignore this
- Every stored secret is a breach with a random fuse. Logs, forks and laptops don’t expire on your rotation schedule — mostly because nothing does.
- Uncapped machine credentials escalate silently. A deploy key that can also read production data is a data breach wearing a DevOps costume.
- Unattributable machine actions poison incident response. “The CI did it” is not an answer when nine pipelines share one identity.
- Secret sprawl compounds per service. Nine today, thirty after next year’s microservices — each one provisioned in a hurry and remembered by no one.