Skip to content

ci: use commit hash for github action version instead of version number or branch - #895

Closed
richm wants to merge 1 commit into
mainfrom
use-commit-hash
Closed

ci: use commit hash for github action version instead of version number or branch#895
richm wants to merge 1 commit into
mainfrom
use-commit-hash

Conversation

@richm

@richm richm commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

The latest security guidance is to use the full commit hash, which is immutable,
instead of a tag or version, which is mutable, for the reference to a version
of a github action. There are known attacks which inserted unauthorized code
in a version tag and moved the tag. This prevents this sort of attack, at the
cost of more maintenance burden, but dependabot will largely take care of this
for us.

Each version or tag has been replaced with the corresponding commit hash - in some
cases, this is not the latest commit on the main branch, so I would expect to see
some dependabot updates in the near future. I thought it was safer to do it this
way - preserve existing behavior/functionality - rather than replace and upgrade
to a newer version at the same time.

Signed-off-by: Rich Megginson rmeggins@redhat.com

Summary by CodeRabbit

  • Chores
    • Pinned workflow actions and supporting tooling to immutable revisions.
    • Improved the consistency and reproducibility of automated builds, tests, documentation, security scans, and release workflows.
    • Preserved existing workflow behavior while reducing reliance on changeable version tags and branches.

@richm richm self-assigned this Aug 12, 2026
…er or branch

The latest security guidance is to use the full commit hash, which is immutable,
instead of a tag or version, which is mutable, for the reference to a version
of a github action.  There are known attacks which inserted unauthorized code
in a version tag and moved the tag.  This prevents this sort of attack, at the
cost of more maintenance burden, but dependabot will largely take care of this
for us.

Each version or tag has been replaced with the corresponding commit hash - in some
cases, this is not the latest commit on the main branch, so I would expect to see
some dependabot updates in the near future.  I thought it was safer to do it this
way - preserve existing behavior/functionality - rather than replace and upgrade
to a newer version at the same time.

Signed-off-by: Rich Megginson <rmeggins@redhat.com>
@coderabbitai

coderabbitai Bot commented Aug 12, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Changes

The pull request replaces mutable GitHub Actions references and the tox-lsr release version with fixed commit SHAs across testing, quality, documentation, release, analysis, and status workflows.

Workflow dependency pinning

Layer / File(s) Summary
Test toolchain pins
.github/workflows/ansible-*.yml, .github/workflows/python-unit-test.yml, .github/workflows/qemu-kvm-integration-tests.yml
Testing workflows pin action and tox-lsr references to specific commits.
Quality workflow pins
.github/workflows/codespell.yml, .github/workflows/markdownlint.yml, .github/workflows/pr-title-lint.yml, .github/workflows/shellcheck.yml, .github/workflows/woke.yml
Quality workflows pin checkout and validation action references.
Documentation and release pins
.github/workflows/build_docs.yml, .github/workflows/changelog_to_tag.yml, .github/workflows/test_converting_readme.yml
Documentation and release workflows pin checkout, artifact, tag, release, push, and Galaxy action references.
Analysis and status pins
.github/workflows/codeql.yml, .github/workflows/tft.yml, .github/workflows/weekly_ci.yml
Analysis, Testing Farm, commit-status, checkout, and GitHub Script references now use fixed commits.

Possibly related PRs

🚥 Pre-merge checks | ✅ 5 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description Format ⚠️ Warning The template requires Enhancement/Feature, Reason, and Result sections; the actual commit description contains none of these labels, although it includes Signed-off-by. Rewrite the PR description with Enhancement or Feature, Reason, and Result sections, and retain the valid Signed-off-by line.
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title follows Conventional Commits format and clearly describes pinning GitHub Actions to commit hashes.
Description check ✅ Passed The description explains the security reason, implementation approach, preserved behavior, and expected Dependabot maintenance.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🤖 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/build_docs.yml:
- Line 28: Disable credential persistence on all checkout steps: update
.github/workflows/build_docs.yml lines 28-28 and 51-56,
.github/workflows/changelog_to_tag.yml line 25, and
.github/workflows/test_converting_readme.yml line 32 with persist-credentials:
false. Add explicit authentication to the git ls-remote, git push, and git fetch
--all --tags commands, while preserving the existing github_token input for
github-push-action.

