For a small binary file such as a signing certificate, base64 encode it, store the text as a secret, and decode it on the runner. For files over 48 KB, GitHub documents an encrypted file workaround with the passphrase stored as a secret. Base64 is encoding, not encryption, and the larger workaround loses log redaction.
The small file route
GitHub's secrets guide describes this for small binary blobs:
base64 -w 0 signing-cert.p12 > cert.b64 # copy the text into the secret
In the workflow, decode it into a temporary path with restricted permissions, use it, and delete it. The docs show the decode step with the base64 decode flag.
The guide includes a warning worth repeating: base64 only converts binary to text, and is not a substitute for actual encryption. The secret store provides the protection, not the encoding.
The over 48 KB route
Secrets larger than 48 KB need a workaround. The docs describe it: encrypt the file with gpg, commit the encrypted file to the repository, store the passphrase as a secret, and decrypt in a workflow step.
There is an important catch, stated plainly in the guide: when using this workaround, GitHub does not redact secrets that are printed in logs. The decrypted content is not a registered secret, so any accidental print is visible.
| Approach | Best for | Redaction | Main risk |
|---|---|---|---|
| Base64 secret | Small certificates and keys under the limit | Applies to the stored value | Encoded variants of the value may not match the mask |
| Encrypted file plus passphrase secret | Large files | Not applied to decrypted content | Printed content shows in logs |
| Fetch from a secrets manager at run time | Anything sensitive or shared | Depends on how you handle it | Needs a trust setup and access policy |
Masking and derived values
GitHub's guidance says redaction depends on exact matches. If you transform a secret, for example by base64 encoding it again, or building a signed token from it, register the derived value as a secret too. You can mask non-secret sensitive values with the add-mask command. Do not put several values in one JSON or YAML blob; create a separate secret for each value.
Steps to do it safely
- Ask whether the file must be in CI at all. A signing key might be better kept in a managed signing service or secrets manager and fetched with short lived credentials.
- If you store it, use an environment secret with required reviewers and a branch rule, not a repository wide secret.
- Decode to a temporary location, set tight file permissions, and delete after use.
- Never print the file or its decoded text. Check logs after the first run.
- Avoid passing the content on a command line. GitHub notes command line processes may be visible to other users; use environment variables or standard input instead.
- Rotate the certificate or key on a schedule and when anyone with write access leaves.
Alternatives worth a look
Cloud key management and secrets manager integrations let a job use a key without ever holding its bytes, for example by asking a service to sign. Where your toolchain supports it, that removes the file from the runner.
Quick check
Is a base64 secret safer than a plain one?
No. It is only a way to fit binary data into a text field. The safety comes from the secret store and the access rules around it.
What happens if a secret is not set?
An expression that references it returns an empty string, so a typo can fail quietly. Check that the decoded file exists before using it.
Safeguard policies and gates can review pull requests that add credential handling to workflows. A free scan is at gold.safeguard.sh/scan.
Sources
Last reviewed: 19 October 2026.