Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions .github/actions/decrypt-secret/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
name: Decrypt secret
description: >-
Decrypt a value produced by encrypt-secret, masking the plaintext before
anything else can read it. Run this before any step that executes code you
do not control.

inputs:
encrypted_value:
description: Base64 ciphertext from encrypt-secret.
required: true
encryption_key:
description: Symmetric passphrase shared with the encrypting job.
required: true

outputs:
value:
description: Decrypted plaintext, masked in logs.
value: ${{ steps.decrypt.outputs.value }}

runs:
using: composite
steps:
- name: Decrypt
id: decrypt
shell: bash
env:
ENCRYPTED_VALUE: ${{ inputs.encrypted_value }}
ENC_KEY: ${{ inputs.encryption_key }}
run: |
: "${ENCRYPTED_VALUE:?}" "${ENC_KEY:?}"
value=$(printf '%s' "${ENCRYPTED_VALUE}" | openssl enc -d -aes-256-cbc -pbkdf2 -pass env:ENC_KEY -base64 -A)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Decrypt strips trailing newlines

Medium Severity

Capturing openssl decrypt output in $() drops all trailing newlines before masking and writing value. The action advertises multi-line support, so secrets that require a final newline (for example PEM or SSH keys) will not round-trip faithfully for callers such as hash#9192.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 98535f8. Configure here.

# Masking is per line, so a multi-line value needs each line masked.
while IFS= read -r line; do
if [ -n "${line}" ]; then
echo "::add-mask::${line}"
fi
done <<<"${value}"
delimiter=$(openssl rand -hex 16)
{
echo "value<<${delimiter}"
echo "${value}"
echo "${delimiter}"
} >>"${GITHUB_OUTPUT}"
32 changes: 32 additions & 0 deletions .github/actions/encrypt-secret/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
name: Encrypt secret
description: >-
Encrypt a value so it can cross a job boundary. GitHub silently drops masked
plaintext from job outputs; ciphertext survives the trip and is useless
without the key.

inputs:
value:
description: Plaintext to encrypt. Must never be echoed.
required: true
encryption_key:
description: Symmetric passphrase shared with the decrypting job.
required: true

outputs:
encrypted_value:
description: Base64 ciphertext, safe to expose as a job output.
value: ${{ steps.encrypt.outputs.encrypted_value }}

runs:
using: composite
steps:
- name: Encrypt
id: encrypt
shell: bash
env:
VALUE: ${{ inputs.value }}
ENC_KEY: ${{ inputs.encryption_key }}
run: |
: "${VALUE:?}" "${ENC_KEY:?}"
encrypted=$(printf '%s' "${VALUE}" | openssl enc -aes-256-cbc -pbkdf2 -salt -pass env:ENC_KEY -base64 -A)
echo "encrypted_value=${encrypted}" >>"${GITHUB_OUTPUT}"
32 changes: 13 additions & 19 deletions .github/workflows/housekeeping-dependencies.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ on:
- ".github/workflows/housekeeping-dependencies.yml"
- ".github/actions/install-renovate/**"
- ".github/actions/github-app-token/**"
- ".github/actions/encrypt-secret/**"
- ".github/actions/decrypt-secret/**"
schedule:
- cron: "0 */2 * * *"
workflow_dispatch:
Expand Down Expand Up @@ -99,7 +101,7 @@ jobs:
cancel-in-progress: false
group: renovate
outputs:
token-ciphertext: ${{ steps.encrypt.outputs.token-ciphertext }}
token-ciphertext: ${{ steps.encrypt.outputs.encrypted_value }}

steps:
- name: Get token
Expand All @@ -115,17 +117,13 @@ jobs:
cf-access-client-secret: ${{ secrets.CF_ACCESS_STAGE_CLIENT_SECRET }}

# The token is already masked, and GitHub silently drops masked values
# from job outputs, so only ciphertext can cross the job boundary. The
# plaintext must never be echoed here.
# from job outputs, so only ciphertext can cross the job boundary.
- name: Encrypt token
id: encrypt
env:
TOKEN: ${{ steps.app-token.outputs.token }}
ENC_KEY: ${{ secrets.RENOVATE_TOKEN_ENC_KEY }}
run: |
: "${TOKEN:?}" "${ENC_KEY:?}"
ciphertext=$(printf '%s' "${TOKEN}" | openssl enc -aes-256-cbc -pbkdf2 -salt -pass env:ENC_KEY -base64 -A)
echo "token-ciphertext=${ciphertext}" >>"${GITHUB_OUTPUT}"
uses: $/.github/actions/encrypt-secret
with:
value: ${{ steps.app-token.outputs.token }}
encryption_key: ${{ secrets.RENOVATE_TOKEN_ENC_KEY }}

renovate:
name: Renovate
Expand All @@ -148,14 +146,10 @@ jobs:
# First step, so the plaintext is masked before anything else runs.
- name: Decrypt token
id: app-token
env:
TOKEN_CIPHERTEXT: ${{ needs.mint-token.outputs.token-ciphertext }}
ENC_KEY: ${{ secrets.RENOVATE_TOKEN_ENC_KEY }}
run: |
: "${TOKEN_CIPHERTEXT:?}" "${ENC_KEY:?}"
token=$(printf '%s' "${TOKEN_CIPHERTEXT}" | openssl enc -d -aes-256-cbc -pbkdf2 -pass env:ENC_KEY -base64 -A)
echo "::add-mask::${token}"
echo "token=${token}" >>"${GITHUB_OUTPUT}"
uses: $/.github/actions/decrypt-secret
with:
encrypted_value: ${{ needs.mint-token.outputs.token-ciphertext }}
encryption_key: ${{ secrets.RENOVATE_TOKEN_ENC_KEY }}

- name: Install Renovate
uses: $/.github/actions/install-renovate
Expand All @@ -182,7 +176,7 @@ jobs:
- name: Run Renovate
env:
LOG_LEVEL: ${{ inputs.logLevel || 'info' }}
RENOVATE_TOKEN: ${{ steps.app-token.outputs.token }}
RENOVATE_TOKEN: ${{ steps.app-token.outputs.value }}
RENOVATE_FORCE: ${{ inputs.overrideSchedule && '{"schedule":null}' || '' }}
RENOVATE_DRY_RUN: ${{ env.dry_run == 'disabled' && 'null' || env.dry_run }}
RENOVATE_PLATFORM_COMMIT: enabled
Expand Down
Loading