Skip to content

fix(ci): harden GitHub Actions workflows (#14789) - #14790

Closed
hf-security-analysis[bot] wants to merge 0 commit into
dependabot/github_actions/actions-ca6b5df6adfrom
security/workflow-hardening/pr-14789
Closed

hf-security-analysis[bot] wants to merge 0 commit into
dependabot/github_actions/actions-ca6b5df6adfrom
security/workflow-hardening/pr-14789

Conversation

@hf-security-analysis

@hf-security-analysis hf-security-analysis Bot commented Sep 16, 2026

Copy link
Copy Markdown
Contributor

Automated hardening of the workflow files flagged on #14789.

Warning

This changes when the workflow runs and what it can reach. Both triggers and permissions were rewritten in .github/workflows/claude_review.yml, .github/workflows/issue_labeler.yml, .github/workflows/pr_labeler.yml. Read the diff before merging — either one can change what the workflow is able to do.

Targets dependabot/github_actions/actions-ca6b5df6ad. Files changed:

  • .github/workflows/benchmark.yml
  • .github/workflows/bot_pytest.yml
  • .github/workflows/claude_review.yml
  • .github/workflows/issue_labeler.yml
  • .github/workflows/mirror_community_pipeline.yml
  • .github/workflows/notify_slack_about_release.yml
  • .github/workflows/pr_dependency_test.yml
  • .github/workflows/pr_labeler.yml
  • .github/workflows/pr_modular_tests.yml
  • .github/workflows/pr_test_fetcher.yml
  • .github/workflows/pr_tests.yml
  • .github/workflows/pr_tests_gpu.yml
  • .github/workflows/pr_torch_dependency_test.yml
  • .github/workflows/push_tests.yml
  • .github/workflows/push_tests_fast.yml
  • .github/workflows/push_tests_mps.yml
  • .github/workflows/pypi_publish.yaml
  • .github/workflows/release_tests_fast.yml
  • .github/workflows/ssh-runner.yml
  • .github/workflows/stale.yml
  • .github/workflows/trufflehog.yml
  • .github/workflows/update_metadata.yml
  • .github/workflows/upload_pr_documentation.yml

Fixed by this PR:

  • HIGH broken_auth_gate (claude) — .github/workflows/bot_pytest.yml
  • HIGH unpinned-action (pinact) — .github/workflows/bot_pytest.yml:78
  • HIGH unpinned-action (pinact) — .github/workflows/bot_pytest.yml:119
  • HIGH excessive-permissions (zizmor) — .github/workflows/claude_review.yml:10
  • HIGH excessive-permissions (zizmor) — .github/workflows/claude_review.yml:11
  • HIGH unpinned-action (pinact) — .github/workflows/claude_review.yml:37
  • HIGH llm_prompt_injection (claude) — .github/workflows/issue_labeler.yml
  • HIGH excessive-permissions (zizmor) — .github/workflows/issue_labeler.yml:9
  • HIGH unpinned-action (pinact) — .github/workflows/mirror_community_pipeline.yml:72
  • HIGH unpinned-action (pinact) — .github/workflows/mirror_community_pipeline.yml:78
  • HIGH unpinned-action (pinact) — .github/workflows/notify_slack_about_release.yml:16
  • HIGH unpinned-action (pinact) — .github/workflows/notify_slack_about_release.yml:19
  • HIGH unpinned-action (pinact) — .github/workflows/pr_dependency_test.yml:25
  • HIGH unpinned-action (pinact) — .github/workflows/pr_dependency_test.yml:27
  • HIGH dangerous-triggers (zizmor) — .github/workflows/pr_labeler.yml:3
  • HIGH excessive-permissions (zizmor) — .github/workflows/pr_labeler.yml:9
  • HIGH unpinned-action (pinact) — .github/workflows/pr_modular_tests.yml:47
  • HIGH unpinned-action (pinact) — .github/workflows/pr_modular_tests.yml:49
  • HIGH unpinned-action (pinact) — .github/workflows/pr_modular_tests.yml:67
  • HIGH unpinned-action (pinact) — .github/workflows/pr_modular_tests.yml:69
  • HIGH unpinned-action (pinact) — .github/workflows/pr_modular_tests.yml:95
  • HIGH unpinned-action (pinact) — .github/workflows/pr_modular_tests.yml:125
  • HIGH unpinned-action (pinact) — .github/workflows/pr_modular_tests.yml:154
  • HIGH unpinned-action (pinact) — .github/workflows/pr_test_fetcher.yml:34
  • HIGH unpinned-action (pinact) — .github/workflows/pr_test_fetcher.yml:48
  • HIGH unpinned-action (pinact) — .github/workflows/pr_test_fetcher.yml:89
  • HIGH unpinned-action (pinact) — .github/workflows/pr_test_fetcher.yml:115
  • HIGH unpinned-action (pinact) — .github/workflows/pr_test_fetcher.yml:144
  • HIGH unpinned-action (pinact) — .github/workflows/pr_test_fetcher.yml:170
  • HIGH unpinned-action (pinact) — .github/workflows/pr_tests.yml:42
  • HIGH unpinned-action (pinact) — .github/workflows/pr_tests.yml:44
  • HIGH unpinned-action (pinact) — .github/workflows/pr_tests.yml:62
  • HIGH unpinned-action (pinact) — .github/workflows/pr_tests.yml:64
  • HIGH unpinned-action (pinact) — .github/workflows/pr_tests.yml:122
  • HIGH unpinned-action (pinact) — .github/workflows/pr_tests.yml:166
  • HIGH unpinned-action (pinact) — .github/workflows/pr_tests.yml:198
  • HIGH unpinned-action (pinact) — .github/workflows/pr_tests.yml:226
  • HIGH unpinned-action (pinact) — .github/workflows/pr_tests.yml:252
  • HIGH unpinned-action (pinact) — .github/workflows/pr_tests.yml:293
  • HIGH unpinned-action (pinact) — .github/workflows/pr_tests_gpu.yml:43
  • HIGH unpinned-action (pinact) — .github/workflows/pr_tests_gpu.yml:45
  • HIGH unpinned-action (pinact) — .github/workflows/pr_tests_gpu.yml:63
  • HIGH unpinned-action (pinact) — .github/workflows/pr_tests_gpu.yml:65
  • HIGH unpinned-action (pinact) — .github/workflows/pr_tests_gpu.yml:95
  • HIGH unpinned-action (pinact) — .github/workflows/pr_tests_gpu.yml:113
  • HIGH unpinned-action (pinact) — .github/workflows/pr_tests_gpu.yml:133
  • HIGH unpinned-action (pinact) — .github/workflows/pr_tests_gpu.yml:182
  • HIGH unpinned-action (pinact) — .github/workflows/pr_tests_gpu.yml:205
  • HIGH unpinned-action (pinact) — .github/workflows/pr_tests_gpu.yml:251
  • HIGH unpinned-action (pinact) — .github/workflows/pr_tests_gpu.yml:267
  • HIGH unpinned-action (pinact) — .github/workflows/pr_tests_gpu.yml:299
  • HIGH unpinned-action (pinact) — .github/workflows/pr_torch_dependency_test.yml:25
  • HIGH unpinned-action (pinact) — .github/workflows/pr_torch_dependency_test.yml:27
  • HIGH unpinned-action (pinact) — .github/workflows/push_tests.yml:40
  • HIGH unpinned-action (pinact) — .github/workflows/push_tests.yml:58
  • HIGH unpinned-action (pinact) — .github/workflows/push_tests.yml:78
  • HIGH unpinned-action (pinact) — .github/workflows/push_tests.yml:109
  • HIGH unpinned-action (pinact) — .github/workflows/push_tests.yml:131
  • HIGH unpinned-action (pinact) — .github/workflows/push_tests.yml:166
  • HIGH unpinned-action (pinact) — .github/workflows/push_tests.yml:183
  • HIGH unpinned-action (pinact) — .github/workflows/push_tests.yml:209
  • HIGH unpinned-action (pinact) — .github/workflows/push_tests.yml:226
  • HIGH unpinned-action (pinact) — .github/workflows/push_tests.yml:251
  • HIGH unpinned-action (pinact) — .github/workflows/push_tests.yml:267
  • HIGH unpinned-action (pinact) — .github/workflows/push_tests.yml:298
  • HIGH unpinned-action (pinact) — .github/workflows/push_tests_fast.yml:63
  • HIGH unpinned-action (pinact) — .github/workflows/push_tests_fast.yml:96
  • HIGH unpinned-action (pinact) — .github/workflows/push_tests_mps.yml:29
  • HIGH unpinned-action (pinact) — .github/workflows/push_tests_mps.yml:71
  • HIGH unpinned-action (pinact) — .github/workflows/pypi_publish.yaml:75
  • HIGH unpinned-action (pinact) — .github/workflows/release_tests_fast.yml:39
  • HIGH unpinned-action (pinact) — .github/workflows/release_tests_fast.yml:58
  • HIGH unpinned-action (pinact) — .github/workflows/release_tests_fast.yml:78
  • HIGH unpinned-action (pinact) — .github/workflows/release_tests_fast.yml:109
  • HIGH unpinned-action (pinact) — .github/workflows/release_tests_fast.yml:131
  • HIGH unpinned-action (pinact) — .github/workflows/release_tests_fast.yml:166
  • HIGH unpinned-action (pinact) — .github/workflows/release_tests_fast.yml:183
  • HIGH unpinned-action (pinact) — .github/workflows/release_tests_fast.yml:223
  • HIGH unpinned-action (pinact) — .github/workflows/release_tests_fast.yml:240
  • HIGH unpinned-action (pinact) — .github/workflows/release_tests_fast.yml:266
  • HIGH unpinned-action (pinact) — .github/workflows/release_tests_fast.yml:283
  • HIGH unpinned-action (pinact) — .github/workflows/release_tests_fast.yml:309
  • HIGH unpinned-action (pinact) — .github/workflows/release_tests_fast.yml:326
  • HIGH unpinned-action (pinact) — .github/workflows/release_tests_fast.yml:359
  • HIGH unpinned-action (pinact) — .github/workflows/ssh-runner.yml:41
  • HIGH unpinned-action (pinact) — .github/workflows/stale.yml:18
  • HIGH unpinned-action (pinact) — .github/workflows/stale.yml:21
  • HIGH unpinned-action (pinact) — .github/workflows/trufflehog.yml:18
  • HIGH unpinned-action (pinact) — .github/workflows/update_metadata.yml:21

Reported on the pull request but not fixed here — each needs a decision this bot should not make for you:

  • HIGH unpinned-images (zizmor) — .github/workflows/benchmark.yml:30
  • HIGH unpinned-images (zizmor) — .github/workflows/bot_pytest.yml:57
  • HIGH unpinned-images (zizmor) — .github/workflows/nightly_tests.yml:34
  • HIGH unpinned-images (zizmor) — .github/workflows/nightly_tests.yml:71
  • HIGH unpinned-images (zizmor) — .github/workflows/nightly_tests.yml:117
  • HIGH unpinned-images (zizmor) — .github/workflows/nightly_tests.yml:189
  • HIGH unpinned-images (zizmor) — .github/workflows/nightly_tests.yml:233
  • HIGH unpinned-images (zizmor) — .github/workflows/nightly_tests.yml:282
  • HIGH unpinned-images (zizmor) — .github/workflows/nightly_tests.yml:356
  • HIGH unpinned-images (zizmor) — .github/workflows/nightly_tests.yml:412
  • HIGH unpinned-images (zizmor) — .github/workflows/nightly_tests.yml:470
  • HIGH unpinned-images (zizmor) — .github/workflows/pr_modular_tests.yml:93
  • HIGH unpinned-images (zizmor) — .github/workflows/pr_modular_tests.yml:116
  • HIGH unpinned-images (zizmor) — .github/workflows/pr_test_fetcher.yml:24
  • HIGH unpinned-images (zizmor) — .github/workflows/pr_test_fetcher.yml:82
  • HIGH unpinned-images (zizmor) — .github/workflows/pr_test_fetcher.yml:135
  • HIGH unpinned-images (zizmor) — .github/workflows/pr_tests.yml:110
  • HIGH unpinned-images (zizmor) — .github/workflows/pr_tests.yml:189
  • HIGH unpinned-images (zizmor) — .github/workflows/pr_tests.yml:240
  • HIGH unpinned-images (zizmor) — .github/workflows/pr_tests_gpu.yml:90
  • HIGH unpinned-images (zizmor) — .github/workflows/pr_tests_gpu.yml:129
  • HIGH unpinned-images (zizmor) — .github/workflows/pr_tests_gpu.yml:193
  • HIGH unpinned-images (zizmor) — .github/workflows/pr_tests_gpu.yml:263
  • HIGH unpinned-images (zizmor) — .github/workflows/push_tests.yml:35
  • HIGH unpinned-images (zizmor) — .github/workflows/push_tests.yml:74
  • HIGH unpinned-images (zizmor) — .github/workflows/push_tests.yml:119
  • HIGH unpinned-images (zizmor) — .github/workflows/push_tests.yml:178
  • HIGH unpinned-images (zizmor) — .github/workflows/push_tests.yml:221
  • HIGH unpinned-images (zizmor) — .github/workflows/push_tests.yml:263
  • HIGH unpinned-images (zizmor) — .github/workflows/push_tests_fast.yml:51
  • HIGH unpinned-images (zizmor) — .github/workflows/release_tests_fast.yml:34
  • HIGH unpinned-images (zizmor) — .github/workflows/release_tests_fast.yml:74
  • HIGH unpinned-images (zizmor) — .github/workflows/release_tests_fast.yml:119
  • HIGH unpinned-images (zizmor) — .github/workflows/release_tests_fast.yml:176
  • HIGH unpinned-images (zizmor) — .github/workflows/release_tests_fast.yml:235
  • HIGH unpinned-images (zizmor) — .github/workflows/release_tests_fast.yml:278
  • HIGH unpinned-images (zizmor) — .github/workflows/release_tests_fast.yml:321
  • HIGH unpinnable-reference (pinact) — .github/workflows/ssh-runner.yml:50
  • HIGH dangerous-triggers (zizmor) — .github/workflows/upload_pr_documentation.yml:3
  • MEDIUM excessive-permissions (zizmor) — .github/workflows/bot_pytest.yml:1
  • MEDIUM excessive-permissions (zizmor) — .github/workflows/codeql.yml:2
  • MEDIUM excessive-permissions (zizmor) — .github/workflows/pr_link_issue_reminder.yml:1
  • MEDIUM excessive-permissions (zizmor) — .github/workflows/stale.yml:1

Permissions

.github/workflows/bot_pytest.yml

job granted why
gate issues: write, pull-requests: write The two gh api -X POST calls hit literal endpoints repos/{repo}/issues/comments/{id}/reactions and repos/{repo}/issues/{pr}/comments, i.e. it writes a reaction and a comment on the PR; pull-requests: write covers PR comments, and issues: write is included because the reaction/comment calls go through the issues-comments API surface — a reviewer may drop issues if reactions succeed with only pull-requests: write.
gpu contents: read Only actions/checkout of the PR head needs the token (contents: read); the dependency installs, pytest run and actions/upload-artifact (same-run upload) need no token scopes.
report issues: write, pull-requests: write The final step calls gh api -X PATCH repos/{repo}/issues/comments/{id} to edit the bot's PR comment, which needs comment write access — pull-requests: write for a PR conversation comment, with issues: write included since the call uses the issues-comments endpoint.

.github/workflows/claude_review.yml

job granted why
claude-review contents: write, issues: read, pull-requests: write The final "Open follow-up PR with Claude's changes" step pushes branches (git push origin) and runs gh pr create/gh pr comment, requiring contents: write and pull-requests: write; gh pr view only needs read, and issues: read covers the claude-code-action reading the triggering issue comment context — that third-party action (anthropics/claude-code-action) is the step I was least able to verify, and some setups also add id-token: write for it, which I omitted since the workflow's own declared permissions do not include it.

.github/workflows/codeql.yml

codeql was left as it is — This job only delegates via uses: to the external reusable workflow huggingface/security-workflows/.github/workflows/codeql-reusable.yml, whose steps are not in this file, so its real token needs cannot be verified here; the permissions it already declares (security-events: write for SARIF upload, plus actions/contents/packages read) match a standard CodeQL analysis and look reasonable, but a reviewer should confirm against the pinned reusable workflow.

.github/workflows/issue_labeler.yml

job granted why
label contents: read, issues: write actions/checkout needs contents: read, and the "Apply labels" step runs gh issue edit --add-label with GITHUB_TOKEN, which requires issues: write; the "Get labels from LLM" step runs utils/label_issues.py, which is not in this file, but it is only given HF_TOKEN and no GitHub token, so it should need no scope — worth a glance during review.

.github/workflows/pr_labeler.yml

job granted why
label contents: read, pull-requests: write actions/labeler reads the .github/labeler.yml config from the repo (contents: read) and adds/removes labels on the PR with sync-labels enabled (pull-requests: write).
missing-tests contents: read, issues: read, pull-requests: write actions/checkout needs contents: read, the gh api repos/.../pulls/N/files call needs pull-request read, the gh api repos/.../issues/N/labels read is an issues endpoint (issues: read), and gh pr edit --add-label/--remove-label requires pull-requests: write; the piped utils/check_test_missing.py only consumes stdin and needs no token.
fixes-issue issues: read, pull-requests: write The GraphQL query reads pull request closingIssuesReferences (pull-request/issues read) and gh pr edit --add-label/--remove-label drives pull-requests: write; the gh api repos/.../issues/N/labels read accounts for issues: read.
size-label issues: read, pull-requests: write Reads PR additions/deletions and current labels via gh api (pull-request read plus issues: read for the issues/labels endpoint) and applies size labels with gh pr edit --add-label/--remove-label, which needs pull-requests: write.

.github/workflows/pr_link_issue_reminder.yml

remind was left as it is — The actions/checkout step needs contents: read, and the final step runs utils/remind_link_issue.py, a script not visible in this file — its documented behaviour (commenting on PRs, adding/checking the no-issue-needed label, and auto-closing PRs) implies pull-requests: write, plus issues: write since PR comments/labels go through the issues API; reviewers should confirm against the script, as that step is the unverifiable one (the Slack webhook secret needs no scope).

.github/workflows/stale.yml

job granted why
close_stale_issues contents: read, issues: write, pull-requests: write actions/checkout needs contents: read, and the final step runs python utils/stale.py with GITHUB_TOKEN via PyGithub to comment on/close stale issues and pull requests, so issues: write and pull-requests: write are required; the script body is not in this file, so a reviewer should confirm utils/stale.py does not also touch other APIs (e.g. labels on other resources or repo contents).

Anything not listed above keeps the permissions it had. To measure a job this could not read, add GitHubSecurityLab/actions-permissions/monitor to it and run the workflow — it reports the minimum the run actually used.

Pinning changes come from pinact and are mechanical. Any other change was generated by Claude — read it before merging.

@github-actions github-actions Bot added size/L PR with diff > 200 LOC CI labels Sep 16, 2026
@dependabot
dependabot Bot force-pushed the dependabot/github_actions/actions-ca6b5df6ad branch from f642106 to d822ec1 Compare September 16, 2026 15:06
@hf-security-analysis
hf-security-analysis Bot force-pushed the security/workflow-hardening/pr-14789 branch from 9eda11d to d822ec1 Compare September 16, 2026 15:13
@github-actions github-actions Bot added size/S PR with diff < 50 LOC and removed size/L PR with diff > 200 LOC labels Sep 16, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CI size/S PR with diff < 50 LOC

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants