ci: harden workflow token permissions and pin actions by SHA by myasnikovdaniil · Pull Request #2849 · cozystack/cozystack · GitHub
Skip to content

ci: harden workflow token permissions and pin actions by SHA#2849

Merged
Aleksei Sviridkin (lexfrei) merged 1 commit into
mainfrom
ci/scorecard-token-perms-pin
Jun 24, 2026
Merged

ci: harden workflow token permissions and pin actions by SHA#2849
Aleksei Sviridkin (lexfrei) merged 1 commit into
mainfrom
ci/scorecard-token-perms-pin

Conversation

@myasnikovdaniil

@myasnikovdaniil myasnikovdaniil commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

What this PR does

Hardens GitHub Actions security posture to raise two OpenSSF Scorecard checks that currently score 0/10, as part of the push to ≥7.5 ahead of CNCF Incubation due diligence. No workflow triggers, job graphs, or step logic change — only GITHUB_TOKEN scopes and action pin refs.

Token-Permissions (was 0/10)

The check failed because several workflows (codegen-drift.yml, pre-commit.yml, retest.yaml, tags.yaml, …) declared no permissions: block at all, so their jobs inherited the repository default token — typically read/write across all scopes.

This PR applies the OpenSSF-recommended pattern to every workflow:

  • A top-level read-only default: permissions: { contents: read }.
  • An explicit per-job permissions: block granting only the writes that job needs. Workflows that previously held write scopes at the workflow level (backport, pr-labeler, pr-size, stale) now hold them at job level, so the read-only default applies everywhere else.

Per-job scopes were derived from what each job actually calls:

Workflow Job Scopes granted
auto-release auto-release contents: write, pull-requests: read (unchanged)
backport prepare contents: read
backport backport contents: write, pull-requests: write
codegen-drift codegen-drift contents: read
labels validate contents: read
labels sync contents: read, issues: write, pull-requests: write
pre-commit pre-commit contents: read
pr-labeler label pull-requests: write
pr-size size pull-requests: write
retest retest actions: write, pull-requests: read (unchanged)
stale stale issues: write, pull-requests: write
update-releasenotes update-releasenotes contents: write (unchanged)
pull-requests detect-changes contents: read, pull-requests: read
pull-requests build contents: read, packages: write (unchanged)
pull-requests resolve_assets contents: read
pull-requests e2e contents: read, packages: read, checks: write (unchanged)
pull-requests-release finalize contents: write (unchanged)
tags prepare-release contents/packages/pull-requests/actions: write (unchanged)
tags generate-changelog contents: write, pull-requests: write (unchanged)
tags update-website-docs contents: read (unchanged)

Self-hosted release jobs that push tags/branches/releases authenticate via the cozystack-ci GitHub App token, so reducing the workflow GITHUB_TOKEN does not affect them; the job-level write scopes they already declared are preserved verbatim.

Pinned-Dependencies (was 0/10)

Every GitHub-owned and third-party action is now pinned to a full commit SHA with a human-readable version comment, e.g. actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4. scorecard.yml was already SHA-pinned in #2721; breakpoint-action was already pinned. SHAs were resolved from each tag's current commit at authoring time, so behavior is identical to the previously-referenced tag.

Scope note: this PR pins GitHub Actions only. Dockerfile FROM digest pinning (≈40 images) is deliberately out of scope here — it touches image builds and is higher blast-radius; tracked separately.

Verification

  • actionlint over all workflows: exit 0.
  • All 14 workflow files parse as valid YAML.
  • Diff is mechanical: only permissions: blocks and action @refs changed.

Release note

ci: declare least-privilege per-job GITHUB_TOKEN permissions and pin all GitHub Actions by commit SHA across CI workflows (OpenSSF Scorecard hardening)

Summary by CodeRabbit

  • Chores
    • Tightened CI/CD security with least-privilege workflow token permissions: workflows now default to read-only access (contents: read), with select jobs requesting write or additional scopes only when needed.
    • Improved supply-chain safety and consistency by pinning GitHub Actions (and related third-party actions) to specific commit SHAs across multiple workflows, reducing reliance on floating version tags.

@gemini-code-assist

Copy link
Copy Markdown
Contributor

Note

Gemini is unable to generate a summary for this pull request due to the file types involved not being currently supported.