In @.github/workflows/markdownlint.yml:
- Line 31: Update the external references in .github/workflows/markdownlint.yml
lines 31-31 and .github/workflows/woke.yml lines 14-18: replace
docker://avtodev/markdown-lint:master with the specified sha256 digest and
replace the tox-lsr/main URL with commit
d594be24ed56e586a5796720d51c261e40c20496. Leave the already-pinned
lsr-woke-action reference unchanged.

In @.github/workflows/pr-title-lint.yml:
- Around line 21-23: Update the actions/checkout step to set persist-credentials
to false alongside fetch-depth, ensuring the downloaded Python code cannot reuse
the checkout token for authenticated Git operations.

In @.github/workflows/python-unit-test.yml:
- Line 46: Update the checkout steps in
.github/workflows/python-unit-test.yml:46-46 and
.github/workflows/qemu-kvm-integration-tests.yml:55-55 to include
with.persist-credentials set to false, disabling credential persistence in both
workflows.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: bdd824a6-be84-45e3-8044-37378c88c22f

📥 Commits

Reviewing files that changed from the base of the PR and between 88fd7be and 9cfc0e5.

📒 Files selected for processing (16)
  • .github/workflows/ansible-lint.yml
  • .github/workflows/ansible-managed-var-comment.yml
  • .github/workflows/ansible-test.yml
  • .github/workflows/build_docs.yml
  • .github/workflows/changelog_to_tag.yml
  • .github/workflows/codeql.yml
  • .github/workflows/codespell.yml
  • .github/workflows/markdownlint.yml
  • .github/workflows/pr-title-lint.yml
  • .github/workflows/python-unit-test.yml
  • .github/workflows/qemu-kvm-integration-tests.yml
  • .github/workflows/shellcheck.yml
  • .github/workflows/test_converting_readme.yml
  • .github/workflows/tft.yml
  • .github/workflows/weekly_ci.yml
  • .github/workflows/woke.yml


- name: Check out code
uses: actions/checkout@v7
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -eu
printf '%s\n' '--- workflow files ---'
for f in .github/workflows/build_docs.yml .github/workflows/changelog_to_tag.yml .github/workflows/test_converting_readme.yml; do
  printf '\n--- %s ---\n' "$f"
  sed -n '1,140p' "$f"
done
printf '\n--- checkout and credential-related usage ---\n'
rg -n -C 3 'actions/checkout|persist-credentials|git (ls-remote|fetch|push)|GITHUB_TOKEN|github.token|token:' .github/workflows

Repository: linux-system-roles/network

Length of output: 22448


🌐 Web query:

actions/checkout persist-credentials false official README token local git config

💡 Result:

In the actions/checkout GitHub Action, the persist-credentials input determines whether the authentication token (or SSH key) used to clone the repository is configured in the local git configuration [1][2]. When persist-credentials is set to true (the default), the action configures the authentication credentials into the local git environment [1][2]. This allows your workflow scripts to run authenticated git commands (such as git push or git pull) without manually providing the token or key again [1][2]. These credentials are automatically removed during the post-job cleanup phase [1][2]. When you set persist-credentials: false, the action will not configure these credentials in the local git configuration [1][2]. This is often used to prevent the runner from having persistent authenticated access to the repository, which can be a security consideration if you are running untrusted scripts or want to strictly limit the scope of the token's availability [3]. It is important to note that in newer versions of actions/checkout (starting with v6), the way credentials are persisted has been updated [4]. Instead of being written directly into the main.git/config file, credentials are now stored in a separate configuration file in the runner's temporary directory ($RUNNER_TEMP) and included using Git's includeIf mechanism [4]. This change improves security by isolating the credentials from the primary repository configuration [4]. If you set persist-credentials: false, your scripts will not have the repository's access token available via the local git configuration, meaning git commands requiring authentication will fail unless you provide alternative authentication methods [1][2].

Citations:


🌐 Web query:

ad-m github-push-action github_token authentication README official

💡 Result:

For the ad-m/github-push-action, authentication is primarily handled via the token input, which supports the default GITHUB_TOKEN or a personal access token (PAT) [1][2][3]. Key points regarding authentication and configuration: 1. Recommended Input: The preferred input name is token, which aligns with common conventions used by other actions like actions/checkout [3][4]. 2. Backward Compatibility: The github_token input remains supported as a non-breaking configuration option [1][2]. It defaults to ${{ github.token }} [3]. 3. Required Permissions: To push changes to a repository, the GITHUB_TOKEN must be configured with read and write permissions [1][3]. This is managed in the repository settings under Actions > General > Workflow permissions [1]. 4. Important Checkout Configuration: If you are using actions/checkout to prepare your workspace, you must set persist-credentials: false in the checkout step to prevent it from persisting the default credentials, which would otherwise override the token you intend to use for the push [5][3]. 5. Cross-Repository Pushing: If you need to push to a different repository, you must use a personal access token (PAT) or a GitHub App Token and pass it to the token input [1][5][6]. For further implementation details and examples, including how to set up GitHub App Tokens, you can refer to the official repository README [1][6].

Citations:


🏁 Script executed:

#!/bin/bash
set -eu
python3 - <<'PY'
from pathlib import Path
import re

files = [
    Path(".github/workflows/build_docs.yml"),
    Path(".github/workflows/changelog_to_tag.yml"),
    Path(".github/workflows/test_converting_readme.yml"),
]

for path in files:
    lines = path.read_text().splitlines()
    print(f"--- {path} ---")
    for i, line in enumerate(lines):
        if "uses: actions/checkout@" not in line:
            continue
        end = len(lines)
        for j in range(i + 1, len(lines)):
            if re.match(r"\s*-\s+", lines[j]):
                end = j
                break
        block = lines[i:end]
        persist = [x.strip() for x in block if "persist-credentials:" in x]
        later_git = [
            (k + 1, x.strip())
            for k, x in enumerate(lines[i:end], i)
            if re.search(r"\bgit\s+(ls-remote|fetch|push)\b", x)
        ]
        print(f"checkout line {i+1}: persist={persist or ['default']}; git commands in step block={later_git}")
PY

Repository: linux-system-roles/network

Length of output: 650


🏁 Script executed:

#!/bin/bash
set -eu
python3 - <<'PY'
from pathlib import Path
import re

files = [
    Path(".github/workflows/build_docs.yml"),
    Path(".github/workflows/changelog_to_tag.yml"),
    Path(".github/workflows/test_converting_readme.yml"),
]

step_start = re.compile(r"^\s{6}-\s+(?:name:|uses:)")
for path in files:
    lines = path.read_text().splitlines()
    steps = []
    start = None
    for index, line in enumerate(lines):
        if step_start.match(line):
            if start is not None:
                steps.append((start, index))
            start = index
    if start is not None:
        steps.append((start, len(lines)))

    print(f"--- {path} ---")
    for start, end in steps:
        block = lines[start:end]
        checkout = next((x for x in block if "uses: actions/checkout@" in x), None)
        if checkout:
            persist = next((x.strip() for x in block if "persist-credentials:" in x), "persist-credentials: default")
            print(f"checkout line {start + 1}: {persist}")
        commands = [
            f"{i + 1}: {line.strip()}"
            for i, line in enumerate(block)
            if re.search(r"\bgit\s+(ls-remote|fetch|push)\b", line)
        ]
        if commands:
            print("  credential-dependent Git commands:")
            print("  " + "\n  ".join(commands))
PY

Repository: linux-system-roles/network

Length of output: 736


Disable credential persistence on all five checkout steps.

Set persist-credentials: false for each listed checkout. Provide authentication explicitly for git ls-remote, git push, and git fetch --all --tags. The existing github_token input keeps the final github-push-action push authenticated.

📍 Affects 3 files
  • .github/workflows/build_docs.yml#L28-L28 (this comment)
  • .github/workflows/build_docs.yml#L51-L56
  • .github/workflows/changelog_to_tag.yml#L25-L25
  • .github/workflows/test_converting_readme.yml#L32-L32
