Skip to content

ci: fix cache poisoning and least-privilege permissions#2392

Merged
SkrubbySkrubInAShrub merged 1 commit into
community-shaders:devfrom
SkrubbySkrubInAShrub:alandtse-lift-1
May 22, 2026
Merged

ci: fix cache poisoning and least-privilege permissions#2392
SkrubbySkrubInAShrub merged 1 commit into
community-shaders:devfrom
SkrubbySkrubInAShrub:alandtse-lift-1

Conversation

@SkrubbySkrubInAShrub
Copy link
Copy Markdown
Collaborator

@SkrubbySkrubInAShrub SkrubbySkrubInAShrub commented May 21, 2026

ci: harden cache trust and workflow permissions

Split actions/cache into trusted (restore+save) and untrusted
(restore-only) paths so PR builds cannot poison caches consumed
by default-branch runs. Addresses the CodeQL
actions/cache-poisoning/poisonable-step finding.

Add explicit least-privilege permissions blocks to three
workflows that previously inherited default write-all scope:

  • maint-todo-issues: issues:write, contents:read
  • nexus-upload: contents:read
  • pr-wip: statuses:write, pull-requests:read

Addresses CodeQL actions/missing-workflow-permissions findings.

Lifted from alandtse fork

Summary by CodeRabbit

  • Chores
    • Enhanced build security by implementing least-privilege permissions model across GitHub Actions workflows
    • Introduced event-based cache handling strategy for improved build infrastructure reliability
    • Updated CI/CD environment configuration for optimized access controls and event trust handling

Review Change Stack

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 21, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 39236089-147b-4928-b35b-c147ca2b0418

📥 Commits

Reviewing files that changed from the base of the PR and between 5180a1e and d4996f3.

📒 Files selected for processing (4)
  • .github/actions/setup-build-environment/action.yaml
  • .github/workflows/maint-todo-issues.yaml
  • .github/workflows/nexus-upload.yaml
  • .github/workflows/pr-wip.yaml

📝 Walkthrough

Walkthrough

This PR hardens GitHub Actions CI/CD security by implementing event-aware cache handling for the build environment and applying least-privilege permissions across multiple workflows. The setup-build-environment action now prevents cache poisoning by gating cache writes to trusted events while providing read-only restore for untrusted PR events.

Changes

GitHub CI/CD Security Hardening

Layer / File(s) Summary
Build cache event-aware strategy
.github/actions/setup-build-environment/action.yaml
The composite action now gates actions/cache@v5 write operations to non-PR events and introduces a new actions/cache/restore@v5 step for pull_request and pull_request_target events, preventing cache poisoning by allowing restore-only access with documented strategy rationale.
GitHub Actions least-privilege permissions
.github/workflows/maint-todo-issues.yaml, .github/workflows/nexus-upload.yaml, .github/workflows/pr-wip.yaml
Three workflows now declare explicit top-level permissions blocks restricting token scopes to the minimum required: issue creation (issues: write), repository reads (contents: read), and PR metadata access (pull-requests: read, statuses: write).

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Possibly related PRs

Suggested reviewers

  • jiayev
  • doodlum
  • davo0411

Poem

🐰 A cache most secure, yet swift to restore,
Trust levels divide what happened before,
Permissions now scoped to their purpose precise,
No poisoned builds now—the strategy is nice!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main changes: fixing cache poisoning vulnerabilities and implementing least-privilege permissions across CI workflows.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 actionlint (1.7.12)
.github/workflows/nexus-upload.yaml

could not read ".github/workflows/nexus-upload.yaml": open .github/workflows/nexus-upload.yaml: no such file or directory

.github/workflows/pr-wip.yaml

could not read ".github/workflows/pr-wip.yaml": open .github/workflows/pr-wip.yaml: no such file or directory

.github/workflows/maint-todo-issues.yaml

could not read ".github/workflows/maint-todo-issues.yaml": open .github/workflows/maint-todo-issues.yaml: no such file or directory

🔧 YAMLlint (1.38.0)
.github/actions/setup-build-environment/action.yaml

[Errno 2] No such file or directory: '.github/actions/setup-build-environment/action.yaml'

.github/workflows/pr-wip.yaml

[Errno 2] No such file or directory: '.github/workflows/pr-wip.yaml'

.github/workflows/maint-todo-issues.yaml

[Errno 2] No such file or directory: '.github/workflows/maint-todo-issues.yaml'

  • 1 others
🔧 zizmor (1.25.2)
.github/workflows/maint-todo-issues.yaml

INFO zizmor: 🌈 zizmor v1.25.2
fatal: no audit was performed
invalid input: .github/workflows/maint-todo-issues.yaml

.github/workflows/nexus-upload.yaml

INFO zizmor: 🌈 zizmor v1.25.2
fatal: no audit was performed
invalid input: .github/workflows/nexus-upload.yaml

