3.3.2. Consuming and Managing Secrets Programmatically
💡 First Principle: Secrets enter a workflow only through explicit reference — ${{ secrets.NAME }} or the secrets: mapping of a called workflow or action input — and they leave GitHub only as ciphertext, which is why the REST API can write a secret but never read one back.
Consumption patterns matter because where you put a secret changes its exposure:
- name: Deploy
env:
API_TOKEN: ${{ secrets.API_TOKEN }} # ✅ preferred: environment variable
run: ./deploy.sh # script reads $API_TOKEN
Passing a secret as a command-line argument (./deploy.sh ${{ secrets.API_TOKEN }}) exposes it in process listings on the runner and risks it landing in logs through shell tracing — the environment-variable form is the recommended pattern for the same reason it's the recommended fix for script injection in 6.1.2.
The REST API surface is small and worth recognizing. GET /repos/{owner}/{repo}/actions/secrets lists secret names and timestamps only — never values. Writing requires a two-step dance: fetch the repository's or organization's public key (GET .../actions/secrets/public-key), encrypt the value client-side with libsodium, then PUT .../actions/secrets/{name} with the ciphertext and key_id. The gh CLI wraps all of this in gh secret set NAME. Organization endpoints add a visibility field (all, private, selected) and, for selected, a companion endpoint to manage the repository list. Variables have a parallel, simpler API — no encryption step, and values are readable.
This asymmetry is the design point: rotation is straightforward (overwrite the value; running jobs keep the old one, new jobs get the new one), while recovery is impossible. If nobody has the plaintext, the only path is regenerating the credential at its source.
⚠️ Exam Trap: No API, CLI, or UI can retrieve a secret's value — the list endpoint returns names only. Any answer that involves "read the existing secret to copy it to another repository" is wrong; you must have the original value, or rotate.
Reflection Question: Secret writing requires fetching a public key and encrypting client-side rather than just POSTing a value over HTTPS. What threat does that extra step address that TLS alone does not?