feat(docker): forward BuildKit secrets to the image build - #15
Conversation
Callers that need a credential during the build had no way to supply one. build-args are recoverable from image history, so a token cannot go there. Adds an optional build-secrets input, passed straight to docker/build-push-action's secrets parameter. Empty input mounts nothing, so existing callers are unaffected.
📝 WalkthroughWalkthroughThe reusable Docker workflow now accepts newline-separated BuildKit secret definitions and passes them to the Docker build action. ChangesBuild secret support
Estimated code review effort: 2 (Simple) | ~5 minutes 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.github/workflows/docker-build-push.yml:
- Around line 87-93: Update the workflow step that prepares or passes
build-secrets so each individual credential value is registered with GitHub
Actions masking before docker/build-push-action runs. Preserve the existing
id=value, one-per-line format and ensure every parsed secret value is masked
separately, including when ACTIONS_STEP_DEBUG is enabled.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 5049d0d4-eb57-43a4-881b-9b8a00fece07
📒 Files selected for processing (1)
.github/workflows/docker-build-push.yml
| build-secrets: | ||
| description: >- | ||
| BuildKit secrets as `id=value`, one per line, exposed to the build via | ||
| `RUN --mount=type=secret,id=<id>`. Use this for credentials a build must | ||
| read: `build-args` are recoverable from image history, mounted secrets | ||
| are not written to any layer. | ||
| required: false |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -eu
file=".github/workflows/docker-build-push.yml"
printf '%s\n' '--- workflow context ---'
sed -n '70,100p' "$file"
sed -n '165,195p' "$file"
printf '%s\n' '--- references to build-secrets and debug settings ---'
rg -n -C 3 'build-secrets|ACTIONS_STEP_DEBUG|RUNNER_DEBUG|docker/build-push-action|secret' .github README.md 2>/dev/null || true
printf '%s\n' '--- repository metadata ---'
git ls-files '.github/workflows/docker-build-push.yml'Repository: samuelho-dev/git-flow
Length of output: 27362
🌐 Web query:
docker/build-push-action structured secrets multiline secret masking debug logs GitHub Actions
💡 Result:
To handle multiline secrets with the docker/build-push-action in GitHub Actions, you must wrap the key-value pair in double quotes [1][2]. This enables the action to correctly parse the multiline content and maintain the necessary formatting [1]. Implementation details: - Formatting: Use the secrets input in your workflow YAML, ensuring multiline values are quoted. If your secret contains double quotes, you must double-escape them [1][2]. yaml with: secrets: | "MY_MULTILINE_SECRET=${{ secrets.GPG_KEY }}" - Important Note on Whitespace: The underlying parser for GitHub Actions inputs may strip trailing newlines from multiline secrets [3]. If your application requires these trailing newlines (e.g., for certain SSH key formats), you may need to append a newline character during the Docker build process, such as by using echo "" >> /path/to/secret within your Dockerfile after the secret is mounted [3]. - Debug Logs and Masking: While GitHub Actions attempts to mask secrets in logs, they may occasionally appear in debug-level output [4]. This is generally considered a function of the GitHub Actions runner runtime rather than the docker/build-push-action itself [4]. To avoid accidental exposure, ensure you are using dedicated secret mounts (via --mount=type=secret in your Dockerfile) rather than passing sensitive data as build arguments (build-args), as build arguments are not masked and will be persisted in the final image layers [4]. - Usage: Mount the secret in your Dockerfile to consume it securely during the build [1][5]: dockerfile RUN --mount=type=secret,id=MY_MULTILINE_SECRET \ cat /run/secrets/MY_MULTILINE_SECRET
Citations:
- 1: https://docs.docker.com/build/ci/github-actions/secrets/
- 2: https://github.com/docker/build-push-action/blob/v2.4.0/docs/advanced/secrets.md
- 3: Whitespace trimmed from multi-line secrets input docker/build-push-action#1242
- 4: secrets are visibible in the debug messages docker/build-push-action#1025
- 5: doc bug: secrets, secret-envs and secret-files documentation is horrible docker/build-push-action#1329
🏁 Script executed:
#!/bin/bash
set -eu
printf '%s\n' '--- docker/build-push-action v7.3.0 metadata ---'
curl -fsSL https://api.github.com/repos/docker/build-push-action/git/ref/tags/v7.3.0 |
jq '{ref, object}'
curl -fsSL https://raw.githubusercontent.com/docker/build-push-action/v7.3.0/src/context.ts |
rg -n -C 5 'secret|Secret|debug|input'
printf '%s\n' '--- action runtime and input logging ---'
curl -fsSL https://raw.githubusercontent.com/docker/build-push-action/v7.3.0/src/main.ts |
rg -n -C 5 'getInput|debug|context|secret'
curl -fsSL https://raw.githubusercontent.com/docker/build-push-action/v7.3.0/src/context.ts |
sed -n '1,240p'
printf '%s\n' '--- cited issue ---'
curl -fsSL https://api.github.com/repos/docker/build-push-action/issues/1025 |
jq '{title, state, body, html_url}'Repository: samuelho-dev/git-flow
Length of output: 20794
🌐 Web query:
site:docs.github.com actions secure use secrets structured values masking multiline secrets
💡 Result:
To secure secrets in GitHub Actions, adhere to the following best practices: Avoid Structured Data as Secrets GitHub strongly recommends against storing structured data (such as JSON, XML, or YAML blobs) as a single secret [1][2]. GitHub's automated redaction system relies on matching exact secret values; encapsulating sensitive data within structured formats significantly reduces the effectiveness of this redaction, increasing the risk that secrets will be exposed in workflow logs [1][2]. Instead, store each individual sensitive value as its own separate secret [1][2]. Masking Sensitive Values If you generate sensitive values during a workflow (e.g., tokens or keys derived from other secrets) or need to mask non-secret data, use the ::add-mask:: workflow command [1][3][4]. * Syntax: echo "::add-mask::<value>" [3][4] * Behavior: When a value is masked, it is treated as a secret for the remainder of the job, and any instance of that exact string appearing in the logs will be replaced with * [3][4]. * Important: Masking must be performed once per job, and the value must be registered before it is printed to the logs or used in other commands [3][4]. Handling Multiline Secrets If you must use multiline strings, avoid using them as secrets if possible. If they are unavoidable, be aware that standard redaction may be less reliable for complex, arbitrary multiline content. For passing multiline strings as outputs or environment variables, use delimiters to prevent shell injection or formatting issues: echo "{name}<<{delimiter}" >> $GITHUB_OUTPUT echo "{value}" >> $GITHUB_OUTPUT echo "{delimiter}" >> $GITHUB_OUTPUT If the value is completely arbitrary and contains the chosen delimiter, do not use this format; instead, write the secret to a temporary file on the runner to avoid command-line parsing issues [4]. General Security Principles * Use Secrets for Sensitive Info: Do not store sensitive data as plaintext in workflow files [1][2]. * Register Transformed Secrets: If a secret is transformed (e.g., Base64 encoded, signed, or used to generate a new token), register the resulting value using ::add-mask:: to ensure it is also redacted if printed [1][2]. * Avoid Command-Line Passing: When possible, pass secrets via environment variables rather than command-line arguments to prevent them from being visible in process monitoring tools like ps [5].
Citations:
- 1: https://docs.github.com/en/enterprise-server@3.21/actions/reference/security/secure-use
- 2: https://docs.github.com/en/actions/reference/security/secure-use
- 3: https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-commands
- 4: https://docs.github.com/en/enterprise-cloud@latest/actions/reference/workflows-and-actions/workflow-commands
- 5: https://docs.github.com/en/actions/how-tos/write-workflows/choose-what-workflows-do/use-secrets
🌐 Web query:
site:github.com/docker/build-push-action/issues/1025 secrets debug logs masking
💡 Result:
To prevent secrets from appearing in debug logs, the most effective approach is to avoid passing sensitive data through mechanisms that are logged by default, such as build arguments or environment variables [1]. Instead, use purpose-built secret management features, such as Docker's build-time secret mounting, which prevents secrets from being exposed in image layers or logs [1]. When integrating with CI/CD platforms like GitHub Actions, rely on the platform's native secret masking logic [1]. If secrets still appear, it may indicate a failure in the runner's masking engine or an insecure implementation, such as using build arguments where secret mounts are appropriate [1]. As a best practice, always favor dedicated secret stores and mounting mechanisms over passing sensitive information directly into commands or configurations that are likely to be output to debug logs [1].
Citations:
🏁 Script executed:
#!/bin/bash
set -eu
printf '%s\n' '--- input-list parser ---'
curl -fsSL https://raw.githubusercontent.com/docker/actions-toolkit/v0.1.0/src/util.ts 2>/dev/null |
rg -n -C 8 'getInputList' || true
curl -fsSL https://raw.githubusercontent.com/docker/actions-toolkit/main/src/util.ts 2>/dev/null |
rg -n -C 12 'getInputList' || true
printf '%s\n' '--- build-push-action secret resolver ---'
curl -fsSL https://raw.githubusercontent.com/docker/actions-toolkit/main/src/buildx/build.ts 2>/dev/null |
rg -n -C 8 'resolveSecretString|hasGitAuthTokenSecret' || true
printf '%s\n' '--- standalone transformation check ---'
python3 - <<'PY'
import json
aggregate = "TOKEN_ID=alpha-secret\nCERT_ID=bravo-secret"
parsed = [line for line in aggregate.splitlines() if line.strip()]
debug_inputs = json.dumps({"secrets": parsed}, separators=(",", ":"))
debug_args = json.dumps(
["build", "--secret", parsed[0], "--secret", parsed[1]],
separators=(",", ":"),
)
print("aggregate:", repr(aggregate))
print("parsed:", parsed)
print("debug inputs:", debug_inputs)
print("debug args:", debug_args)
for secret in ("alpha-secret", "bravo-secret"):
print(secret, "in debug inputs:", secret in debug_inputs,
"in debug args:", secret in debug_args)
print("aggregate in debug inputs:", aggregate in debug_inputs)
print("aggregate in debug args:", aggregate in debug_args)
PYRepository: samuelho-dev/git-flow
Length of output: 2917
Mask each credential inside build-secrets.
When ACTIONS_STEP_DEBUG is enabled, docker/build-push-action logs the parsed secrets input. The action logs each id=value entry separately, so masking only the original multi-line secret does not mask the individual credentials. Mask each credential before the action, or use separate workflow secrets.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.github/workflows/docker-build-push.yml around lines 87 - 93, Update the
workflow step that prepares or passes build-secrets so each individual
credential value is registered with GitHub Actions masking before
docker/build-push-action runs. Preserve the existing id=value, one-per-line
format and ensure every parsed secret value is masked separately, including when
ACTIONS_STEP_DEBUG is enabled.
Why
A build that needs a credential had no way to receive one.
build-argsare the only existing channel and they are recoverable from image history, so a token cannot go through them.Concretely: creativetoolkits' web image builds without
SENTRY_AUTH_TOKEN, sowithSentryConfigdisables source-map upload. Every Sentry release for that project carries 0 artifacts, and production stack traces cannot be de-minified.What
One optional
build-secretssecret, forwarded todocker/build-push-action'ssecrets:parameter. Callers passid=valuelines and read them in a Dockerfile viaRUN --mount=type=secret,id=<id>; the value is never written to a layer.Compatibility
Additive. An unset input forwards an empty string, which mounts nothing, so all five current call sites keep their present behaviour with no change.
Summary by CodeRabbit