.github/workflows/pr-wip.yaml

INFO zizmor: 🌈 zizmor v1.25.2
fatal: no audit was performed
invalid input: .github/workflows/pr-wip.yaml

🔧 Checkov (3.2.529)
.github/workflows/nexus-upload.yaml

2026-05-21 10:33:19,814 [MainThread ] [ERROR] Template file not found: .github/workflows/nexus-upload.yaml
2026-05-21 10:33:19,824 [MainThread ] [ERROR] Template file not found: .github/workflows/nexus-upload.yaml
2026-05-21 10:33:19,862 [MainThread ] [ERROR] Template file not found: .github/workflows/nexus-upload.yaml
2026-05-21 10:33:19,866 [MainThread ] [ERROR] Failed to invoke function /usr/local/lib/python3.11/dist-packages/checkov/common/runners/object_runner. with .github/workflows/nexus-upload.yaml
Traceback (most recent call last):
File "/usr/local/lib/python3.11/dist-packages/checkov/common/parallelizer/parallel_runner.py", line 88, in func_wrapper
result = original_func(item)
^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.11/dist-packages/checkov/common/runners/object_runner.py", line 74, in
results = parallel_runner.run_function(lambda f: (f, self._parse_file(f)), files_to_load)

... [truncated 9182 characters] ...

