BXUP Track progress & test yourself — free
War Room · Field diagnosis · ~2 min read
WAR ROOM · FIELD DIAGNOSIS

WR-107 · The API key printed into a public build log

Symptom

An LLM provider emails "your API key may have been exposed", or the monthly bill jumps for calls nobody made. Searching the repo shows no key in any file. The key is in the Actions log of a public repo: a step's output contains the raw value — readable by anyone on the internet, in a log that lives with the run history.

Context

A CI-only auth failure was being debugged: the workflow's secret-using step kept getting 401s. To check whether the secret was arriving, a teammate added run: echo "key is $PROVIDER_API_KEY" — "temporarily". The repo is public, so its run logs are public too. Linked modules: 4.6 (secrets in CI), 3.2 (API key discipline), 0.2 (WR-006, the pushed secret).

Diagnosis

Actions masks repository secrets in logs: the exact value is replaced with ***. So how did the key leak? Masking matches the literal string. Any transformed print evades it — the value reversed, base64-encoded, split across two echoes, or embedded inside a longer URL. In this incident the debug step printed request headers, and the key appeared inside Authorization: Bearer ... with spacing the mask did not match. The moment any form of the key reached the log of a public repo, it must be treated as stolen: bots index public CI logs the same way they index public commits (3.2's lesson). Deleting the step or the log afterwards does not un-expose anything.

The fix

Rotation first, cleanup second — WR-006's order, unchanged. (1) Revoke the key at the provider and issue a fresh one. (2) Update the repository secret with the new value — one place, no code change, which is exactly why secrets live there. (3) Delete the echo/debug step. (4) Check the provider's usage dashboard for calls during the exposure window. If verification is still needed, print a property, never the value: print('key length:', len(os.environ['PROVIDER_API_KEY'])) confirms arrival and leaks nothing.

Prevention

Never print a secret, even "temporarily" — the temporary echo is how nearly every log leak is born. Verify secrets by length or presence only. Keys enter steps only through env: lines reading ${{ secrets.NAME }}; a key pasted into workflow YAML is a key committed to the repo. Review workflow diffs with the same care as code diffs — a new line that prints environment content should stop a review. Enable GitHub's secret scanning and push protection on the repo. Treat masking as a seatbelt, not a licence.

In an interview

Q: "How do secrets reach a GitHub Actions step, and what do you do if one shows up in a log?" Strong answer: repository secrets injected per-step via env:, never hardcoded; masking exists but is evadable by any transformation, with one concrete example; rotate-first response order; and the print-a-length verification habit. Bonus signal: noting that public repo logs are public artifacts, so exposure time — not deletion time — defines the blast radius.

This is the free reading view. Open BXUP to track your progress, run the hands-on labs, and prove it with original, exam-pattern assessments.
© Anvi Innovate · BXUP — Be Exponentially Upskilled · BXUP is an independent learning platform, not affiliated with or endorsed by AWS, Microsoft, Google, NVIDIA, IBM, Salesforce, Databricks, Oracle or Anthropic. All trademarks belong to their respective owners. · bxup.in