🤖 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/build_docs.yml at line 28, Disable credential persistence
on all checkout steps: update .github/workflows/build_docs.yml lines 28-28 and
51-56, .github/workflows/changelog_to_tag.yml line 25, and
.github/workflows/test_converting_readme.yml line 32 with persist-credentials:
false. Add explicit authentication to the git ls-remote, git push, and git fetch
--all --tags commands, while preserving the existing github_token input for
github-push-action.

Source: Linters/SAST tools


- name: Check out code
uses: actions/checkout@v7
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -eu

for f in .github/workflows/markdownlint.yml .github/workflows/woke.yml; do
  echo "===== $f ====="
  wc -l "$f"
  cat -n "$f"
done

echo "===== reported references ====="
rg -n -C 3 --fixed-strings \
  -e 'docker://avtodev/markdown-lint:master' \
  -e 'tox-lsr/main' \
  -e 'actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1' \
  -e 'linux-system-roles/lsr-woke-action@c9aa70dcf848deb3e1bf4fdea085ce49bf02341b' \
  .github

Repository: linux-system-roles/network

Length of output: 12853


🏁 Script executed:

#!/bin/bash
set -u

echo "===== tox-lsr main branch ====="
curl -fsSL \
  -H 'Accept: application/vnd.github+json' \
  'https://api.github.com/repos/linux-system-roles/tox-lsr/commits/main' |
  jq -r '{sha: .sha, message: .commit.message}'

echo "===== lsr-woke-action pinned commit ====="
curl -fsSL \
  -H 'Accept: application/vnd.github+json' \
  'https://api.github.com/repos/linux-system-roles/lsr-woke-action/commits/c9aa70dcf848deb3e1bf4fdea085ce49bf02341b' |
  jq -r '{sha: .sha, message: .commit.message}'

echo "===== markdown-lint Docker Hub tag ====="
curl -fsSL \
  'https://hub.docker.com/v2/repositories/avtodev/markdown-lint/tags/master' |
  jq -r '{name: .name, digest: .digest, last_updated: .last_updated, images: [.images[]? | {digest: .digest, architecture: .architecture, os: .os}]}'

Repository: linux-system-roles/network

Length of output: 798


🏁 Script executed:

#!/bin/bash
set -eu

tox_sha='d594be24ed56e586a5796720d51c261e40c20496'
image_digest='sha256:040db72e30eae54299c0b8f87dfe24dadaf2d9a36c037408f5c9686cab0e105d'

grep -Fq 'uses: docker://avtodev/markdown-lint:master' .github/workflows/markdownlint.yml
grep -Fq 'https://raw.githubusercontent.com/linux-system-roles/tox-lsr/main/src/tox_lsr/config_files/woke.yml' .github/workflows/woke.yml

resolved_tox_sha="$(
  curl -fsSL \
    -H 'Accept: application/vnd.github+json' \
    "https://api.github.com/repos/linux-system-roles/tox-lsr/commits/$tox_sha" |
  jq -r '.sha'
)"
test "$resolved_tox_sha" = "$tox_sha"

manifest_headers="$(
  curl -fsSLI \
    -H 'Accept: application/vnd.docker.distribution.manifest.v2+json' \
    'https://registry-1.docker.io/v2/avtodev/markdown-lint/manifests/master'
)"
printf '%s\n' "$manifest_headers" | grep -Fiq "docker-content-digest: $image_digest"

printf '%s\n' "mutable workflow references present"
printf '%s\n' "tox-lsr main resolves to $resolved_tox_sha"
printf '%s\n' "markdown-lint master resolves to $image_digest"

Repository: linux-system-roles/network

Length of output: 215


🏁 Script executed:

#!/bin/bash
set -eu

repository='avtodev/markdown-lint'
expected='sha256:040db72e30eae54299c0b8f87dfe24dadaf2d9a36c037408f5c9686cab0e105d'

auth_header="$(
  curl -fsSL \
    'https://auth.docker.io/token?service=registry.docker.io&scope=repository:avtodev/markdown-lint:pull' |
  jq -r '.token' |
  sed 's/^/Authorization: Bearer /'
)"