or directory: '.github/workflows/nexus-upload.yaml'
2026-05-21 10:33:20,016 [MainThread ] [ERROR] Exception traceback:
Traceback (most recent call last):
File "/usr/local/lib/python3.11/dist-packages/checkov/main.py", line 647, in run
self.scan_reports = runner_registry.run(
^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.11/dist-packages/checkov/common/runners/runner_registry.py", line 177, in run
for result in parallel_runner_results:
File "/usr/local/lib/python3.11/dist-packages/checkov/common/parallelizer/parallel_runner.py", line 118, in _run_function_multiprocess_fork
raise v.internal_exception.with_traceback(v.internal_exception.traceback)
FileNotFoundError: [Errno 2] No such file or directory: '.github/workflows/nexus-upload.yaml'

.github/workflows/pr-wip.yaml

2026-05-21 10:33:19,835 [MainThread ] [ERROR] Template file not found: .github/workflows/pr-wip.yaml
2026-05-21 10:33:19,849 [MainThread ] [ERROR] Template file not found: .github/workflows/pr-wip.yaml
2026-05-21 10:33:19,931 [MainThread ] [ERROR] Template file not found: .github/workflows/pr-wip.yaml
2026-05-21 10:33:19,919 [MainThread ] [ERROR] Failed to invoke function /usr/local/lib/python3.11/dist-packages/checkov/common/runners/object_runner. with .github/workflows/pr-wip.yaml
Traceback (most recent call last):
File "/usr/local/lib/python3.11/dist-packages/checkov/common/parallelizer/parallel_runner.py", line 88, in func_wrapper
result = original_func(item)
^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.11/dist-packages/checkov/common/runners/object_runner.py", line 74, in
results = parallel_runner.run_function(lambda f: (f, self._parse_file(f)), files_to_load)
^^^^^^^^^^^

... [truncated 9080 characters] ...

ning: could not process file .github/workflows/pr-wip.yaml
2026-05-21 10:33:20,026 [MainThread ] [ERROR] Exception traceback:
Traceback (most recent call last):
File "/usr/local/lib/python3.11/dist-packages/checkov/main.py", line 647, in run
self.scan_reports = runner_registry.run(
^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.11/dist-packages/checkov/common/runners/runner_registry.py", line 177, in run
for result in parallel_runner_results:
File "/usr/local/lib/python3.11/dist-packages/checkov/common/parallelizer/parallel_runner.py", line 118, in _run_function_multiprocess_fork
raise v.internal_exception.with_traceback(v.internal_exception.traceback)
FileNotFoundError: [Errno 2] No such file or directory: '.github/workflows/pr-wip.yaml'

.github/workflows/maint-todo-issues.yaml

2026-05-21 10:33:19,811 [MainThread ] [ERROR] Template file not found: .github/workflows/maint-todo-issues.yaml
2026-05-21 10:33:19,814 [MainThread ] [ERROR] Template file not found: .github/workflows/maint-todo-issues.yaml
2026-05-21 10:33:19,877 [MainThread ] [ERROR] Template file not found: .github/workflows/maint-todo-issues.yaml
2026-05-21 10:33:19,936 [MainThread ] [ERROR] Failed to invoke function /usr/local/lib/python3.11/dist-packages/checkov/common/runners/object_runner. with .github/workflows/maint-todo-issues.yaml
Traceback (most recent call last):
File "/usr/local/lib/python3.11/dist-packages/checkov/common/parallelizer/parallel_runner.py", line 88, in func_wrapper
result = original_func(item)
^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.11/dist-packages/checkov/common/runners/object_runner.py", line 74, in
results = parallel_runner.run_function(lambda f: (f, self._parse_file(f)), files_to_load)

... [truncated 9267 characters] ...

ory: '.github/workflows/maint-todo-issues.yaml'
2026-05-21 10:33:20,012 [MainThread ] [ERROR] Exception traceback:
Traceback (most recent call last):
File "/usr/local/lib/python3.11/dist-packages/checkov/main.py", line 647, in run
self.scan_reports = runner_registry.run(
^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.11/dist-packages/checkov/common/runners/runner_registry.py", line 177, in run
for result in parallel_runner_results:
File "/usr/local/lib/python3.11/dist-packages/checkov/common/parallelizer/parallel_runner.py", line 118, in _run_function_multiprocess_fork
raise v.internal_exception.with_traceback(v.internal_exception.traceback)
FileNotFoundError: [Errno 2] No such file or directory: '.github/workflows/maint-todo-issues.yaml'

  • 1 others

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 and usage tips.

@doodlum
Copy link
Copy Markdown
Collaborator

doodlum commented May 21, 2026

@coderabbitai explain this PR and say if there's any potential issues with it, or if its all good changes and no more work is required

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 21, 2026

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Fetch the PR diff and relevant file contents
gh pr view 2392 --repo community-shaders/skyrim-community-shaders --json files,body,title

echo "=== DIFF ==="
gh pr diff 2392 --repo community-shaders/skyrim-community-shaders

echo "=== action.yaml ==="
cat .github/actions/setup-build-environment/action.yaml

echo "=== maint-todo-issues.yaml ==="
cat .github/workflows/maint-todo-issues.yaml

echo "=== nexus-upload.yaml ==="
cat .github/workflows/nexus-upload.yaml

echo "=== pr-wip.yaml ==="
cat .github/workflows/pr-wip.yaml

Length of output: 7391


@doodlum — here's a breakdown of what this PR does and my assessment of whether it's safe to merge.


What the PR Does

1. Cache Poisoning Fix (setup-build-environment/action.yaml)

This is the most impactful change. Previously, a single actions/cache@v5 step was used unconditionally, meaning a malicious PR could write a poisoned cache entry that a later trusted (default-branch) run would restore and execute. The fix splits the step in two, gated by github.event_name:

flowchart TD
    A[CI Job Starts] --> B{Event type?}
    B -->|push / workflow_dispatch / workflow_call| C["actions/cache@v5\n(restore + save)\nTrusted path"]
    B -->|pull_request / pull_request_target| D["actions/cache/restore@v5\n(restore-only)\nUntrusted path"]
    C --> E[PR builds can read trusted cache]
    D --> E
    style C fill:rgba(0,200,0,0.5)
    style D fill:rgba(255,165,0,0.5)
Loading
  • Trusted events (push, workflow_dispatch, etc.): full cache restore + save ✅
  • Untrusted PRs: restore-only — they can read from caches warmed by trusted runs, but cannot write back to poison them ✅
  • Both steps use the exact same key and restore-keys, so cache hits work correctly across the trust boundary ✅

2. Least-Privilege Permissions (3 workflow files)

All three workflows previously inherited GitHub's default write-all scope. The new permissions are:

Workflow Permissions Added Reason
maint-todo-issues.yaml contents: read, issues: write alstr/todo-to-issue-action needs to create issues from TODO comments
nexus-upload.yaml contents: read Only reads releases via gh api / gh release download; writes only to Nexus via secrets
pr-wip.yaml statuses: write, pull-requests: read wip/action@v1 reads PR title/labels and sets a commit status (uses the Statuses API, not Checks API)

All three permission sets are correctly scoped — nothing is over- or under-granted.


Assessment

This PR is clean — no issues found. Specifically:

  • ✅ The cache if: conditions are correct (pull_request and pull_request_target both excluded from writes)
  • actions/cache/restore@v5 is the correct sub-action for restore-only behavior
  • ✅ Composite actions inherit github.event_name from the caller workflow, so the conditions work as intended
  • ✅ All three permission blocks are minimal and match each workflow's actual access needs
  • ✅ The changes are additive and don't alter any existing workflow logic

This directly addresses two CodeQL security findings (actions/cache-poisoning/poisonable-step and actions/missing-workflow-permissions) and follows well-established GitHub Actions hardening best practices. Safe to merge. 🐇

@SkrubbySkrubInAShrub SkrubbySkrubInAShrub merged commit 377da5f into community-shaders:dev May 22, 2026
5 checks passed
IgorAlanAlbuquerque pushed a commit to IgorAlanAlbuquerque/skyrim-community-shaders that referenced this pull request May 29, 2026
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.

3 participants