@github-actions github-actions Bot added area/uncategorized PR auto-labeler could not map title scope to a known area/*; please review size/L This PR changes 100-499 lines, ignoring generated files labels Jun 8, 2026
@coderabbitai

coderabbitai Bot commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: fc6a8cd5-8734-4ec7-a433-2b75f399b96d

📥 Commits

Reviewing files that changed from the base of the PR and between 710fe65 and 76ad921.

📒 Files selected for processing (13)
  • .github/workflows/auto-release.yaml
  • .github/workflows/backport.yaml
  • .github/workflows/codegen-drift.yml
  • .github/workflows/labels.yaml
  • .github/workflows/pr-labeler.yaml
  • .github/workflows/pr-size.yaml
  • .github/workflows/pre-commit.yml
  • .github/workflows/pull-requests-release.yaml
  • .github/workflows/pull-requests.yaml
  • .github/workflows/retest.yaml
  • .github/workflows/stale.yaml
  • .github/workflows/tags.yaml
  • .github/workflows/update-releasenotes.yaml
🚧 Files skipped from review as they are similar to previous changes (5)
  • .github/workflows/retest.yaml
  • .github/workflows/update-releasenotes.yaml
  • .github/workflows/stale.yaml
  • .github/workflows/pr-labeler.yaml
  • .github/workflows/backport.yaml

📝 Walkthrough

Walkthrough

This PR standardizes GitHub Actions security across workflows by defaulting workflow permissions to contents: read and pinning third-party action uses: references to specific commit SHAs; job-level permissions are added where write access is required.

Changes

GitHub Actions Security Hardening

Layer / File(s) Summary
Core utility workflow security hardening
.github/workflows/auto-release.yaml, .github/workflows/backport.yaml, .github/workflows/codegen-drift.yml, .github/workflows/labels.yaml, .github/workflows/pr-labeler.yaml, .github/workflows/pr-size.yaml, .github/workflows/pre-commit.yml, .github/workflows/retest.yaml, .github/workflows/stale.yaml, .github/workflows/update-releasenotes.yaml
Adds top-level permissions: contents: read defaults and pins action references (e.g., actions/create-github-app-token, actions/checkout, actions/github-script, actions/setup-go, actions/setup-python, korthout/backport-action, EndBug/label-sync, actions/stale) to specific commit SHAs across all utility workflows; job-level permissions explicitly grant write scopes where needed (e.g., pull-requests: write for pr-labeler and pr-size, issues: write and pull-requests: write for stale).
Pull request workflow permissions and action pinning
.github/workflows/pull-requests.yaml
Adds top-level permissions: contents: read, sets explicit job-level permissions for detect-changes (including pull-requests: read) and resolve_assets (contents: read), and pins actions used for path filtering (dorny/paths-filter), checkout, Docker login, artifact upload/download, app-token generation, and actions/github-script to commit SHAs across build, e2e, and asset resolution jobs.
Release workflow permissions and action pinning
.github/workflows/pull-requests-release.yaml, .github/workflows/tags.yaml
Adds workflow-level permissions: contents: read defaults and pins actions across prepare-release, generate-changelog, and update-website-docs jobs (actions/create-github-app-token, actions/github-script, actions/checkout, docker/login-action, actions/setup-node) to specific commit SHAs; jobs that publish or write continue to request explicit write permissions (contents: write, pull-requests: write).

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Suggested labels

security, area/ci, size/M

Suggested reviewers

  • kvaps
  • lllamnyp
  • lexfrei
  • androndo
  • IvanHunters
  • sircthulhu

Poem

🐰 Hops the rabbit, pinning each SHA,
Tokens trimmed to read-only play,
Jobs that must write ask just in time,
Supply chain steady, actions locked in rhyme,
A tiny hop for CI, security sublime.

🚥 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 accurately summarizes the primary changes: hardening workflow token permissions and pinning GitHub Actions by SHA, which are the main objectives of the PR.
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
  • Commit unit tests in branch ci/scorecard-token-perms-pin

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
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (2)
.github/workflows/pull-requests.yaml (1)

128-133: 💤 Low value

Consider adding persist-credentials: false as a follow-up hardening measure.

Zizmor flags that actions/checkout persists credentials by default, which could leak tokens if subsequent steps upload the .git directory to artifacts. This is a defense-in-depth measure that complements the permissions work in this PR. Since it's out of the PR's stated scope (permissions and pinning only), this can be tracked separately.

🛡️ Optional hardening for credential persistence
       - name: Checkout code
         if: contains(github.event.pull_request.labels.*.name, 'release')
         uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
         with:
           fetch-depth: 0
           fetch-tags: true
+          persist-credentials: false
🤖 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/pull-requests.yaml around lines 128 - 133, The checkout
step named "Checkout code" currently uses
actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 and should explicitly
disable credential persistence to avoid leaking tokens; update the step (the
"Checkout code" step that calls actions/checkout) to include
persist-credentials: false in its with: block so credentials are not left in the
checked-out repository.

Source: Linters/SAST tools

.github/workflows/tags.yaml (1)

307-336: 💤 Low value

Consider disabling default caching in actions/setup-node to mitigate cache-poisoning risk.

Zizmor flags that actions/setup-node enables caching by default, which can be exploited in cache-poisoning attacks on shared runners. Since this workflow runs on self-hosted runners and the cache is only used for the short-lived Copilot CLI installation, the risk is low. However, explicitly disabling caching would satisfy the OpenSSF Scorecard check and eliminate the warning.

🛡️ Optional fix to disable default caching
       - name: Setup Node.js
         if: steps.check_changelog.outputs.exists == 'false'
         uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
         with:
           node-version: 22
+          cache: ''
🤖 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/tags.yaml around lines 307 - 336, The actions/setup-node
step named "Setup Node.js" currently uses actions/setup-node without explicitly
disabling its built-in caching; add a explicit with entry to turn off caching
(e.g., add a with: cache: 'false' under the actions/setup-node usage) so the
step does not enable dependency/tool caching and avoids cache-poisoning warnings
from Scorecard.

Source: Linters/SAST tools

🤖 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/auto-release.yaml:
- Around line 29-33: The GitHub App token is being minted with
actions/create-github-app-token (uses: actions/create-github-app-token@...) and
owner: cozystack but no repository restriction, so add the repositories input
and set it to the current repo (use the github.repository expression) to scope
the installation token to this repository only; update the action invocation
that includes app-id and private-key to also include repositories: ${{
github.repository }}.

In @.github/workflows/labels.yaml:
- Line 83: The checkout step in the sync job is missing
persist-credentials:false which risks leaking write-scoped GitHub token; update
the actions/checkout step (the one using
actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5) in the sync job to
include persist-credentials: false so the write-scoped credentials are not
persisted to the checked-out repository workspace.

---

Nitpick comments:
In @.github/workflows/pull-requests.yaml:
- Around line 128-133: The checkout step named "Checkout code" currently uses
actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 and should explicitly
disable credential persistence to avoid leaking tokens; update the step (the
"Checkout code" step that calls actions/checkout) to include
persist-credentials: false in its with: block so credentials are not left in the
checked-out repository.

In @.github/workflows/tags.yaml:
- Around line 307-336: The actions/setup-node step named "Setup Node.js"
currently uses actions/setup-node without explicitly disabling its built-in
caching; add a explicit with entry to turn off caching (e.g., add a with: cache:
'false' under the actions/setup-node usage) so the step does not enable
dependency/tool caching and avoids cache-poisoning warnings from Scorecard.
🪄 Autofix (Beta)

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

Run ID: b77f9431-4e71-4759-834d-fbf7a29b4216

📥 Commits

Reviewing files that changed from the base of the PR and between 80fbfa1 and eac7973.

📒 Files selected for processing (13)
  • .github/workflows/auto-release.yaml
  • .github/workflows/backport.yaml
  • .github/workflows/codegen-drift.yml
  • .github/workflows/labels.yaml
  • .github/workflows/pr-labeler.yaml
  • .github/workflows/pr-size.yaml
  • .github/workflows/pre-commit.yml
  • .github/workflows/pull-requests-release.yaml
  • .github/workflows/pull-requests.yaml
  • .github/workflows/retest.yaml
  • .github/workflows/stale.yaml
  • .github/workflows/tags.yaml
  • .github/workflows/update-releasenotes.yaml

Comment on lines +29 to 33
uses: actions/create-github-app-token@d72941d797fd3113feb6b93fd0dec494b13a2547 # v1
with:
app-id: ${{ secrets.COZYSTACK_CI_APP_ID }}
private-key: ${{ secrets.COZYSTACK_CI_PRIVATE_KEY }}
owner: cozystack

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate and print the relevant workflow section with line numbers
ls -la .github/workflows/auto-release.yaml
sed -n '1,120p' .github/workflows/auto-release.yaml | nl -ba

Repository: cozystack/cozystack

Length of output: 209


🌐 Web query:

actions/create-github-app-token repositories input scope behavior

💡 Result:

If you’re using the actions/create-github-app-token GitHub Action, the token’s repository access (“scope”) depends on whether you set the owner and repositories inputs: 1) Neither owner nor repositories set - The token is scoped to only the current repository. [1] 2) owner set, repositories empty - The token is scoped to all repositories in the provided owner’s GitHub App installation. [1] 3) owner not set, repositories set - The token is scoped to the specified repositories under the current repository owner. [1] 4) owner set, repositories set - The token is scoped to the specified repositories under the provided owner. [1] Additionally: - The repositories input accepts a comma-separated or newline-separated list of repository names. [1][2] - The token inherits all permissions granted to the GitHub App installation (you can still reduce permissions via the action’s permission-* inputs, but by default it mirrors the installation’s permissions). [2] - Under the hood, it creates a GitHub App installation access token via the POST /app/installations/{installation_id}/access_tokens endpoint; the REST API documentation notes you can specify individual repositories with repositories/repository_ids, and if you don’t specify repositories, the installation access token has access to all repositories the installation was granted. [3] Sources: [1][2][3]

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

sed -n '1,120p' .github/workflows/auto-release.yaml | cat -n

Repository: cozystack/cozystack

Length of output: 6248


🏁 Script executed:

#!/bin/bash
set -euo pipefail

sed -n '1,120p' .github/workflows/auto-release.yaml | cat -n

Repository: cozystack/cozystack

Length of output: 6248


🌐 Web query:

actions/create-github-app-token input repositories format github.event.repository.name

💡 Result:

The repositories input for the actions/create-github-app-token action accepts a list of repository names provided as a comma-separated or newline-separated string [1][2]. Regarding the format and use of github.event.repository.name (or similar expressions like github.repository): 1. Format Options: You can provide repository names as bare names (e.g., repo1) or as full owner/repository names (e.g., owner/repo1) [3][1]. 2. Owner Consistency: If you use the full owner/repository format, the owner portion must match the owner input (if provided) or the current repository owner (if the owner input is unset) [1][2]. 3. Using github.repository: It is valid to include the current repository using the context variable ${{ github.repository }} within your list [1][2]. The action is designed to normalize these inputs by parsing them and stripping the owner portion where necessary for token scoping, while still validating that the owner matches the expected target [3]. Example usage with a newline-separated list: repositories: | ${{ github.repository }} another-repo The action automatically handles trimming and parsing the entries, making it robust for both comma-separated and multi-line YAML formats [4].

Citations:


Scope the GitHub App token to the current repository

With owner: cozystack but no repositories input, actions/create-github-app-token can mint an installation token covering all repositories granted to the GitHub App installation under that owner. Constrain it to the current repo.

Suggested patch
       - name: Generate GitHub App token
         id: app-token
         uses: actions/create-github-app-token@d72941d797fd3113feb6b93fd0dec494b13a2547 # v1
         with:
           app-id: ${{ secrets.COZYSTACK_CI_APP_ID }}
           private-key: ${{ secrets.COZYSTACK_CI_PRIVATE_KEY }}
           owner: cozystack
+          repositories: ${{ github.event.repository.name }}
📝 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/create-github-app-token@d72941d797fd3113feb6b93fd0dec494b13a2547 # v1
with:
app-id: ${{ secrets.COZYSTACK_CI_APP_ID }}
private-key: ${{ secrets.COZYSTACK_CI_PRIVATE_KEY }}
owner: cozystack
uses: actions/create-github-app-token@d72941d797fd3113feb6b93fd0dec494b13a2547 # v1
with:
app-id: ${{ secrets.COZYSTACK_CI_APP_ID }}
private-key: ${{ secrets.COZYSTACK_CI_PRIVATE_KEY }}
owner: cozystack
repositories: ${{ github.event.repository.name }}
🧰 Tools
🪛 zizmor (1.25.2)

[error] 33-33: dangerous use of GitHub App tokens (github-app): token granted access to all repositories for this owner's app installation

(github-app)


[error] 29-29: dangerous use of GitHub App tokens (github-app): app token inherits blanket installation permissions

(github-app)

🤖 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/auto-release.yaml around lines 29 - 33, The GitHub App
token is being minted with actions/create-github-app-token (uses:
actions/create-github-app-token@...) and owner: cozystack but no repository
restriction, so add the repositories input and set it to the current repo (use
the github.repository expression) to scope the installation token to this
repository only; update the action invocation that includes app-id and
private-key to also include repositories: ${{ github.repository }}.

Source: Linters/SAST tools

steps:
- uses: actions/checkout@v4
- uses: EndBug/label-sync@v2
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE=".github/workflows/labels.yaml"
echo "== File = $FILE =="
ls -la "$FILE" || true

echo
echo "== Lines around 70-95 =="
nl -ba "$FILE" | sed -n '70,100p'

echo
echo "== Find all checkout steps in this workflow =="
rg -n "uses:\s*actions/checkout@" "$FILE" || true

echo
echo "== Show surrounding block for each checkout step =="
rg -n "uses:\s*actions/checkout@" "$FILE" | cut -d: -f1 | while read -r ln; do
  start=$((ln-8))
  end=$((ln+12))
  echo
  echo "--- checkout context at line $ln ($start-$end) ---"
  nl -ba "$FILE" | sed -n "${start},${end}p"
done

echo
echo "== Check sync job permissions and checkout persist-credentials settings =="
python3 - <<'PY'
import re, pathlib
p = pathlib.Path(".github/workflows/labels.yaml")
txt = p.read_text()
# Very lightweight parse: find "sync:" job block region by indentation
lines = txt.splitlines()
# locate "sync:" line
for i,l in enumerate(lines, start=1):
    if re.match(r'^\s*sync:\s*$', l):
        start=i
        # block ends when indentation <= that of 'sync:' (2 spaces typically for jobs: children)
        indent=len(l)-len(l.lstrip(' '))
        j=i
        while j < len(lines):
            nl=len(lines[j])-len(lines[j].lstrip(' '))
            if nl<=indent and re.match(r'^\s*\w', lines[j]) and not re.match(r'^\s*sync:\s*$', lines[j]):
                break
            j+=1
        block="\n".join(lines[start-1:j])
        print("== sync job block (approx lines %d-%d) =="%(start,j))
        print(block)
        break
else:
    print("sync job not found by exact 'sync:' match")
PY

Repository: cozystack/cozystack

Length of output: 273


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE=".github/workflows/labels.yaml"
python3 - <<'PY'
import pathlib, re
p = pathlib.Path(".github/workflows/labels.yaml")
lines = p.read_text().splitlines()

def show(start, end):
    for i in range(start, min(end, len(lines)) + 1):
        print(f"{i:>4}: {lines[i-1]}")

print("== Lines 75-90 ==")
show(75, 90)

print("\n== All checkout steps ==");
pat = re.compile(r"^\s*-\s*uses:\s*actions/checkout@([0-9a-fA-F]{40})")
checkout_lines = [i for i,l in enumerate(lines, start=1) if pat.search(l)]
print("checkout line numbers:", checkout_lines)

for ln in checkout_lines:
    start = max(ln-8, 1)
    end = min(ln+12, len(lines))
    print(f"\n--- checkout context at line {ln} (SHA={pat.search(lines[ln-1]).group(1)}) ---")
    show(start, end)

print("\n== Attempt to extract `sync` job block & its permissions/checkout config ==");
# Simple indentation-based extraction:
sync_start = None
for i,l in enumerate(lines, start=1):
    if re.match(r"^\s*sync:\s*$", l):
        sync_start = i
        sync_indent = len(l) - len(l.lstrip(' '))
        break

if sync_start is None:
    print("No `sync:` job found.")
    raise SystemExit(0)

# Find end of block: first line with indentation <= sync_indent and starting a new top-level key
j = sync_start
while j <= len(lines):
    l = lines[j-1]
    indent = len(l) - len(l.lstrip(' '))
    if j > sync_start and indent <= sync_indent and re.match(r"^\s*\w", l):
        break
    j += 1

block = lines[sync_start-1:j-1]
print(f"sync job approx lines {sync_start}-{j-1}")

# Extract permissions section lines within sync block
perm_block = []
in_perm = False
perm_indent = None
for idx, l in enumerate(block, start=sync_start):
    if re.match(r"^\s*permissions:\s*$", l):
        in_perm = True
        perm_indent = len(l) - len(l.lstrip(' '))
        perm_block.append((idx, l))
        continue
    if in_perm:
        indent = len(l) - len(l.lstrip(' '))
        if indent <= perm_indent and re.match(r"^\s*\w", l):
            in_perm = False
            continue
        perm_block.append((idx,l))

if perm_block:
    print("\n-- sync permissions block --")
    for idx,l in perm_block:
        print(f"{idx:>4}: {l}")
else:
    print("\nNo `sync` permissions block found.")

# Find checkout inside sync block
sync_checkout = []
for idx in range(sync_start, j):
    l = lines[idx-1]
    if re.search(r"uses:\s*actions/checkout@", l):
        sync_checkout.append(idx)

print("\ncheckout steps inside sync job (line numbers):", sync_checkout)
for ln in sync_checkout:
    start=max(ln-6,1); end=min(ln+14,len(lines))
    print(f"\n--- sync checkout context at line {ln} ---")
    show(start,end)

# Check persist-credentials anywhere near checkout lines
for ln in sync_checkout:
    window = lines[max(ln-2,1)-1:min(ln+8,len(lines))]
    txt = "\n".join(window)
    if "persist-credentials:" in txt:
        print(f"persist-credentials present near line {ln}")
        # show exact line(s)
        for k in range(max(ln-2,1), min(ln+8,len(lines))+1):
            if "persist-credentials" in lines[k-1]:
                print(f"  {k:>4}: {lines[k-1]}")
    else:
        print(f"persist-credentials NOT present near line {ln}")
PY

Repository: cozystack/cozystack

Length of output: 2793


Disable credential persistence in the write-scoped sync job actions/checkout.

In .github/workflows/labels.yaml, the sync job grants issues: write and pull-requests: write but the actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 step at line 83 does not set persist-credentials: false.

Suggested patch
-      - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
+      - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
+        with:
+          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@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
with:
persist-credentials: false
🧰 Tools
🪛 zizmor (1.25.2)

[warning] 83-83: 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/labels.yaml at line 83, The checkout step in the sync job
is missing persist-credentials:false which risks leaking write-scoped GitHub
token; update the actions/checkout step (the one using
actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5) in the sync job to
include persist-credentials: false so the write-scoped credentials are not
persisted to the checked-out repository workspace.

Source: Linters/SAST tools

@myasnikovdaniil myasnikovdaniil self-assigned this Jun 9, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM — adds explicit minimal permissions: blocks and SHA-pins every action across the workflows, which is exactly what the targeted Scorecard Token-Permissions and Pinned-Dependencies checks score; no job graph or step logic changes.

Business context: raise the OpenSSF Scorecard Token-Permissions and Pinned-Dependencies checks (both 0/10) toward the ≥7.5 target by scoping GITHUB_TOKEN and pinning actions by commit SHA.

Verified: a top-level permissions: contents: read is added with per-job write scopes retained where needed (e.g. the labels.yaml sync job keeps issues: write + pull-requests: write), and actions are pinned to full commit SHAs with version comments. PR-triggered workflows stay green, so the tightened scopes do not break them; the release/tag/backport workflows only run on their own events, so it is worth confirming those once on the next release.

Non-blocking follow-ups

  1. auto-release.yaml mints a GitHub App token with owner: cozystack and no repositories: input (pre-existing, not introduced here), so the token is scoped to every repository in the org installation rather than just this one. Constraining it with repositories: ${{ github.event.repository.name }} would close the over-scope while this file is already being hardened.
  2. No changed workflow sets persist-credentials: false on actions/checkout, including the write-scoped sync job (issues/pull-requests: write); that leaves the token in .git/config for later steps, which is the Dangerous-Workflow surface. It is orthogonal to the two checks this PR targets, but adding it would complete the hardening.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
.github/workflows/pull-requests-release.yaml (1)

30-36: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Scope the GitHub App token to the current repository.

The finalize job mints a GitHub App token with owner: cozystack but no repositories input. Add repositories: ${{ github.event.repository.name }} to restrict the token to this repository only.

Suggested patch
       - name: Generate GitHub App token
         id: app-token
         uses: actions/create-github-app-token@d72941d797fd3113feb6b93fd0dec494b13a2547 # v1
         with:
           app-id: ${{ secrets.COZYSTACK_CI_APP_ID }}
           private-key: ${{ secrets.COZYSTACK_CI_PRIVATE_KEY }}
           owner: cozystack
+          repositories: ${{ github.event.repository.name }}
🤖 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/pull-requests-release.yaml around lines 30 - 36, The
GitHub App token step (id: app-token) currently mints a token with owner:
cozystack but no repository scope; update the step that uses
actions/create-github-app-token (the app-token step in the finalize job) to
include a repositories input set to ${{ github.event.repository.name }} so the
generated token is limited to the current repository only.

Source: Linters/SAST tools

🧹 Nitpick comments (2)
.github/workflows/tags.yaml (2)

336-338: 💤 Low value

Optional: Disable implicit caching on setup-node if strict security posture is desired.

Static analysis flags that actions/setup-node enables caching by default when a lockfile is present, which could theoretically be exploited in cache poisoning scenarios. Given this runs on self-hosted runners and the job has continue-on-error: true, the practical risk is low.

To explicitly disable caching if desired:

uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
  node-version: 22
  cache: ''
🤖 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/tags.yaml around lines 336 - 338, The workflow uses
actions/setup-node@... with node-version: 22 and currently relies on implicit
caching; to disable implicit lockfile-based caching set the cache input to an
empty string in the setup invocation (update the uses: actions/setup-node step
where node-version: 22 is specified to include cache: ''), which will explicitly
turn off setup-node's automatic caching behavior on that job.

Source: Linters/SAST tools


34-38: 💤 Low value

Informational: GitHub App token permissions are app-installation scoped.

The static analysis tool flags that app tokens inherit "blanket installation permissions." This is expected behavior—the token's capabilities are bounded by the GitHub App's installation configuration, not this workflow. The workflow correctly demonstrates least-privilege awareness by minting a separate read-only token at line 275 for the AI changelog step where write access isn't needed.

Reducing the app installation's permissions (if broader than necessary) would be a separate infrastructure change outside this PR's scope.

🤖 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/tags.yaml around lines 34 - 38, Add a short inline comment
above the actions/create-github-app-token step to state that GitHub App tokens
are scoped to the App installation (permissions come from the App installation
configuration) and that a separate least-privilege read-only token is minted
later for steps that don’t need write access; reference the existing keys
(app-id, private-key, owner) so reviewers understand this is intentional and not
a workflow misconfiguration.

Source: Linters/SAST tools

🤖 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/pre-commit.yml:
- Line 22: Update the actions/checkout pin in the pre-commit workflow so it
matches the repo standard: replace the current uses entry that references
actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 (v3) with the v4 SHA
used elsewhere (34e114876b0b11c390a56381ad16ebd13914f8d5) in the uses:
actions/checkout@... line, or alternatively add a short comment explaining why
this workflow must remain on v3 if you intentionally keep it; ensure the single
uses line is updated consistently.

In @.github/workflows/pull-requests.yaml:
- Around line 189-196: The "Generate GitHub App token" step (id: app-token,
uses: actions/create-github-app-token@...) currently issues a token scoped to
the app installation (all repos); restrict it to the current repo by adding the
repositories input: set repositories: ${{ github.event.repository.name }} under
the step's with: block so the generated token is limited to the current pull
request repository.
- Around line 120-126: The Generate GitHub App token step (id: app-token, uses:
actions/create-github-app-token@...) mints a token for owner: cozystack but
doesn’t restrict repositories; update that step to include the repositories
input and pass the current repo (e.g., repositories: ${{ github.repository }})
so the token is scoped to the current repository only.

---

Outside diff comments:
In @.github/workflows/pull-requests-release.yaml:
- Around line 30-36: The GitHub App token step (id: app-token) currently mints a
token with owner: cozystack but no repository scope; update the step that uses
actions/create-github-app-token (the app-token step in the finalize job) to
include a repositories input set to ${{ github.event.repository.name }} so the
generated token is limited to the current repository only.

---

Nitpick comments:
In @.github/workflows/tags.yaml:
- Around line 336-338: The workflow uses actions/setup-node@... with
node-version: 22 and currently relies on implicit caching; to disable implicit
lockfile-based caching set the cache input to an empty string in the setup
invocation (update the uses: actions/setup-node step where node-version: 22 is
specified to include cache: ''), which will explicitly turn off setup-node's
automatic caching behavior on that job.
- Around line 34-38: Add a short inline comment above the
actions/create-github-app-token step to state that GitHub App tokens are scoped
to the App installation (permissions come from the App installation
configuration) and that a separate least-privilege read-only token is minted
later for steps that don’t need write access; reference the existing keys
(app-id, private-key, owner) so reviewers understand this is intentional and not
a workflow misconfiguration.
🪄 Autofix (Beta)

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

Run ID: 019b05a6-5cb4-4e2f-92d0-2f910bb8864c

📥 Commits

Reviewing files that changed from the base of the PR and between eac7973 and 4feac5b.

📒 Files selected for processing (13)
  • .github/workflows/auto-release.yaml
  • .github/workflows/backport.yaml
  • .github/workflows/codegen-drift.yml
  • .github/workflows/labels.yaml
  • .github/workflows/pr-labeler.yaml
  • .github/workflows/pr-size.yaml
  • .github/workflows/pre-commit.yml
  • .github/workflows/pull-requests-release.yaml
  • .github/workflows/pull-requests.yaml
  • .github/workflows/retest.yaml
  • .github/workflows/stale.yaml
  • .github/workflows/tags.yaml
  • .github/workflows/update-releasenotes.yaml
🚧 Files skipped from review as they are similar to previous changes (4)
  • .github/workflows/update-releasenotes.yaml
  • .github/workflows/pr-labeler.yaml
  • .github/workflows/stale.yaml
  • .github/workflows/backport.yaml

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Caution

Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
.github/workflows/pull-requests-release.yaml (1)

30-36: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Scope the GitHub App token to the current repository.

The finalize job mints a GitHub App token with owner: cozystack but no repositories input. Add repositories: ${{ github.event.repository.name }} to restrict the token to this repository only.

Suggested patch
       - name: Generate GitHub App token
         id: app-token
         uses: actions/create-github-app-token@d72941d797fd3113feb6b93fd0dec494b13a2547 # v1
         with:
           app-id: ${{ secrets.COZYSTACK_CI_APP_ID }}
           private-key: ${{ secrets.COZYSTACK_CI_PRIVATE_KEY }}
           owner: cozystack
+          repositories: ${{ github.event.repository.name }}
🤖 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/pull-requests-release.yaml around lines 30 - 36, The
GitHub App token step (id: app-token) currently mints a token with owner:
cozystack but no repository scope; update the step that uses
actions/create-github-app-token (the app-token step in the finalize job) to
include a repositories input set to ${{ github.event.repository.name }} so the
generated token is limited to the current repository only.

Source: Linters/SAST tools

🧹 Nitpick comments (2)
.github/workflows/tags.yaml (2)

336-338: 💤 Low value

Optional: Disable implicit caching on setup-node if strict security posture is desired.

Static analysis flags that actions/setup-node enables caching by default when a lockfile is present, which could theoretically be exploited in cache poisoning scenarios. Given this runs on self-hosted runners and the job has continue-on-error: true, the practical risk is low.

To explicitly disable caching if desired:

uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
  node-version: 22
  cache: ''
🤖 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/tags.yaml around lines 336 - 338, The workflow uses
actions/setup-node@... with node-version: 22 and currently relies on implicit
caching; to disable implicit lockfile-based caching set the cache input to an
empty string in the setup invocation (update the uses: actions/setup-node step
where node-version: 22 is specified to include cache: ''), which will explicitly
turn off setup-node's automatic caching behavior on that job.

Source: Linters/SAST tools


34-38: 💤 Low value

Informational: GitHub App token permissions are app-installation scoped.

The static analysis tool flags that app tokens inherit "blanket installation permissions." This is expected behavior—the token's capabilities are bounded by the GitHub App's installation configuration, not this workflow. The workflow correctly demonstrates least-privilege awareness by minting a separate read-only token at line 275 for the AI changelog step where write access isn't needed.

Reducing the app installation's permissions (if broader than necessary) would be a separate infrastructure change outside this PR's scope.

🤖 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/tags.yaml around lines 34 - 38, Add a short inline comment
above the actions/create-github-app-token step to state that GitHub App tokens
are scoped to the App installation (permissions come from the App installation
configuration) and that a separate least-privilege read-only token is minted
later for steps that don’t need write access; reference the existing keys
(app-id, private-key, owner) so reviewers understand this is intentional and not
a workflow misconfiguration.

Source: Linters/SAST tools

🤖 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/pre-commit.yml:
- Line 22: Update the actions/checkout pin in the pre-commit workflow so it
matches the repo standard: replace the current uses entry that references
actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 (v3) with the v4 SHA
used elsewhere (34e114876b0b11c390a56381ad16ebd13914f8d5) in the uses:
actions/checkout@... line, or alternatively add a short comment explaining why
this workflow must remain on v3 if you intentionally keep it; ensure the single
uses line is updated consistently.

In @.github/workflows/pull-requests.yaml:
- Around line 189-196: The "Generate GitHub App token" step (id: app-token,
uses: actions/create-github-app-token@...) currently issues a token scoped to
the app installation (all repos); restrict it to the current repo by adding the
repositories input: set repositories: ${{ github.event.repository.name }} under
the step's with: block so the generated token is limited to the current pull
request repository.
- Around line 120-126: The Generate GitHub App token step (id: app-token, uses:
actions/create-github-app-token@...) mints a token for owner: cozystack but
doesn’t restrict repositories; update that step to include the repositories
input and pass the current repo (e.g., repositories: ${{ github.repository }})
so the token is scoped to the current repository only.

---

Outside diff comments:
In @.github/workflows/pull-requests-release.yaml:
- Around line 30-36: The GitHub App token step (id: app-token) currently mints a
token with owner: cozystack but no repository scope; update the step that uses
actions/create-github-app-token (the app-token step in the finalize job) to
include a repositories input set to ${{ github.event.repository.name }} so the
generated token is limited to the current repository only.

---

Nitpick comments:
In @.github/workflows/tags.yaml:
- Around line 336-338: The workflow uses actions/setup-node@... with
node-version: 22 and currently relies on implicit caching; to disable implicit
lockfile-based caching set the cache input to an empty string in the setup
invocation (update the uses: actions/setup-node step where node-version: 22 is
specified to include cache: ''), which will explicitly turn off setup-node's
automatic caching behavior on that job.
- Around line 34-38: Add a short inline comment above the
actions/create-github-app-token step to state that GitHub App tokens are scoped
to the App installation (permissions come from the App installation
configuration) and that a separate least-privilege read-only token is minted
later for steps that don’t need write access; reference the existing keys
(app-id, private-key, owner) so reviewers understand this is intentional and not
a workflow misconfiguration.
🪄 Autofix (Beta)

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

Run ID: 019b05a6-5cb4-4e2f-92d0-2f910bb8864c

📥 Commits

Reviewing files that changed from the base of the PR and between eac7973 and 4feac5b.

📒 Files selected for processing (13)
  • .github/workflows/auto-release.yaml
  • .github/workflows/backport.yaml
  • .github/workflows/codegen-drift.yml
  • .github/workflows/labels.yaml
  • .github/workflows/pr-labeler.yaml
  • .github/workflows/pr-size.yaml
  • .github/workflows/pre-commit.yml
  • .github/workflows/pull-requests-release.yaml
  • .github/workflows/pull-requests.yaml
  • .github/workflows/retest.yaml
  • .github/workflows/stale.yaml
  • .github/workflows/tags.yaml
  • .github/workflows/update-releasenotes.yaml
🚧 Files skipped from review as they are similar to previous changes (4)
  • .github/workflows/update-releasenotes.yaml
  • .github/workflows/pr-labeler.yaml
  • .github/workflows/stale.yaml
  • .github/workflows/backport.yaml
🛑 Comments failed to post (3)
.github/workflows/pre-commit.yml (1)

22-22: ⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check all checkout action versions in this PR's workflow files
echo "=== Checkout action versions across workflows ==="
rg -n "uses:\s*actions/checkout@" .github/workflows/ | sort

Repository: cozystack/cozystack

Length of output: 1979


🏁 Script executed:

#!/bin/bash
set -euo pipefail
echo "=== pre-commit.yml around line 22 ==="
nl -ba .github/workflows/pre-commit.yml | sed -n '1,80p'

Repository: cozystack/cozystack

Length of output: 162


🏁 Script executed:

#!/bin/bash
set -euo pipefail
echo "=== pre-commit.yml ==="
nl -ba .github/workflows/pre-commit.yml | sed -n '1,120p'

Repository: cozystack/cozystack

Length of output: 147


🏁 Script executed:

#!/bin/bash
set -euo pipefail
echo "=== .github/workflows/pre-commit.yml (lines 1-120) ==="
nl -ba .github/workflows/pre-commit.yml | sed -n '1,120p'

Repository: cozystack/cozystack

Length of output: 179


🏁 Script executed:

#!/bin/bash
set -euo pipefail
echo "=== .github/workflows/pre-commit.yml (lines 1-60) ==="
awk 'NR>=1 && NR<=60 {printf "%d:%s\n", NR, $0}' .github/workflows/pre-commit.yml
echo "=== .github/workflows/pre-commit.yml (line 22 only) ==="
awk 'NR==22 {printf "%d:%s\n", NR, $0}' .github/workflows/pre-commit.yml

Repository: cozystack/cozystack

Length of output: 2379


Align actions/checkout pin in .github/workflows/pre-commit.yml with the rest of the repo

.github/workflows/pre-commit.yml pins actions/checkout to v3 (@f43a0e5ff2bd294095638e18286ca9a3d1956744) while other workflows pin to the v4 SHA (@34e114876b0b11c390a56381ad16ebd13914f8d5). Update pre-commit.yml to match for consistency, or document why v3 must be kept.

uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3
🤖 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/pre-commit.yml at line 22, Update the actions/checkout pin
in the pre-commit workflow so it matches the repo standard: replace the current
uses entry that references
actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 (v3) with the v4 SHA
used elsewhere (34e114876b0b11c390a56381ad16ebd13914f8d5) in the uses:
actions/checkout@... line, or alternatively add a short comment explaining why
this workflow must remain on v3 if you intentionally keep it; ensure the single
uses line is updated consistently.
.github/workflows/pull-requests.yaml (2)

120-126: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Scope the GitHub App token to the current repository.

The resolve_assets job mints a GitHub App token with owner: cozystack but no repositories input, granting access to all repositories in the installation. Add the repository restriction:

Suggested patch
       - name: Generate GitHub App token
         id: app-token
         uses: actions/create-github-app-token@d72941d797fd3113feb6b93fd0dec494b13a2547 # v1
         with:
           app-id: ${{ secrets.COZYSTACK_CI_APP_ID }}
           private-key: ${{ secrets.COZYSTACK_CI_PRIVATE_KEY }}
           owner: cozystack
+          repositories: ${{ github.event.repository.name }}
🧰 Tools
🪛 zizmor (1.25.2)

[error] 126-126: dangerous use of GitHub App tokens (github-app): token granted access to all repositories for this owner's app installation

(github-app)


[error] 122-122: dangerous use of GitHub App tokens (github-app): app token inherits blanket installation permissions

(github-app)

🤖 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/pull-requests.yaml around lines 120 - 126, The Generate
GitHub App token step (id: app-token, uses: actions/create-github-app-token@...)
mints a token for owner: cozystack but doesn’t restrict repositories; update
that step to include the repositories input and pass the current repo (e.g.,
repositories: ${{ github.repository }}) so the token is scoped to the current
repository only.

Source: Linters/SAST tools


189-196: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Scope the GitHub App token to the current repository in the e2e job.

Same issue as in resolve_assets: the token is granted access to all repositories for the owner's app installation. Add repositories: ${{ github.event.repository.name }}.

Suggested patch
       - name: Generate GitHub App token
         if: contains(github.event.pull_request.labels.*.name, 'release')
         id: app-token
         uses: actions/create-github-app-token@d72941d797fd3113feb6b93fd0dec494b13a2547 # v1
         with:
           app-id: ${{ secrets.COZYSTACK_CI_APP_ID }}
           private-key: ${{ secrets.COZYSTACK_CI_PRIVATE_KEY }}
           owner: cozystack
+          repositories: ${{ github.event.repository.name }}
📝 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.

      - name: Generate GitHub App token
        if: contains(github.event.pull_request.labels.*.name, 'release')
        id: app-token
        uses: actions/create-github-app-token@d72941d797fd3113feb6b93fd0dec494b13a2547 # v1
        with:
          app-id: ${{ secrets.COZYSTACK_CI_APP_ID }}
          private-key: ${{ secrets.COZYSTACK_CI_PRIVATE_KEY }}
          owner: cozystack
          repositories: ${{ github.event.repository.name }}
🧰 Tools
🪛 zizmor (1.25.2)

[error] 196-196: dangerous use of GitHub App tokens (github-app): token granted access to all repositories for this owner's app installation

(github-app)


[error] 192-192: dangerous use of GitHub App tokens (github-app): app token inherits blanket installation permissions

(github-app)

🤖 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/pull-requests.yaml around lines 189 - 196, The "Generate
GitHub App token" step (id: app-token, uses:
actions/create-github-app-token@...) currently issues a token scoped to the app
installation (all repos); restrict it to the current repo by adding the
repositories input: set repositories: ${{ github.event.repository.name }} under
the step's with: block so the generated token is limited to the current pull
request repository.

Source: Linters/SAST tools

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Re-approving after rebase onto current main (no content changes).

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 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/pre-commit.yml:
- Line 22: The workflow is pinning actions/checkout to the v3 SHA (uses:
actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744) which is inconsistent
with other workflows; update the uses: entry to the v4 SHA (34e114...) to match
the majority of workflows (replace the current actions/checkout SHA with the v4
SHA), or if v3 is intentionally required keep the current pin but add a short
comment explaining why v3 is needed for this workflow.
🪄 Autofix (Beta)

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

Run ID: 57eb9039-f318-43ca-9794-14a8d466d47d

📥 Commits

Reviewing files that changed from the base of the PR and between 4feac5b and 716b4ea.

📒 Files selected for processing (13)
  • .github/workflows/auto-release.yaml
  • .github/workflows/backport.yaml
  • .github/workflows/codegen-drift.yml
  • .github/workflows/labels.yaml
  • .github/workflows/pr-labeler.yaml
  • .github/workflows/pr-size.yaml
  • .github/workflows/pre-commit.yml
  • .github/workflows/pull-requests-release.yaml
  • .github/workflows/pull-requests.yaml
  • .github/workflows/retest.yaml
  • .github/workflows/stale.yaml
  • .github/workflows/tags.yaml
  • .github/workflows/update-releasenotes.yaml
🚧 Files skipped from review as they are similar to previous changes (5)
  • .github/workflows/pr-labeler.yaml
  • .github/workflows/stale.yaml
  • .github/workflows/retest.yaml
  • .github/workflows/update-releasenotes.yaml
  • .github/workflows/backport.yaml

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check all checkout SHAs used across workflows
echo "=== actions/checkout versions in workflows ==="
rg -n "uses:\s*actions/checkout@" .github/workflows/ | sort

Repository: cozystack/cozystack

Length of output: 1976


Align actions/checkout pin in pre-commit.yml (line 22).
.github/workflows/pre-commit.yml pins actions/checkout to the v3 SHA, while most other workflows use the v4 SHA (34e114...)—and a couple use @v4 or v6.0.2. Update to the v4 SHA for consistency unless v3 is required (then document why).

🤖 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/pre-commit.yml at line 22, The workflow is pinning
actions/checkout to the v3 SHA (uses:
actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744) which is inconsistent
with other workflows; update the uses: entry to the v4 SHA (34e114...) to match
the majority of workflows (replace the current actions/checkout SHA with the v4
SHA), or if v3 is intentionally required keep the current pin but add a short
comment explaining why v3 is needed for this workflow.

@myasnikovdaniil myasnikovdaniil force-pushed the ci/scorecard-token-perms-pin branch from 716b4ea to 710fe65 Compare June 17, 2026 11:55
myasnikovdaniil added a commit that referenced this pull request Jun 20, 2026
The build-main cache warmer is a privileged workflow (packages:write + an OCIR
push token) and never pushes git, so set persist-credentials: false to keep the
checkout's GITHUB_TOKEN out of .git/config where a later build step could read
it. (SHA-pinning the actions is handled repo-wide in #2849.)

Addresses non-blocking follow-up 7 on #2948.

Assisted-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
myasnikovdaniil added a commit that referenced this pull request Jun 22, 2026
## What

Consolidated landing branch for a set of interdependent platform/CI
stabilization fixes. It began as the source PRs in the table below (in
dependency order) and has since grown with review-driven fixes and a few
production-behavior changes — the full current inventory is under
**Scope** below. Landing them as one unit lets CI run against the real
combined state instead of a fragile bottom-up merge train.

## Source PRs (dependency order)

| # | Commit | Source PR | Addresses |
|---|--------|-----------|-----------|
| 1 | fix(capi): startupProbe on capi-controller-manager | #2946 | capi
crashloop during cert provisioning |
| 2 | test(e2e): bucket.bats port-forward + S3 client reliability |
#2944 | flaky bucket test |
| 3 | fix(seaweedfs): restore -lock BucketClass, s3 svc name, drop
volumeSizeLimitMB | #2943 | missing bucket/access classes |
| 4 | test(e2e): in-cluster Cilium endpoint-leak healer (install + apps)
| #2874 | cilium "IP already in use" leak |
| 5 | refactor(build): mode=max registry cache + main-only warmer |
#2938 | build cache |
| 6 | ci(build): isolate each PR build on its own ephemeral runner VM |
#2939 | shared-buildkit contention |
| 7 | fix(e2e): LINSTOR post-install waits on a single 15m deadline |
#2928 | LINSTOR provisioning timeouts |
| 8 | test(metallb): assert digest-pinned image form, not version
literal | #2873 | brittle metallb assertion |

## Why consolidated

These form a dependency DAG (verified from CI logs): e.g. 2943 needs
2946, 2938 needs 2874, 2928 needs 2938/2939, 2873 needs 2928. Tested
bottom-up, each lower PR runs with none of the fixes above it and can't
go green alone. This branch carries all of them, so CI runs against the
real combined state.

## Verification status

CI is green on the latest head (`5653c30`): full-suite E2E passes
end-to-end and is reproducible (2 of 3 runs on this SHA green). The one
red run was a LINSTOR tie-breaker / DRBD-metadata infra flake on the
sandbox, unrelated to the diff — a different environmental subsystem
failed each run (details in the comments below). The earlier 3-hour
crust-gather snapshot hang is fixed in `5653c30`.

## Scope beyond the original 8

The branch has grown past the 8 source PRs above with review-driven
fixes and a few production-behavior changes. For reviewer transparency,
the full set:

**Review fixes (@lexfrei review):**
- **B1 (blocker)** — `8f41910` converts the SeaweedFS
`-lock`/`-readonly` BucketClasses and the s3 service-name override into
`patches/`, wired into `make update`, so a re-vendor no longer drops
them.
- **FU1** — `cc99230` grants the cilium leak-healer `delete` on pods
(both delete remedies were RBAC-forbidden).
- **FU3** — `014de3f` adds a unit test for the HelmRelease update
conflict-retry path.
- **FU4** — `6deddc6` makes the leak-healer refuse a disconnect for any
non-terminal owner phase, not just `Running`.
- **FU7** — `f3914d2` sets `persist-credentials: false` on the
build-cache checkout (SHA-pinning is handled repo-wide in #2849).

**Production-behavior changes (not in the table above):**
- VPA `updateMode: Auto → Initial` for etcd (`6e9ff90`) and monitoring
(`0997105`), plus `vmselect`/`vmstorage` `minAllowed` floors — stops
install-time eviction churn.
- cozystack-api: `retry.RetryOnConflict` on the Application→HelmRelease
update path (`728f676`).

**E2E robustness follow-ups:** per-test crust-gather snapshots
(`50e5f94`), EtcdBackupSchedule wait (`763b85e`), tenant-node
single-deadline wait (`9e43d45`), harbor BucketClaim 10m budget
(`d7f244f`), tenant API via LoadBalancer (`9beda4e`), crust-gather pin
(`95f42aa`).

**Not addressed (by decision):** FU2 — the s3 Service keeps
`seaweedfs.name`-based naming via patch (rendered output unchanged;
cozystack renders `fullname == seaweedfs`, so it matches the
`componentName`/`fullname` siblings in practice). FU6 — fork PR build
push is handled by the existing mirror-to-same-repo-branch workflow.

## Relates to

#2946 #2944 #2943 #2874 #2938 #2939 #2928 #2873 — if this lands, those
can be closed; otherwise they remain the granular per-PR review path.


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
  * Added CI build cache warming for main branch builds.
* Added a best-effort in-cluster Cilium endpoint self-heal watchdog for
e2e installs.
* Extended SeaweedFS COSI with object-lock support plus separate
readonly access.

* **Improvements**
* Reduced e2e install flakiness with a shared readiness deadline, safer
waits, and improved cleanup/diagnostics capture.
* Prevented install-time churn by switching VPA update mode to
**Initial**.
  * Added container startup probes and updated SeaweedFS volume sizing.

* **Tests**
* Improved e2e robustness (etcd backup schedule waiting, digest-pinned
image checks, and corrected S3 port-forwarding).
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
Raise the OpenSSF Scorecard Token-Permissions and Pinned-Dependencies
checks (both currently 0/10) ahead of CNCF Incubation due diligence:

- Declare a top-level read-only default (permissions: contents: read) in
  every workflow, plus an explicit per-job permissions: block that grants
  only the writes that job actually needs. Workflows that previously held
  write scopes at the workflow level (backport, pr-labeler, pr-size,
  stale) now hold them at job level instead.
- Pin every GitHub-owned and third-party action to a full commit SHA with
  a version comment. scorecard.yml was already pinned in #2721.

No triggers, job graph, or step logic change; only token scopes and
action refs. Self-hosted release jobs keep the exact write scopes they
already declared.

Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
Assisted-By: Claude <noreply@anthropic.com>
@myasnikovdaniil myasnikovdaniil force-pushed the ci/scorecard-token-perms-pin branch from 710fe65 to 76ad921 Compare June 23, 2026 06:33

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
.github/workflows/tags.yaml (2)

467-492: 🔒 Security & Privacy | 🟠 Major

Limit the website App token to the website repository.

This job only checks out and opens a PR in cozystack/website; with owner: cozystack and no repositories parameter, the token has access to all repositories in the App installation.

Suggested patch
         with:
           app-id: ${{ secrets.COZYSTACK_CI_APP_ID }}
           private-key: ${{ secrets.COZYSTACK_CI_PRIVATE_KEY }}
           owner: cozystack
+          repositories: website
+          permission-contents: write
+          permission-pull-requests: write
+          permission-metadata: read
🤖 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/tags.yaml around lines 467 - 492, The
`create-github-app-token` action in the workflow is configured with `owner:
cozystack` but lacks a `repositories` parameter, which grants the token access
to all repositories in the organization. Since this job only accesses the
`cozystack/website` repository for checkout and PR operations, add a
`repositories` parameter to the `create-github-app-token` action step to
explicitly limit the token scope to just the website repository, restricting the
token's access from the entire organization to only what is needed.

Source: Linters/SAST tools


307-312: 🔒 Security & Privacy | 🟠 Major

Use the read-only token for checkout and omit unnecessary write credentials.

The workflow explicitly creates a read-only token (app-token-read) to constrain the AI step, but the checkout at lines 307-312 uses the full write token (steps.app-token) instead. Checkout only requires read access to fetch the tag; using the write token unnecessarily persists elevated credentials. Apply the suggested fix to use app-token-read with persist-credentials: false.

Suggested patch
       - name: Checkout release tag
         uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
         with:
           ref: ${{ steps.tag.outputs.tag }}
           fetch-depth: 0
           fetch-tags: true
-          token: ${{ steps.app-token.outputs.token }}
+          token: ${{ steps.app-token-read.outputs.token }}
+          persist-credentials: false
🤖 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/tags.yaml around lines 307 - 312, The checkout action in
the workflow is using the full write token (steps.app-token.outputs.token) when
it should use the read-only token (app-token-read) since checkout only needs
read access to fetch the tag. Replace the token reference in the checkout step's
token parameter with the read-only app-token-read token, and add
persist-credentials: false to ensure elevated credentials are not unnecessarily
persisted to the runner environment.
.github/workflows/pull-requests-release.yaml (1)

32-36: 🔒 Security & Privacy | 🟠 Major

Constrain the release App token to only required permissions.

This job only needs write access to repository contents (for tags and branches) and read access to metadata (for releases). Without explicit permission parameters, the token inherits all permissions granted to the GitHub App installation, violating the principle of least privilege.

Suggested patch
         with:
           app-id: ${{ secrets.COZYSTACK_CI_APP_ID }}
           private-key: ${{ secrets.COZYSTACK_CI_PRIVATE_KEY }}
           owner: cozystack
+          repositories: ${{ github.event.repository.name }}
+          permission-contents: write
+          permission-metadata: read
🤖 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/pull-requests-release.yaml around lines 32 - 36, The
create-github-app-token action is not constraining the generated token to only
the required permissions, which violates the principle of least privilege. Add a
permissions parameter to the action configuration that explicitly grants only
write access to repository contents (for managing tags and branches) and read
access to metadata (for releases). This ensures the token has minimal necessary
permissions rather than inheriting all permissions from the GitHub App
installation.

Source: Linters/SAST tools

🤖 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/pull-requests-release.yaml:
- Around line 56-58: The actions/checkout action is persisting the GITHUB_TOKEN
via git configuration, which prevents the APP_TOKEN from being used in
subsequent git operations. In the
actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 step's with section
(where fetch-depth is already configured), add persist-credentials: false to
disable automatic token persistence and allow the APP_TOKEN set in earlier steps
to be used for git operations like the remote set-url call that follows.

In @.github/workflows/pull-requests.yaml:
- Around line 62-65: The actions/checkout step in the Build job (line 62-65)
stores the GITHUB_TOKEN in git config by default when elevated permissions are
present, creating a security risk. Add persist-credentials: false to the with
section of the checkout action in the Build job to disable credential
persistence. Additionally, apply the same change to the E2E job's checkout
action (mentioned at line 207) which has checks: write permission, ensuring both
elevated-permission checkout operations prevent credential storage.
- Around line 73-79: The "Set up build toolchain" step installs helm-unittest
plugin and Flux CLI without pinning to specific versions, which results in
non-reproducible builds. Modify the helm plugin install command to specify an
exact version tag for the helm-unittest plugin (e.g., appending a version
constraint to the GitHub URL) and update the Flux installation curl command to
download a specific release version instead of the latest. Alternatively,
consider baking these pre-built tools into the oracle-vm runner image as a more
robust solution to ensure consistency across all builds.
- Around line 143-149: The "Generate GitHub App token" step in the
pull-requests.yaml workflow currently specifies only owner: cozystack without
limiting the repositories, which grants the token access to all repositories in
the organization. To scope this token only to the current repository as needed
for listing and downloading draft release assets, add a repositories field to
the with: section of the actions/create-github-app-token action that specifies
the current repository name. This change should be applied to both occurrences
of the token generation step (the one shown and the one at lines 212-219).

---

Outside diff comments:
In @.github/workflows/pull-requests-release.yaml:
- Around line 32-36: The create-github-app-token action is not constraining the
generated token to only the required permissions, which violates the principle
of least privilege. Add a permissions parameter to the action configuration that
explicitly grants only write access to repository contents (for managing tags
and branches) and read access to metadata (for releases). This ensures the token
has minimal necessary permissions rather than inheriting all permissions from
the GitHub App installation.

In @.github/workflows/tags.yaml:
- Around line 467-492: The `create-github-app-token` action in the workflow is
configured with `owner: cozystack` but lacks a `repositories` parameter, which
grants the token access to all repositories in the organization. Since this job
only accesses the `cozystack/website` repository for checkout and PR operations,
add a `repositories` parameter to the `create-github-app-token` action step to
explicitly limit the token scope to just the website repository, restricting the
token's access from the entire organization to only what is needed.
- Around line 307-312: The checkout action in the workflow is using the full
write token (steps.app-token.outputs.token) when it should use the read-only
token (app-token-read) since checkout only needs read access to fetch the tag.
Replace the token reference in the checkout step's token parameter with the
read-only app-token-read token, and add persist-credentials: false to ensure
elevated credentials are not unnecessarily persisted to the runner environment.
🪄 Autofix (Beta)

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

Run ID: fc6a8cd5-8734-4ec7-a433-2b75f399b96d

📥 Commits

Reviewing files that changed from the base of the PR and between 710fe65 and 76ad921.

📒 Files selected for processing (13)
  • .github/workflows/auto-release.yaml
  • .github/workflows/backport.yaml
  • .github/workflows/codegen-drift.yml
  • .github/workflows/labels.yaml
  • .github/workflows/pr-labeler.yaml
  • .github/workflows/pr-size.yaml
  • .github/workflows/pre-commit.yml
  • .github/workflows/pull-requests-release.yaml
  • .github/workflows/pull-requests.yaml
  • .github/workflows/retest.yaml
  • .github/workflows/stale.yaml
  • .github/workflows/tags.yaml
  • .github/workflows/update-releasenotes.yaml
🚧 Files skipped from review as they are similar to previous changes (5)
  • .github/workflows/retest.yaml
  • .github/workflows/update-releasenotes.yaml
  • .github/workflows/stale.yaml
  • .github/workflows/pr-labeler.yaml
  • .github/workflows/backport.yaml

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Caution

Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
.github/workflows/tags.yaml (2)

467-492: 🔒 Security & Privacy | 🟠 Major

Limit the website App token to the website repository.

This job only checks out and opens a PR in cozystack/website; with owner: cozystack and no repositories parameter, the token has access to all repositories in the App installation.

Suggested patch
         with:
           app-id: ${{ secrets.COZYSTACK_CI_APP_ID }}
           private-key: ${{ secrets.COZYSTACK_CI_PRIVATE_KEY }}
           owner: cozystack
+          repositories: website
+          permission-contents: write
+          permission-pull-requests: write
+          permission-metadata: read
🤖 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/tags.yaml around lines 467 - 492, The
`create-github-app-token` action in the workflow is configured with `owner:
cozystack` but lacks a `repositories` parameter, which grants the token access
to all repositories in the organization. Since this job only accesses the
`cozystack/website` repository for checkout and PR operations, add a
`repositories` parameter to the `create-github-app-token` action step to
explicitly limit the token scope to just the website repository, restricting the
token's access from the entire organization to only what is needed.

Source: Linters/SAST tools


307-312: 🔒 Security & Privacy | 🟠 Major

Use the read-only token for checkout and omit unnecessary write credentials.

The workflow explicitly creates a read-only token (app-token-read) to constrain the AI step, but the checkout at lines 307-312 uses the full write token (steps.app-token) instead. Checkout only requires read access to fetch the tag; using the write token unnecessarily persists elevated credentials. Apply the suggested fix to use app-token-read with persist-credentials: false.

Suggested patch
       - name: Checkout release tag
         uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
         with:
           ref: ${{ steps.tag.outputs.tag }}
           fetch-depth: 0
           fetch-tags: true
-          token: ${{ steps.app-token.outputs.token }}
+          token: ${{ steps.app-token-read.outputs.token }}
+          persist-credentials: false
🤖 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/tags.yaml around lines 307 - 312, The checkout action in
the workflow is using the full write token (steps.app-token.outputs.token) when
it should use the read-only token (app-token-read) since checkout only needs
read access to fetch the tag. Replace the token reference in the checkout step's
token parameter with the read-only app-token-read token, and add
persist-credentials: false to ensure elevated credentials are not unnecessarily
persisted to the runner environment.
.github/workflows/pull-requests-release.yaml (1)

32-36: 🔒 Security & Privacy | 🟠 Major

Constrain the release App token to only required permissions.

This job only needs write access to repository contents (for tags and branches) and read access to metadata (for releases). Without explicit permission parameters, the token inherits all permissions granted to the GitHub App installation, violating the principle of least privilege.

Suggested patch
         with:
           app-id: ${{ secrets.COZYSTACK_CI_APP_ID }}
           private-key: ${{ secrets.COZYSTACK_CI_PRIVATE_KEY }}
           owner: cozystack
+          repositories: ${{ github.event.repository.name }}
+          permission-contents: write
+          permission-metadata: read
🤖 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/pull-requests-release.yaml around lines 32 - 36, The
create-github-app-token action is not constraining the generated token to only
the required permissions, which violates the principle of least privilege. Add a
permissions parameter to the action configuration that explicitly grants only
write access to repository contents (for managing tags and branches) and read
access to metadata (for releases). This ensures the token has minimal necessary
permissions rather than inheriting all permissions from the GitHub App
installation.

Source: Linters/SAST tools

🤖 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/pull-requests-release.yaml:
- Around line 56-58: The actions/checkout action is persisting the GITHUB_TOKEN
via git configuration, which prevents the APP_TOKEN from being used in
subsequent git operations. In the
actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 step's with section
(where fetch-depth is already configured), add persist-credentials: false to
disable automatic token persistence and allow the APP_TOKEN set in earlier steps
to be used for git operations like the remote set-url call that follows.

In @.github/workflows/pull-requests.yaml:
- Around line 62-65: The actions/checkout step in the Build job (line 62-65)
stores the GITHUB_TOKEN in git config by default when elevated permissions are
present, creating a security risk. Add persist-credentials: false to the with
section of the checkout action in the Build job to disable credential
persistence. Additionally, apply the same change to the E2E job's checkout
action (mentioned at line 207) which has checks: write permission, ensuring both
elevated-permission checkout operations prevent credential storage.
- Around line 73-79: The "Set up build toolchain" step installs helm-unittest
plugin and Flux CLI without pinning to specific versions, which results in
non-reproducible builds. Modify the helm plugin install command to specify an
exact version tag for the helm-unittest plugin (e.g., appending a version
constraint to the GitHub URL) and update the Flux installation curl command to
download a specific release version instead of the latest. Alternatively,
consider baking these pre-built tools into the oracle-vm runner image as a more
robust solution to ensure consistency across all builds.
- Around line 143-149: The "Generate GitHub App token" step in the
pull-requests.yaml workflow currently specifies only owner: cozystack without
limiting the repositories, which grants the token access to all repositories in
the organization. To scope this token only to the current repository as needed
for listing and downloading draft release assets, add a repositories field to
the with: section of the actions/create-github-app-token action that specifies
the current repository name. This change should be applied to both occurrences
of the token generation step (the one shown and the one at lines 212-219).

---

Outside diff comments:
In @.github/workflows/pull-requests-release.yaml:
- Around line 32-36: The create-github-app-token action is not constraining the
generated token to only the required permissions, which violates the principle
of least privilege. Add a permissions parameter to the action configuration that
explicitly grants only write access to repository contents (for managing tags
and branches) and read access to metadata (for releases). This ensures the token
has minimal necessary permissions rather than inheriting all permissions from
the GitHub App installation.

In @.github/workflows/tags.yaml:
- Around line 467-492: The `create-github-app-token` action in the workflow is
configured with `owner: cozystack` but lacks a `repositories` parameter, which
grants the token access to all repositories in the organization. Since this job
only accesses the `cozystack/website` repository for checkout and PR operations,
add a `repositories` parameter to the `create-github-app-token` action step to
explicitly limit the token scope to just the website repository, restricting the
token's access from the entire organization to only what is needed.
- Around line 307-312: The checkout action in the workflow is using the full
write token (steps.app-token.outputs.token) when it should use the read-only
token (app-token-read) since checkout only needs read access to fetch the tag.
Replace the token reference in the checkout step's token parameter with the
read-only app-token-read token, and add persist-credentials: false to ensure
elevated credentials are not unnecessarily persisted to the runner environment.
🪄 Autofix (Beta)

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

Run ID: fc6a8cd5-8734-4ec7-a433-2b75f399b96d

📥 Commits

Reviewing files that changed from the base of the PR and between 710fe65 and 76ad921.

📒 Files selected for processing (13)
  • .github/workflows/auto-release.yaml
  • .github/workflows/backport.yaml
  • .github/workflows/codegen-drift.yml
  • .github/workflows/labels.yaml
  • .github/workflows/pr-labeler.yaml
  • .github/workflows/pr-size.yaml
  • .github/workflows/pre-commit.yml
  • .github/workflows/pull-requests-release.yaml
  • .github/workflows/pull-requests.yaml
  • .github/workflows/retest.yaml
  • .github/workflows/stale.yaml
  • .github/workflows/tags.yaml
  • .github/workflows/update-releasenotes.yaml
🚧 Files skipped from review as they are similar to previous changes (5)
  • .github/workflows/retest.yaml
  • .github/workflows/update-releasenotes.yaml
  • .github/workflows/stale.yaml
  • .github/workflows/pr-labeler.yaml
  • .github/workflows/backport.yaml
🛑 Comments failed to post (4)
.github/workflows/pull-requests-release.yaml (1)

56-58: 🔒 Security & Privacy | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cd .github/workflows && cat -n pull-requests-release.yaml | head -100

Repository: cozystack/cozystack

Length of output: 4364


🌐 Web query:

actions/checkout persist-credentials default behavior github

💡 Result:

The default behavior for the persist-credentials input in the actions/checkout GitHub Action is true [1][2][3]. When persist-credentials is set to true (the default), the action configures the Git repository using the provided token or SSH key [1][4]. This allows subsequent steps in your workflow to execute authenticated Git commands (such as git fetch or git push) without needing to manually re-authenticate [1][5]. Key details regarding this behavior include: - Security Implementation: The action stores the credentials in a separate file (typically under $RUNNER_TEMP) rather than directly inside the .git/config file to improve security [1][6]. - Post-Job Cleanup: The action includes a post-job cleanup step that removes these persisted credentials once the job completes [1][6]. - Opting Out: If you wish to prevent the action from persisting credentials, you can explicitly set persist-credentials: false in your workflow configuration [1][2]. While some users have expressed security concerns regarding the default being true [7][8], it remains the established default to ensure seamless functionality for common Git workflows in GitHub Actions [1][4].

Citations:


🌐 Web query:

git remote set-url with credentials persist-credentials actions/checkout precedence

💡 Result:

In GitHub Actions, the actions/checkout action handles authentication by injecting a persistent credential—typically a GITHUB_TOKEN—into the local Git configuration [1][2]. Specifically, it sets an http.<url>.extraheader configuration entry [3][4]. Because this extraheader is applied at the local Git configuration level, it takes precedence over many other credential sources, often making it difficult to override with standard methods like git remote set-url or external credential helpers [3][4]. When persist-credentials is enabled (the default), actions/checkout ensures this authentication configuration persists for the duration of the job [1][2]. If you attempt to use git remote set-url to inject different credentials, Git may continue to prefer the injected extraheader configuration, causing the new remote URL to be ignored for authentication purposes [3][4]. To successfully use different credentials, you have two primary approaches: 1. Disable persistence: Set persist-credentials: false in your actions/checkout step [1][2][5]. This prevents the action from injecting the extraheader [3]. You can then configure your desired credentials (e.g., using a personal access token) via git remote set-url or other authentication commands [6][5]. 2. Clear the extraheader: If you cannot disable persist-credentials, you must manually unset the extraheader configuration before applying your own credentials [3][4]. You can do this by running: git config --unset http.https://github.com/.extraheader (Adjust the URL host if necessary to match the configured remote) [4]. Note that newer versions of actions/checkout have improved security by storing these credentials in a separate file under $RUNNER_TEMP rather than directly in .git/config, but the precedence behavior regarding extraheader configuration remains a significant factor in how subsequent Git operations resolve authentication [1][7].

Citations:


Disable credential persistence to allow APP_TOKEN to be used for git operations.

actions/checkout persists the job's GITHUB_TOKEN via git's http.<url>.extraheader configuration. This configuration takes precedence over credentials embedded in the remote URL, which means the git remote set-url call in the next step will not override the persisted token. Add persist-credentials: false to allow the APP_TOKEN to be used as intended.

       - name: Checkout repo
         uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
         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.

        uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
        with:
          fetch-depth: 0
          persist-credentials: false
🧰 Tools
🪛 zizmor (1.26.1)

[warning] 55-58: 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/pull-requests-release.yaml around lines 56 - 58, The
actions/checkout action is persisting the GITHUB_TOKEN via git configuration,
which prevents the APP_TOKEN from being used in subsequent git operations. In
the actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 step's with
section (where fetch-depth is already configured), add persist-credentials:
false to disable automatic token persistence and allow the APP_TOKEN set in
earlier steps to be used for git operations like the remote set-url call that
follows.
.github/workflows/pull-requests.yaml (3)

62-65: 🔒 Security & Privacy | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cat -n .github/workflows/pull-requests.yaml | head -300

Repository: cozystack/cozystack

Length of output: 14281


Add persist-credentials: false to checkout actions in build and e2e jobs.

The Build job has packages: write permission (line 52), and E2E has checks: write permission (line 207). When actions/checkout runs with these elevated scopes, it stores the GITHUB_TOKEN in git config by default. This allows subsequent PR-controlled code to potentially access or misuse the token via commands like git config --list. Disable credential persistence on both checkouts.

Suggested patch
       - name: Checkout code
         uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
         with:
           fetch-depth: 0
           fetch-tags: true
+          persist-credentials: false
@@
       - name: Checkout code
         uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
         with:
           fetch-depth: 0
+          persist-credentials: false
🤖 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/pull-requests.yaml around lines 62 - 65, The
actions/checkout step in the Build job (line 62-65) stores the GITHUB_TOKEN in
git config by default when elevated permissions are present, creating a security
risk. Add persist-credentials: false to the with section of the checkout action
in the Build job to disable credential persistence. Additionally, apply the same
change to the E2E job's checkout action (mentioned at line 207) which has
checks: write permission, ensuring both elevated-permission checkout operations
prevent credential storage.

73-79: 🔒 Security & Privacy | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cat -n .github/workflows/pull-requests.yaml | head -100

Repository: cozystack/cozystack

Length of output: 4553


🏁 Script executed:

# Check the full context of the problematic section and surrounding lines
sed -n '60,90p' .github/workflows/pull-requests.yaml

Repository: cozystack/cozystack

Length of output: 1378


🏁 Script executed:

# Search for other version pins or tool configurations in the workflow
rg -i "helm|flux" .github/workflows/pull-requests.yaml -B 2 -A 2

Repository: cozystack/cozystack

Length of output: 1482


🏁 Script executed:

# Check for any TODO or issue references about this known limitation
rg -i "todo|fixme|issue|pin.*helm|pin.*flux" .github/workflows/pull-requests.yaml -B 2 -A 2

Repository: cozystack/cozystack

Length of output: 240


🏁 Script executed:

# Check if there are any version constraints or pins elsewhere in the codebase for these tools
fd -type f \( -name "*.yaml" -o -name "*.yml" -o -name "Dockerfile*" -o -name "*.sh" \) -path "./.github/*" -o -path "./Dockerfile*" | head -20
rg "helm-unittest|fluxcd" .github/ --type yaml -l

Repository: cozystack/cozystack

Length of output: 399


🏁 Script executed:

# Check the git history to see if there were recent changes related to tool versioning
git log --oneline -20 -- .github/workflows/pull-requests.yaml

Repository: cozystack/cozystack

Length of output: 1689


Pin Helm and Flux tool versions.

This hardening PR pins actions, but the "Set up build toolchain" step installs the latest helm-unittest plugin and Flux at runtime without version constraints, creating non-reproducible and unverified builds. Either pin exact versions using release tags/SHAs (e.g., helm plugin install https://github.com/helm-unittest/helm-unittest@v1.x.x) or bake these tools into the oracle-vm runner image as mentioned in the existing comment.

🤖 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/pull-requests.yaml around lines 73 - 79, The "Set up build
toolchain" step installs helm-unittest plugin and Flux CLI without pinning to
specific versions, which results in non-reproducible builds. Modify the helm
plugin install command to specify an exact version tag for the helm-unittest
plugin (e.g., appending a version constraint to the GitHub URL) and update the
Flux installation curl command to download a specific release version instead of
the latest. Alternatively, consider baking these pre-built tools into the
oracle-vm runner image as a more robust solution to ensure consistency across
all builds.

143-149: 🔒 Security & Privacy | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, let's look at the complete workflow file structure
find . -name "pull-requests.yaml" -type f | head -20

Repository: cozystack/cozystack

Length of output: 196


🏁 Script executed:

# Once we find the file, let's examine the relevant sections
cat -n .github/workflows/pull-requests.yaml | sed -n '140,155p'

Repository: cozystack/cozystack

Length of output: 847


🏁 Script executed:

# Also check the second location mentioned (lines 212-219)
cat -n .github/workflows/pull-requests.yaml | sed -n '209,225p'

Repository: cozystack/cozystack

Length of output: 1072


🌐 Web query:

actions/create-github-app-token action documentation parameters repositories permission-contents

💡 Result:

The actions/create-github-app-token action allows for granular control over the GitHub App installation access token it generates using specific input parameters [1][2]. Repositories Parameter The repositories parameter is an optional input that accepts a comma-separated or newline-separated list of repository names to which the token should be granted access [3][4][5]. - Scoping Behavior: If both the owner and repositories inputs are empty, the token is scoped only to the current repository [3][4]. If an owner is set but repositories is empty, the token is scoped to all repositories within that owner's installation [3][4][5]. - Formatting: It supports bare repository names (e.g., repo) as well as full repository names (e.g., owner/repo), provided the owner matches the specified owner input or the current repository owner if the owner is unset [4][6]. Permission-contents Parameter The action supports granular permissions by providing inputs prefixed with permission- [1][2]. The permission-contents parameter is one of these dynamically generated inputs [7]. - Purpose: It specifies the level of permission to grant the access token for repository contents, including commits, branches, downloads, releases, and merges [7]. - Usage: It can be set to either read or write [7]. - Default Behavior: If no specific permission inputs are provided, the generated token inherits all permissions granted to the GitHub App installation [1][2]. It is recommended to explicitly list required permissions for better security [2]. Note that the requested permissions must be granted to the App installation; requesting a permission the App does not possess will cause the action to fail [1][2].

Citations:


🏁 Script executed:

# Let's see what these tokens are used for in the workflow
cat -n .github/workflows/pull-requests.yaml | sed -n '143,175p'

Repository: cozystack/cozystack

Length of output: 1790


🏁 Script executed:

# Check the second instance and its usage
cat -n .github/workflows/pull-requests.yaml | sed -n '212,235p'

Repository: cozystack/cozystack

Length of output: 1401


🏁 Script executed:

# Let's search for all references to the app-token to understand its usage
rg "steps\.app-token|needs\..+\.outputs\.token" .github/workflows/pull-requests.yaml -A 3 -B 1

Repository: cozystack/cozystack

Length of output: 638


🏁 Script executed:

# Get the broader context of both job sections to understand their purpose
cat -n .github/workflows/pull-requests.yaml | sed -n '125,160p'

Repository: cozystack/cozystack

Length of output: 1631


🏁 Script executed:

# And the second job context
cat -n .github/workflows/pull-requests.yaml | sed -n '190,240p'

Repository: cozystack/cozystack

Length of output: 2587


🏁 Script executed:

# Let's search for where APP_TOKEN is used in the workflow
rg "APP_TOKEN" .github/workflows/pull-requests.yaml -B 2 -A 2

Repository: cozystack/cozystack

Length of output: 577


🏁 Script executed:

# Get the complete context of how the e2e job uses the token
cat -n .github/workflows/pull-requests.yaml | sed -n '235,300p'

Repository: cozystack/cozystack

Length of output: 3636


🏁 Script executed:

# Let's see the full workflow to understand the token usage patterns
wc -l .github/workflows/pull-requests.yaml

Repository: cozystack/cozystack

Length of output: 198


🏁 Script executed:

# Check the rest of the e2e job and see where APP_TOKEN is used
cat -n .github/workflows/pull-requests.yaml | sed -n '250,350p'

Repository: cozystack/cozystack

Length of output: 5393


Scope release-asset app tokens to the current repository.

The token generation currently specifies owner: cozystack without repositories or explicit permission limits. This mints a token with access to all repositories in the cozystack organization and all permissions granted to the GitHub App installation. The tokens are only used to list and download draft release assets from the current repository, so they should be scoped accordingly.

Suggested patch
         with:
           app-id: ${{ secrets.COZYSTACK_CI_APP_ID }}
           private-key: ${{ secrets.COZYSTACK_CI_PRIVATE_KEY }}
           owner: cozystack
+          repositories: ${{ github.event.repository.name }}
+          permission-contents: read
+          permission-metadata: read

Also applies to: lines 212-219

🧰 Tools
🪛 zizmor (1.26.1)

[error] 149-149: dangerous use of GitHub App tokens (github-app): token granted access to all repositories for this owner's app installation

(github-app)


[error] 145-145: dangerous use of GitHub App tokens (github-app): app token inherits blanket installation permissions

(github-app)

🤖 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/pull-requests.yaml around lines 143 - 149, The "Generate
GitHub App token" step in the pull-requests.yaml workflow currently specifies
only owner: cozystack without limiting the repositories, which grants the token
access to all repositories in the organization. To scope this token only to the
current repository as needed for listing and downloading draft release assets,
add a repositories field to the with: section of the
actions/create-github-app-token action that specifies the current repository
name. This change should be applied to both occurrences of the token generation
step (the one shown and the one at lines 212-219).

Source: Linters/SAST tools

@lexfrei Aleksei Sviridkin (lexfrei) merged commit 7303552 into main Jun 24, 2026
18 of 19 checks passed
@lexfrei Aleksei Sviridkin (lexfrei) deleted the ci/scorecard-token-perms-pin branch June 24, 2026 06:35
myasnikovdaniil added a commit that referenced this pull request Jun 25, 2026
## What this PR does

Raises OpenSSF Scorecard **Pinned-Dependencies** (currently **1/10**) by
having Renovate manage and pin container base images and GitHub Actions
to immutable digests.

[PR #2849](#2849) SHA-pinned
actions in 13 workflows, but the Scorecard check also scores **container
base images** — 58 unpinned `FROM` lines across **27 distinct images** —
which dominate the score and keep it at 1/10. #2849 also left
`build-main.yaml` and `release-e2e.yaml` actions unpinned, and nothing
in the Renovate config kept the newly-added action SHAs updated (they
were set to rot).

### Changes

- **`.github/renovate.json`**
- Add `dockerfile` and `github-actions` to `enabledManagers` (was
`gomod`-only).
- `pinDigests: true` for both managers — Renovate opens the initial "pin
to `@sha256:`" PR (including the two workflows #2849 missed) and keeps
digests fresh thereafter.
- Batch the initial pin + all digest-only refreshes into a **single
weekly PR** (`matchUpdateTypes: ["digest", "pinDigest"]`, scheduled
Monday). Frequently-rebuilt bases (alpine, debian, golang, distroless)
would otherwise trickle in one rebuild-triggering PR each and load the
shared CI runner. Real tag bumps (major/minor/patch) are intentionally
excluded from the group so they get individual review.
- Existing `do-not-merge/hold` label + `automerge: false` are unchanged,
so every PR still requires a manual merge.

- **`packages/system/bucket/images/s3manager/Dockerfile`** — pin
floating tags to concrete versions so digest tracking doesn't churn
endlessly: `golang:1` → `golang:1.26`, `alpine:latest` → `alpine:3.24`.

### Notes

- The bulk digest pin of the 27 images is left to Renovate's first run
(one grouped PR) rather than hand-edited here, keeping this PR to the
config + tag-hygiene prerequisites.
- Expected effect once the follow-up pin PR merges: Pinned-Dependencies
**1 → ~7–8**. The remaining `downloadThenRun` / pip / npm items cap it
below 10.

### Release note

```release-note
ci(renovate): enable Renovate digest pinning for Dockerfile base images and GitHub Actions to improve the OpenSSF Scorecard Pinned-Dependencies score
```


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Chores**
* Improved automated dependency management for container images and
GitHub Actions, including clearer update grouping and digest pinning.
* Updated container base images to newer, fixed versions for more
predictable builds and deployments.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
pull Bot pushed a commit to medampudi/cozystack that referenced this pull request Jun 25, 2026
Enable Renovate's dockerfile and github-actions managers with
pinDigests so OpenSSF Scorecard Pinned-Dependencies (currently 1/10)
can pass and supply-chain refs stay reproducible.

PR cozystack#2849 SHA-pinned actions in 13 workflows, but the check also scores
container base images -- 58 unpinned FROM lines across 27 distinct
images -- and left build-main.yaml / release-e2e.yaml unpinned.
Nothing kept the new action SHAs updated either, so they would
silently stop receiving security patches.

Enabling the managers lets Renovate open the initial pin PR (and pin
the two missed workflows) and keep digests fresh thereafter.
Digest-only refreshes and the initial pin are batched into a single
weekly PR to avoid a trickle of rebuild-triggering PRs on the shared
CI runner; real tag bumps stay individual for review.

Also pin s3manager's floating tags (golang:1, alpine:latest) to
concrete versions first, otherwise digest tracking churns endlessly.

Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Myasnikov Daniil <myasnikovdaniil2001@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/uncategorized PR auto-labeler could not map title scope to a known area/*; please review size/L This PR changes 100-499 lines, ignoring generated files

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants