Skip to content

Build1 publisher2 min readPublished

One CI secret across every building turns a leaked log into an accounting dispute

A dev.to walkthrough rotates a production API key with two scoped credentials, an explicit activation step, and a revoke that waits for the old key's telemetry. The design assumes a provider that can scope, report and revoke.

The Engineer · Build desk

Illustration accompanying One CI secret across every building turns a leaked log into an accounting dispute

What happened

  • A dev.to walkthrough rotates a production API key by running two narrowly scoped keys, switching traffic at an explicit activation step, and revoking the old key only after logs and live requests confirm the cutover.
  • The post puts attribution first: one shared CI secret makes every building, job and deployment look like the same caller on a bill, so a leaked build log is both a security incident and an accounting dispute.
  • It offers three patterns. A dual-key cutover where the API allows overlapping credentials; a brokered short-lived token where policy bars CI from a long-lived secret; a manual change window with a documented outage budget.
  • The control plane is split into four lanes. Issue creates the smallest useful permission, distribution hands it to one job, observation ties the key fingerprint to a run, and retirement revokes after a quiet period.
  • The sample scope pins the key to environment production, tenant IDs building-017 and building-021, and the actions leases:read and work-orders:write.

Compiled by The EngineerSomething wrong?How this is made

Why it matters

  • constraint The dual-key cutover exists only where the provider will hold two live credentials at once; on an API without overlap or revoke, a team is scheduling an outage window and calling it a rotation.
  • decision Moving the administrative token into a protected rotation job takes key creation and revocation away from pull-request workflows, so somebody has to own that job and its approvals.
  • exposure With one shared key, the telemetry cannot settle which tenants the exposed credential was billing for during the window it was public. The finance side of a leak is unanswerable.
  • cost The recurring cost is the observability work: per-key fingerprints joined to run IDs, plus a log allowlist and a fake-key test that every new log line has to pass.

A retried issue call is where a rotation can fork. The dev.to post's `POST /keys` call sends an `idempotency-key` header built from the scope's tenant list, written as `property-ci-${scope.tenantIds.join("-")}` [9]. For the sample scope of `building-017` and `building-021`, that resolves to `property-ci-building-017-building-021`. It resolves the same way on the retry, because the value is a function of the scope [10]. Without that guard, according to the post, a second POST can create two valid keys and the cutover becomes ambiguous; the same rule applies to revoke [12]. On a `429`, the advice is to honor `Retry-After` with capped exponential backoff [11].

Two live production keys are harder to clean up than a failed rotation. Someone has to work out which one live traffic is using before either is revoked.

The observe lane is defined as joining the key fingerprint to a workflow run [7]. That join only works if the provider reports usage per key. The runtime example logs three fields: `ok`, `status`, and the `x-request-id` response header [15]. The post's log parser allows `keyId`, `status` and `requestId`, then rejects values that look like bearer tokens [16]. A pre-merge test feeds a fake key through the logger and asserts the output contains neither the value nor a URL with credentials [17]. The author calls the client's shape "intentionally boring" and writes: "Boring is observable." [18]

Everything here assumes an account platform with three operations: create a key scoped below the account, report what that key did, and revoke it on request. The post is explicit that its endpoint names are placeholders for your platform's documented API, and that the contract that matters is method, scope, and response handling [19].

The privilege split is the cheapest part to adopt. The administrative token lives in a protected rotation job instead of every pull-request job, and the application job receives only the scoped runtime key. In GitHub Actions the secret is mapped to an environment variable at the step boundary, with shell tracing avoided [13]. A failed request reports an HTTP status and a correlation ID, never the Authorization header [14].

For a printed secret, the rule the post gives is to assume the log is public from that moment and not wait for proof of use [20]. The sequence after that is to freeze the affected workflow, preserve the run ID, and issue the replacement with a narrower scope. Then compare request telemetry for the old key against the log's time window and tenant set [21]. That comparison is only possible if the issue lane bound the key to a tenant set in the first place [6].

What to watch

  • A worked brokered-exchange example: the post describes the short-lived-token path but only implements the dual-key issue call.
  • Providers adding a revoke-with-overlap operation would close the manual change window the post treats as a gap to fix.
  • Whether teams actually ship the pre-merge logger test, or leave the log allowlist as an unenforced convention.
Loading claim ledger
Loading source directory links
Loading share composer
Loading topic controls
Loading related stories