headers="$(
  curl -fsSLI \
    -H "$auth_header" \
    -H 'Accept: application/vnd.docker.distribution.manifest.list.v2+json' \
    -H 'Accept: application/vnd.docker.distribution.manifest.v2+json' \
    "https://registry-1.docker.io/v2/$repository/manifests/master"
)"

printf '%s\n' "$headers"
printf '%s\n' "$headers" |
  tr -d '\r' |
  grep -Fiq "docker-content-digest: $expected"

printf '%s\n' "registry digest matches $expected"

Repository: linux-system-roles/network

Length of output: 880


Pin the remaining external references immutably.

  • Replace docker://avtodev/markdown-lint:master with docker://avtodev/markdown-lint@sha256:040db72e30eae54299c0b8f87dfe24dadaf2d9a36c037408f5c9686cab0e105d.
  • Replace the tox-lsr/main URL with the same path at commit d594be24ed56e586a5796720d51c261e40c20496.

The lsr-woke-action reference is already pinned to a commit SHA.

📍 Affects 2 files
  • .github/workflows/markdownlint.yml#L31-L31 (this comment)
  • .github/workflows/woke.yml#L14-L18
🤖 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/markdownlint.yml at line 31, Update the external
references in .github/workflows/markdownlint.yml lines 31-31 and
.github/workflows/woke.yml lines 14-18: replace
docker://avtodev/markdown-lint:master with the specified sha256 digest and
replace the tox-lsr/main URL with commit
d594be24ed56e586a5796720d51c261e40c20496. Leave the already-pinned
lsr-woke-action reference unchanged.

Comment on lines +21 to 23
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
with:
fetch-depth: 0

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -eu

printf '%s\n' '--- workflow ---'
cat -n .github/workflows/pr-title-lint.yml

printf '%s\n' '--- candidate files and references ---'
git ls-files | rg '(^|/)(pr_title_lint\.py|.*pr.*title.*lint.*|.*workflow.*)$' || true
rg -n --hidden --glob '!.git' 'pr_title_lint\.py|persist-credentials|git (fetch|push|pull|clone)|GITHUB_TOKEN|github\.token|actions/checkout' .github . 2>/dev/null | head -300

Repository: linux-system-roles/network

Length of output: 9071


🏁 Script executed:

#!/bin/bash
set -eu

python3 - <<'PY'
from pathlib import Path
import re

path = Path(".github/workflows/pr-title-lint.yml")
text = path.read_text()

checkout = re.search(
    r"uses:\s*actions/checkout@[^\n]+\n(?P<with>(?:\s+.*\n)*)",
    text,
)
with_block = checkout.group("with") if checkout else ""
has_persist_false = bool(re.search(r"^\s+persist-credentials:\s*false\s*$", with_block, re.M))
downloads_script = bool(re.search(r"curl\s+-o\s+pr_title_lint\.py\s+https://raw\.githubusercontent\.com/", text))
executes_script = bool(re.search(r"python3\s+pr_title_lint\.py\b", text))
has_authenticated_git = bool(re.search(r"^\s*(?:run:.*\b|-\s+run:.*\b)(?:git\s+(?:fetch|pull|push|clone)|gh\s+)", text, re.M))

print({
    "checkout_present": checkout is not None,
    "persist_credentials_false": has_persist_false,
    "downloads_external_python": downloads_script,
    "executes_downloaded_python": executes_script,
    "later_authenticated_git_command": has_authenticated_git,
    "contents_read_permission": bool(re.search(r"^\s+contents:\s*read\s*$", text, re.M)),
})
PY

Repository: linux-system-roles/network

Length of output: 374


🌐 Web query:

official actions/checkout README persist-credentials default local git config

💡 Result:

In the official actions/checkout GitHub Action, the persist-credentials input determines whether the authentication token or SSH key used to fetch the repository is configured within the local git environment [1][2]. Key details include: Default Behavior: The default value for persist-credentials is true [1][2]. When enabled, the action configures the provided token or SSH key in the local git environment, which allows subsequent steps in the same job to execute authenticated git commands (e.g., git push or git fetch) without additional configuration [1][2]. Security and Cleanup: The credentials are removed during the post-job cleanup process [1][2]. Recent updates have improved security by storing these credentials in a file under $RUNNER_TEMP instead of directly within the local.git/config file, requiring a minimum Actions Runner version of v2.329.0 for this approach [3][4]. Opting Out: To prevent the action from configuring credentials in the local git environment, you can explicitly set persist-credentials to false in your workflow step [1][5]: - uses: actions/checkout@v4 with: persist-credentials: false While there has been community discussion and proposals regarding changing this default to false for security reasons, it remains true by default in the current version [6][7].

Citations:


🏁 Script executed:

#!/bin/bash
set -eu

sha='3d3c42e5aac5ba805825da76410c181273ba90b1'
base="https://raw.githubusercontent.com/actions/checkout/${sha}"

printf '%s\n' '--- pinned action.yml ---'
curl -fsSL "$base/action.yml" | sed -n '1,180p'

printf '%s\n' '--- pinned package/source references ---'
curl -fsSL "$base/action.yml" | rg -n 'runs:|main:|persist-credentials|version' || true
curl -fsSL "https://api.github.com/repos/actions/checkout/commits/${sha}" |
  jq -r '{sha: .sha, message: .commit.message, date: .commit.committer.date}'

Repository: linux-system-roles/network

Length of output: 5912


🌐 Web query:

actions/checkout 3d3c42e5aac5ba805825da76410c181273ba90b1 persist-credentials RUNNER_TEMP

💡 Result:

The commit SHA 3d3c42e5aac5ba805825da76410c181273ba90b1 corresponds to a version of the actions/checkout GitHub Action, specifically associated with the v7.0.1 release [1][2][3]. Regarding the technical context of persist-credentials and RUNNER_TEMP: 1. Refactored Credential Management: Starting with version v6.0.0, actions/checkout changed how it handles the persist-credentials input [4][5][6]. Previously, credentials were stored directly in the repository's .git/config file [4][5]. 2. Move to RUNNER_TEMP: The action now persists Git credentials in a separate configuration file located in the directory specified by the RUNNER_TEMP environment variable [4][5]. 3. Mechanism: This change improves security by isolating credentials from the repository's main Git configuration [4][7]. The action utilizes Git's includeIf.gitdir mechanism to conditionally include these credentials only when operating within the relevant Git directory [4][7]. 4. Requirements: Because this approach relies on specific file structures and Git features, it requires an Actions Runner version of v2.329.0 or higher to function correctly, particularly in scenarios involving Docker container actions [4][5][6]. The input persist-credentials remains a boolean option (defaulting to true) that controls whether the action configures the authentication token or SSH key into the local Git configuration [8][6]. When enabled, this allows subsequent steps in your workflow to run authenticated Git commands [6].

Citations:


Disable checkout credential persistence.

Because the job executes downloaded Python code, set persist-credentials: false. The default is true, so the script can use the checkout token for authenticated Git operations. No later step requires this access.

Proposed fix
       - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
         with:
           fetch-depth: 0
+          persist-credentials: false
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
with:
fetch-depth: 0
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1
with:
fetch-depth: 0
persist-credentials: false
🧰 Tools
🪛 zizmor (1.29.0)

[warning] 21-23: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false

(artipacked)

🤖 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/pr-title-lint.yml around lines 21 - 23, Update the
actions/checkout step to set persist-credentials to false alongside fetch-depth,
ensuring the downloaded Python code cannot reuse the checkout token for
authenticated Git operations.

Source: Linters/SAST tools


- name: checkout PR
uses: actions/checkout@v7
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -eu
printf '%s\n' '--- workflow excerpts ---'
for f in .github/workflows/python-unit-test.yml .github/workflows/qemu-kvm-integration-tests.yml; do
  echo "### $f"
  nl -ba "$f" | sed -n '1,140p'
done
printf '%s\n' '--- checkout usage and workflow permissions ---'
rg -n -C 3 'actions/checkout|persist-credentials|permissions:|run:' .github/workflows/python-unit-test.yml .github/workflows/qemu-kvm-integration-tests.yml

