Summary
GH-2423 (PR #2424, merged as `f47ad6a5`) added a `Resolve token` step that writes the resolved token to `$GITHUB_OUTPUT`. GitHub auto-masks registered secrets passing through outputs, so this is not a confirmed leak today, but it's a brittle pattern: any future transformation (trim, base64-encode, reformat) breaks the masker, after which the raw token leaks into step logs.
Replace with the idiomatic `${{ secrets.A || secrets.B }}` expression — the secret is never written to a step output, masking is robust against future code changes, and the workflow gets shorter.
Required changes (`.github/workflows/docs-version-sync.yml`)
Remove the entire `Resolve token` step (lines ~49-60 today):
```yaml
- name: Resolve token
id: token
env:
PILOT_DOCS_PAT: ${{ secrets.PILOT_DOCS_PAT }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
if [ -n "$PILOT_DOCS_PAT" ]; then
echo "token=$PILOT_DOCS_PAT" >> "$GITHUB_OUTPUT"
else
echo "::warning::PILOT_DOCS_PAT not set — sync-docs.yml will not chain from the auto-merge"
echo "token=$GITHUB_TOKEN" >> "$GITHUB_OUTPUT"
fi
```
Replace with a warning-only step:
```yaml
- name: Warn if PAT missing
if: ${{ !secrets.PILOT_DOCS_PAT }}
run: echo "::warning::PILOT_DOCS_PAT not set — auto-merge will use GITHUB_TOKEN, sync-docs.yml will not chain"
```
Update the two consumer references:
- `Create Pull Request`: `token: ${{ steps.token.outputs.token }}` → `token: ${{ secrets.PILOT_DOCS_PAT || secrets.GITHUB_TOKEN }}`
- `Enable auto-merge`: `GH_TOKEN: ${{ steps.token.outputs.token }}` → `GH_TOKEN: ${{ secrets.PILOT_DOCS_PAT || secrets.GITHUB_TOKEN }}`
GitHub treats unset secrets as empty strings → falsy in `||` and `if:` expressions. The `||` operator's result is still a registered-secret value, fully masked through the rest of the run.
Acceptance
Out of scope
Files
- `.github/workflows/docs-version-sync.yml`
Summary
GH-2423 (PR #2424, merged as `f47ad6a5`) added a `Resolve token` step that writes the resolved token to `$GITHUB_OUTPUT`. GitHub auto-masks registered secrets passing through outputs, so this is not a confirmed leak today, but it's a brittle pattern: any future transformation (trim, base64-encode, reformat) breaks the masker, after which the raw token leaks into step logs.
Replace with the idiomatic `${{ secrets.A || secrets.B }}` expression — the secret is never written to a step output, masking is robust against future code changes, and the workflow gets shorter.
Required changes (`.github/workflows/docs-version-sync.yml`)
Remove the entire `Resolve token` step (lines ~49-60 today):
```yaml
id: token
env:
PILOT_DOCS_PAT: ${{ secrets.PILOT_DOCS_PAT }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
if [ -n "$PILOT_DOCS_PAT" ]; then
echo "token=$PILOT_DOCS_PAT" >> "$GITHUB_OUTPUT"
else
echo "::warning::PILOT_DOCS_PAT not set — sync-docs.yml will not chain from the auto-merge"
echo "token=$GITHUB_TOKEN" >> "$GITHUB_OUTPUT"
fi
```
Replace with a warning-only step:
```yaml
if: ${{ !secrets.PILOT_DOCS_PAT }}
run: echo "::warning::PILOT_DOCS_PAT not set — auto-merge will use GITHUB_TOKEN, sync-docs.yml will not chain"
```
Update the two consumer references:
GitHub treats unset secrets as empty strings → falsy in `||` and `if:` expressions. The `||` operator's result is still a registered-secret value, fully masked through the rest of the run.
Acceptance
Out of scope
Files