Repository: linux-system-roles/network

Length of output: 10972


🏁 Script executed:

#!/bin/bash
set -eu
for f in .github/workflows/python-unit-test.yml .github/workflows/qemu-kvm-integration-tests.yml; do
  echo "### $f"
  wc -l "$f"
  sed -n '1,240p' "$f"
done

Repository: linux-system-roles/network

Length of output: 13217


🌐 Web query:

site:github.com/actions/checkout README persist-credentials false default

💡 Result:

The default value for the persist-credentials input in the actions/checkout GitHub Action is currently true [1][2][3]. When set to true (the default), the action configures the authentication token or SSH key in the local git configuration, which allows subsequent steps in the same job to execute authenticated git commands [1][3][4]. The credentials are removed during the post-job cleanup [1][2][4]. If you wish to prevent the action from persisting these credentials, you must explicitly set persist-credentials to false in your workflow file [1][2][5]. This opt-out is recommended to reduce the risk of exposing the GITHUB_TOKEN or other credentials to subsequent steps in a workflow [6][5]. Note that there have been ongoing discussions and pull requests within the actions/checkout repository aimed at changing this default to false to improve security [6][5].

Citations:


🏁 Script executed:

#!/bin/bash
set -eu
python3 - <<'PY'
from pathlib import Path
import re

files = [
    Path(".github/workflows/python-unit-test.yml"),
    Path(".github/workflows/qemu-kvm-integration-tests.yml"),
]

for path in files:
    text = path.read_text()
    checkout = re.search(
        r"(?m)^\s*-\s+name:.*\n\s+uses:\s+actions/checkout@[^ \n]+(?P<rest>.*(?:\n\s+.+)*)?",
        text,
    )
    # Use line-oriented parsing to avoid treating unrelated `with:` blocks as
    # checkout inputs.
    lines = text.splitlines()
    checkout_index = next(
        i for i, line in enumerate(lines)
        if re.match(r"\s*uses:\s*actions/checkout@", line)
    )
    next_step = next(
        (i for i in range(checkout_index + 1, len(lines))
         if re.match(r"\s*-\s+name:", lines[i])),
        len(lines),
    )
    checkout_block = lines[checkout_index:next_step]
    later_run = [
        (i + 1, line.strip())
        for i, line in enumerate(lines[next_step:], start=next_step)
        if re.match(r"\s*run:", line)
    ]
    permissions = []
    in_permissions = False
    for line in lines:
        if re.match(r"^permissions:\s*$", line):
            in_permissions = True
            continue
        if in_permissions and line and not line[0].isspace():
            in_permissions = False
        if in_permissions and re.match(r"^\s+[A-Za-z-]+:\s+\S+", line):
            permissions.append(line.strip())

    print(path)
    print("  checkout:", lines[checkout_index].strip())
    print("  checkout block:", [line.strip() for line in checkout_block])
    print("  persist-credentials present:",
          any("persist-credentials:" in line for line in checkout_block))
    print("  later run steps:", len(later_run), later_run[:5])
    print("  permissions:", permissions)
PY

Repository: linux-system-roles/network

Length of output: 921


Disable checkout credential persistence in both workflows. Add with: persist-credentials: false to both checkout steps. Subsequent repository commands otherwise can access the persisted GITHUB_TOKEN; the integration workflow also grants statuses: write.

  • .github/workflows/python-unit-test.yml#L46
  • .github/workflows/qemu-kvm-integration-tests.yml#L55
🧰 Tools
🪛 zizmor (1.29.0)

[warning] 45-46: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false

(artipacked)

📍 Affects 2 files
  • .github/workflows/python-unit-test.yml#L46-L46 (this comment)
  • .github/workflows/qemu-kvm-integration-tests.yml#L55-L55
🤖 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/python-unit-test.yml at line 46, Update the checkout steps
in .github/workflows/python-unit-test.yml:46-46 and
.github/workflows/qemu-kvm-integration-tests.yml:55-55 to include
with.persist-credentials set to false, disabling credential persistence in both
workflows.

Source: Linters/SAST tools

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant