Skip to content

ci: automate GitHub Actions freshness audits#66

Merged
Mehdi-Bl merged 3 commits into
mainfrom
chore/actions-freshness-automation
Feb 11, 2026
Merged

ci: automate GitHub Actions freshness audits#66
Mehdi-Bl merged 3 commits into
mainfrom
chore/actions-freshness-automation

Conversation

@Mehdi-Bl

@Mehdi-Bl Mehdi-Bl commented Feb 11, 2026

Copy link
Copy Markdown
Contributor

Summary

  • switch Dependabot GitHub Actions updates from weekly to daily cadence
  • add a scheduled Actions freshness audit workflow that scans pinned action SHAs daily
  • add an audit script that generates markdown/json reports and auto-manages a tracker issue when pins are stale
  • add unit tests for action reference parsing and report rendering

Validation

  • npm run lint
  • npm test -- --runInBand
  • npm run lint:tests
  • npm test -- --runInBand tests/unit/scripts/actions-freshness.test.js
  • node scripts/audit-actions-freshness.js --report /tmp/actions-freshness-report.md --json /tmp/actions-freshness-report.json

Summary by Sourcery

Automate auditing of pinned GitHub Actions references and tighten CI dependency freshness.

New Features:

  • Add a scheduled GitHub Actions workflow that audits pinned action references, generates freshness reports, and uploads them as artifacts.
  • Introduce a Node.js audit script and helper library to scan workflow files, detect stale or unpinned GitHub Actions, and produce JSON/markdown reports, optionally managing a tracker issue.
  • Expose an npm script to run the GitHub Actions freshness audit locally.

Enhancements:

  • Increase Dependabot update frequency for GitHub Actions from weekly to daily to keep CI dependencies more up to date.

Tests:

  • Add unit tests for parsing workflow action references, classifying pinned vs unpinned references, and rendering the markdown freshness report.

Summary by CodeRabbit

  • Chores

    • Dependabot updates now run daily for more frequent dependency checks.
  • New Features

    • Added a daily GitHub Actions audit that scans workflows for stale or unpinned action references and produces human-readable and machine-readable reports.
    • Added a convenience script to run the actions-freshness audit locally or in CI; audit can optionally manage a tracking issue and fail the run on stale findings.
  • Tests

    • Added unit tests for the actions-freshness reporting and parsing behavior.

@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, you can upgrade your account or add credits to your account and enable them for code reviews in your settings.

@sourcery-ai

sourcery-ai Bot commented Feb 11, 2026

Copy link
Copy Markdown

Reviewer's Guide

Automates daily GitHub Actions freshness auditing by adding a Node-based audit script, a scheduled workflow that runs it and manages a tracker issue, increasing Dependabot GitHub Actions update cadence, and wiring in tests and an npm script for local/CI use.

Sequence diagram for the scheduled Actions freshness audit

sequenceDiagram
  actor GitHubScheduler
  participant ActionsFreshnessWorkflow
  participant CheckoutStep
  participant SetupNodeStep
  participant AuditStep
  participant AuditScript
  participant GitHubAPI
  participant GitHubIssues
  participant UploadArtifactStep

  GitHubScheduler ->> ActionsFreshnessWorkflow: Trigger on cron or workflow_dispatch

  ActionsFreshnessWorkflow ->> CheckoutStep: Run actions/checkout@<pinned_sha>
  CheckoutStep -->> ActionsFreshnessWorkflow: Repository checked out

  ActionsFreshnessWorkflow ->> SetupNodeStep: Run actions/setup-node@<pinned_sha>
  SetupNodeStep -->> ActionsFreshnessWorkflow: Node.js 20 available

  ActionsFreshnessWorkflow ->> AuditStep: Run node scripts/audit-actions-freshness.js --report ... --json ... --manage-issue
  AuditStep ->> AuditScript: Start script with CLI options

  AuditScript ->> AuditScript: parseArguments
  AuditScript ->> AuditScript: readWorkflowFiles(.github/workflows)
  AuditScript ->> AuditScript: collectWorkflowReferences / splitReferencesByPinning

  AuditScript ->> GitHubAPI: Resolve latest versions for pinned action repositories
  GitHubAPI -->> AuditScript: Latest tags and SHAs or errors

  AuditScript ->> AuditScript: collectStaleReferences
  AuditScript ->> AuditScript: buildJsonReport / buildMarkdownReport
  AuditScript ->> AuditScript: writeReportFiles (md + json)
  AuditScript ->> AuditScript: writeStepSummary (GITHUB_STEP_SUMMARY)

  alt manage-issue enabled and token available
    AuditScript ->> GitHubIssues: Find existing tracking issue via GitHub API
    opt staleCount > 0
      AuditScript ->> GitHubIssues: Create or update tracking issue with report
    end
    opt staleCount == 0 and tracking issue exists
      AuditScript ->> GitHubIssues: Comment and close tracking issue
    end
  end

  AuditScript -->> AuditStep: Exit (possibly with nonzero code if stale and failOnStale)
  AuditStep -->> ActionsFreshnessWorkflow: Script completed

  ActionsFreshnessWorkflow ->> UploadArtifactStep: Upload actions-freshness-report.md/json
  UploadArtifactStep -->> ActionsFreshnessWorkflow: Artifact stored
Loading

File-Level Changes

Change Details Files
Increase Dependabot GitHub Actions update frequency to daily.
  • Change the github-actions update schedule interval from weekly to daily.
  • Remove the explicit weekday setting while keeping existing time and timezone configuration.
.github/dependabot.yml
Expose an npm script to run the Actions freshness audit locally or in CI.
  • Add a security:actions-freshness npm script that runs the audit-actions-freshness Node script with markdown and JSON report outputs in the repo root.
package.json
Introduce a Node CLI to scan workflow files for GitHub Action references, resolve latest versions via GitHub API, generate reports, and optionally manage a tracking issue.
  • Implement argument parsing for report paths, workflow directory, failing-on-stale, and issue management options.
  • Read all YAML workflows from .github/workflows, collect uses: references, and distinguish pinned (full SHA) from unpinned references.
  • Call the GitHub REST API (releases/tags/commits) with an optional token to resolve the latest tag and SHA per action repository, handling 404s and collecting resolution errors.
  • Compare pinned SHAs to latest SHAs to identify stale references and assemble a structured JSON report, including counts, stale/unpinned lists, and resolution error metadata.
  • Generate markdown and JSON reports, write them to disk (ensuring parent directories), and append markdown to GITHUB_STEP_SUMMARY when available.
  • Support optional issue management by discovering/creating/updating/closing a single tracking issue marked with a hidden HTML marker, and updating its body with the latest report.
  • Exit non‑zero when fail-on-stale is set and stale references are present, while logging paths and counts for observability.
  • Use a small internal HTTP helper around fetch for GitHub API calls with proper headers and error handling.
scripts/audit-actions-freshness.js
Add reusable helpers for parsing workflow action references and rendering the markdown report.
  • Implement regex-based parsing of uses: lines, including stripping YAML quoting, skipping local or docker:// actions, and extracting owner/repo/subpath@ref along with location metadata.
  • Classify references into pinned versus unpinned based on a full-length SHA predicate and provide helpers to split collections accordingly.
  • Add utilities for short SHA formatting, deterministic sorting by workflow path and line number, and markdown table generation for stale, unpinned, and error sections.
  • Export patterns and helper functions for use by the CLI and tests.
scripts/lib/actions-freshness.js
Cover actions freshness helpers with unit tests.
  • Test that parseWorkflowContent correctly extracts action references, pinning state, repository keys, and line numbers while ignoring local and docker actions.
  • Test that collectWorkflowReferences and splitReferencesByPinning correctly aggregate references across multiple workflows and separate pinned vs unpinned.
  • Test that buildMarkdownReport renders expected sections and content for stale and unpinned references.
tests/unit/scripts/actions-freshness.test.js
Add a scheduled GitHub Actions workflow to run the freshness audit daily and publish artifacts.
  • Create a workflow that runs daily on a cron schedule and via manual dispatch with contents read and issues write permissions.
  • Pin checkout, setup-node, and upload-artifact actions to specific SHAs in the new workflow.
  • Run the audit-actions-freshness script with issue management enabled, wiring in GITHUB_TOKEN from secrets, and always upload the generated markdown/JSON report as an artifact with retention configuration and if-no-files-found behavior.
.github/workflows/actions-freshness.yml

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@qodo-free-for-open-source-projects

Copy link
Copy Markdown

Review Summary by Qodo

Automate GitHub Actions freshness audits with daily scanning

✨ Enhancement 🧪 Tests

Grey Divider

Walkthroughs

Description
• Add automated GitHub Actions freshness audit script with SHA pinning detection
• Create scheduled workflow to scan pinned action references daily
• Implement GitHub issue tracking for stale action references
• Add comprehensive unit tests for action parsing and report generation
• Switch Dependabot GitHub Actions updates from weekly to daily cadence
Diagram
flowchart LR
  A["Workflow Files"] -->|"Parse uses statements"| B["Action References"]
  B -->|"Classify by pinning"| C["Pinned vs Unpinned"]
  C -->|"Resolve latest versions"| D["GitHub API"]
  D -->|"Compare SHAs"| E["Stale References"]
  E -->|"Generate reports"| F["Markdown & JSON"]
  F -->|"Manage tracking"| G["GitHub Issues"]
Loading

Grey Divider

File Changes

1. scripts/audit-actions-freshness.js ✨ Enhancement +536/-0

Main audit orchestration and GitHub integration

• Main audit script that orchestrates workflow scanning and reporting
• Parses command-line arguments for report paths and issue management options
• Resolves latest action versions via GitHub API with error handling
• Manages tracking issues by creating, updating, or closing based on stale reference count
• Writes markdown and JSON reports, and appends to GitHub step summary

scripts/audit-actions-freshness.js


2. scripts/lib/actions-freshness.js ✨ Enhancement +191/-0

Action reference parsing and report generation

• Parses workflow YAML files to extract action references using regex patterns
• Detects full-length SHA pinning and filters out local/docker actions
• Splits references into pinned and unpinned categories
• Generates markdown reports with stale, unpinned, and error sections
• Exports utility functions for workflow analysis and report rendering

scripts/lib/actions-freshness.js


3. tests/unit/scripts/actions-freshness.test.js 🧪 Tests +106/-0

Unit tests for action freshness helpers

• Tests workflow content parsing with various action reference formats
• Validates pinning state detection and filtering of local/docker actions
• Tests reference classification across multiple workflow files
• Verifies markdown report rendering for stale and unpinned sections

tests/unit/scripts/actions-freshness.test.js


View more (3)
4. .github/workflows/actions-freshness.yml ⚙️ Configuration changes +50/-0

Scheduled GitHub Actions freshness audit workflow

• New scheduled workflow triggered daily at 04:20 UTC and on manual dispatch
• Checks out repository and sets up Node.js environment
• Runs audit script with issue management enabled
• Uploads markdown and JSON reports as artifacts with 14-day retention
• Requires contents read and issues write permissions

.github/workflows/actions-freshness.yml


5. .github/dependabot.yml ⚙️ Configuration changes +1/-2

Increase GitHub Actions update frequency

• Changes GitHub Actions Dependabot update schedule from weekly to daily
• Removes day-of-week specification, keeping time at 03:15 UTC

.github/dependabot.yml


6. package.json ⚙️ Configuration changes +1/-0

Add npm script for manual audits

• Adds new npm script security:actions-freshness for manual audit execution
• Script runs audit with markdown and JSON report output

package.json


Grey Divider

Qodo Logo

@gemini-code-assist

Copy link
Copy Markdown

Summary of Changes

Hello @Mehdi-Bl, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly enhances the repository's continuous integration security and maintenance posture. By increasing the frequency of Dependabot updates for GitHub Actions and introducing an automated freshness audit, it ensures that all pinned actions are regularly checked against their latest available versions. This proactive approach helps prevent the use of outdated or vulnerable action versions, streamlining the process of keeping CI workflows secure and efficient.

Highlights

  • Dependabot Cadence: Changed Dependabot's GitHub Actions update schedule from weekly to daily, ensuring more frequent checks for action updates.
  • Freshness Audit Workflow: Introduced a new scheduled workflow to daily audit pinned GitHub Action SHAs, identifying outdated action versions.
  • Audit Script: Developed a Node.js script that generates markdown and JSON reports on action freshness and automatically manages a tracker issue for stale pins.
  • Unit Tests: Added comprehensive unit tests for the action reference parsing and report rendering logic, ensuring reliability of the new audit system.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Changelog
  • .github/dependabot.yml
    • Updated the github-actions package ecosystem schedule from weekly to daily.
  • package.json
    • Added a new security:actions-freshness script command to trigger the audit.
  • scripts/audit-actions-freshness.js
    • Added a new Node.js script to perform GitHub Actions freshness audits.
    • Implemented argument parsing for report paths, workflow directories, and issue management.
    • Included functions for reading workflow files, making GitHub API requests, resolving latest action versions, and managing a tracking issue.
    • Integrated report generation (JSON and Markdown) and step summary writing.
  • scripts/lib/actions-freshness.js
    • Added a new utility library for parsing GitHub Actions workflow content.
    • Provided functions to normalize reference values, check for full-length SHAs, collect and split references by pinning status, and build markdown reports.
  • tests/unit/scripts/actions-freshness.test.js
    • Added unit tests for parseWorkflowContent, collectWorkflowReferences, splitReferencesByPinning, and buildMarkdownReport functions.
Ignored Files
  • Ignored by pattern: .github/workflows/** (1)
    • .github/workflows/actions-freshness.yml
Activity
  • No human activity (comments, reviews, approvals) has been recorded on this pull request yet.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@coderabbitai

coderabbitai Bot commented Feb 11, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

Adds an actions-freshness auditing system: new scheduled workflow and CLI script to scan workflows for action references, resolve latest versions via GitHub API, generate reports, manage a tracking issue, plus parsing utilities, tests, and a Dependabot schedule change to daily.

Changes

Cohort / File(s) Summary
GitHub config
\.github/dependabot.yml, \.github/workflows/actions-freshness.yml
Dependabot schedule changed from weekly to daily; new actions-freshness workflow added (daily + manual trigger), with artifact upload, permissions, and concurrency.
Audit CLI
scripts/audit-actions-freshness.js
New CLI that discovers workflows, extracts action refs, queries GitHub REST API for latest tags/SHAs, detects stale pinned refs, writes JSON/MD reports, optionally manages a tracking issue, and supports flags for failure/issue management.
Parser library
scripts/lib/actions-freshness.js
New library to parse workflow content, extract/classify action references (pinned/unpinned), helpers for SHA handling, and Markdown report builder; exports parsing and report functions and regex constants.
Package script
package.json
Adds security:actions-freshness npm script to run the audit script and output MD/JSON reports.
Tests
tests/unit/scripts/actions-freshness.test.js
New unit tests covering parsing, reference collection/splitting, and Markdown report rendering.

Sequence Diagram

sequenceDiagram
    participant GHA as GitHub Actions
    participant Audit as Audit CLI\n(scripts/audit-actions-freshness.js)
    participant Parser as Parser Lib\n(scripts/lib/actions-freshness.js)
    participant GHAPI as GitHub REST API
    participant Artifacts as Artifacts & Issue

    GHA->>Audit: trigger (daily or manual)
    Audit->>Parser: read & parse workflows
    Parser-->>Audit: list of action references (pinned/unpinned)
    Audit->>GHAPI: request latest release/tag/commit for each repo
    GHAPI-->>Audit: latest SHAs/tags or errors
    Audit->>Parser: generate Markdown + JSON report
    Parser-->>Audit: formatted report content
    Audit->>Artifacts: upload reports & update/create/close tracking issue
    Artifacts-->>GHA: artifacts uploaded / issue updated
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐰 I hop through workflows, sniff each pin,
I check the tags and latest kin,
A markdown note, a JSON trace,
I tidy trackers, close the case,
Fresh actions daily — nibble, then grin. 🥕

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely summarizes the main change: automating GitHub Actions freshness audits through scheduled workflows and audit scripts.

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

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch chore/actions-freshness-automation

No actionable comments were generated in the recent review. 🎉

🧹 Recent nitpick comments
scripts/lib/actions-freshness.js (2)

179-193: Pipe characters in error.message can break the Markdown table.

If a resolution error message contains | or newline characters, the rendered table row will be malformed. Consider escaping pipes in dynamic values before interpolating them into table cells.

Proposed fix

Add a small helper and use it for all dynamic table-cell values:

+function escapeMarkdownTableCell(value) {
+  return String(value).replace(/\|/g, '\\|').replace(/\n/g, ' ');
+}
+
 function buildMarkdownReport(report) {

Then in the error rows:

-      lines.push(`| ${error.repository} | ${error.message} |`);
+      lines.push(`| ${escapeMarkdownTableCell(error.repository)} | ${escapeMarkdownTableCell(error.message)} |`);

Apply the same treatment to the stale and unpinned table rows for consistency.


195-198: "All current" message is shown even when unpinned references exist.

The condition on Line 195 checks staleCount === 0 && resolutionErrors.length === 0 but does not account for unpinnedCount > 0. The message says "All pinned GitHub Actions references are current" which is technically accurate, but could be misleading when there are unpinned references listed just above. Consider whether the wording or condition should be adjusted.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Hey - I've left some high level feedback:

  • The readWorkflowFiles helper only scans files directly under .github/workflows and will miss any workflows placed in subdirectories there; consider adding a recursive scan if nested workflows are or might be used.
  • resolveRepositoryVersions performs GitHub API calls sequentially per repository, which will be slow and more prone to rate limits as the number of pinned actions grows; consider resolving in parallel with a small concurrency limit.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The `readWorkflowFiles` helper only scans files directly under `.github/workflows` and will miss any workflows placed in subdirectories there; consider adding a recursive scan if nested workflows are or might be used.
- `resolveRepositoryVersions` performs GitHub API calls sequentially per repository, which will be slow and more prone to rate limits as the number of pinned actions grows; consider resolving in parallel with a small concurrency limit.

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

@github-actions

github-actions Bot commented Feb 11, 2026

Copy link
Copy Markdown

Dependency Review

✅ No vulnerabilities or license issues found.

Scanned Files

None

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a robust mechanism for auditing GitHub Actions freshness, including a new script to scan for stale pinned actions, generate reports, and automatically manage a tracking issue, alongside increasing the Dependabot check frequency for actions to daily. However, the new script contains path traversal vulnerabilities in how it handles command-line arguments for directories and file paths, which could allow an attacker to read or write arbitrary files in the CI environment. It is recommended to add path validation to restrict file operations to the intended workspace. Additionally, consider refactoring the argument parsing logic for improved readability.

}

function readWorkflowFiles(workflowDirectory) {
const directoryPath = path.resolve(process.cwd(), workflowDirectory);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

security-medium medium

The workflowDirectory parameter is taken directly from command-line arguments and used in path.resolve(process.cwd(), workflowDirectory) without any validation or sanitization. This allows an attacker who can influence the script's arguments to read arbitrary directories and files on the system by using path traversal sequences like ... In a CI environment, this could be used to exfiltrate sensitive files or environment configurations.

  const directoryPath = path.resolve(process.cwd(), workflowDirectory);
  if (!directoryPath.startsWith(process.cwd())) {
    throw new Error(`Invalid workflow directory: ${workflowDirectory}`);
  }

}

function ensureParentDirectory(filePath) {
const absolutePath = path.resolve(process.cwd(), filePath);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

security-medium medium

The filePath parameter (derived from --report and --json arguments) is used in path.resolve(process.cwd(), filePath) without validation. This allows an attacker to specify arbitrary file paths for writing the audit reports, potentially overwriting sensitive files or creating files in unintended locations.

  const absolutePath = path.resolve(process.cwd(), filePath);
  if (!absolutePath.startsWith(process.cwd())) {
    throw new Error(`Invalid file path: ${filePath}`);
  }

Comment on lines +32 to +68
const argument = argv[index];

if (argument === '--report') {
options.reportPath = argv[index + 1];
index += 1;
continue;
}

if (argument === '--json') {
options.jsonPath = argv[index + 1];
index += 1;
continue;
}

if (argument === '--workflow-dir') {
options.workflowDirectory = argv[index + 1];
index += 1;
continue;
}

if (argument === '--issue-title') {
options.issueTitle = argv[index + 1];
index += 1;
continue;
}

if (argument === '--fail-on-stale') {
options.failOnStale = true;
continue;
}

if (argument === '--manage-issue') {
options.manageIssue = true;
continue;
}

throw new Error(`Unsupported argument: ${argument}`);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

For better readability and maintainability, you could refactor this series of if statements into a switch statement. This would make the argument parsing logic cleaner and easier to extend in the future.

    const argument = argv[index];

    switch (argument) {
      case '--report':
        options.reportPath = argv[index + 1];
        index += 1;
        break;
      case '--json':
        options.jsonPath = argv[index + 1];
        index += 1;
        break;
      case '--workflow-dir':
        options.workflowDirectory = argv[index + 1];
        index += 1;
        break;
      case '--issue-title':
        options.issueTitle = argv[index + 1];
        index += 1;
        break;
      case '--fail-on-stale':
        options.failOnStale = true;
        break;
      case '--manage-issue':
        options.manageIssue = true;
        break;
      default:
        throw new Error(`Unsupported argument: ${argument}`);
    }

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In @.github/workflows/actions-freshness.yml:
- Around line 8-10: Move the top-level permissions block (permissions: contents:
read, issues: write) down into the specific job that needs issue-write access
(the audit job); remove the workflow-level permissions and add a job-level
permissions block inside the audit job with contents: read and issues: write so
only the audit job has write access to issues, keeping least-privilege.

In `@scripts/audit-actions-freshness.js`:
- Around line 143-154: The githubRequest function currently always includes an
Authorization header even when token is an empty string; change headers
construction in githubRequest so Authorization is added only when token is
truthy (e.g., conditionally set or spread a mini-object when token exists) to
avoid sending Authorization: '' during unauthenticated runs; locate the
githubRequest function and update its headers logic to omit the Authorization
key when token is falsy.
🧹 Nitpick comments (4)
.github/workflows/actions-freshness.yml (1)

41-50: Consider warn instead of error for if-no-files-found.

With if: always(), the upload step runs even if the audit step fails before writing any report files. In that scenario, if-no-files-found: error will cause the workflow to fail with a confusing artifact-upload error, masking the real failure. Using warn would let the actual script error surface clearly.

Proposed change
-          if-no-files-found: error
+          if-no-files-found: warn
scripts/audit-actions-freshness.js (2)

31-68: No bounds check when consuming flag values.

If a flag like --report is the last argument, argv[index + 1] is undefined. The downstream validation (lines 71–85) catches this because undefined is falsy, but the error message ("The --report option requires a value") is misleading if the user actually passed --report without a value vs. not passing it at all. Minor UX nit — not a blocker.


247-282: Sequential API resolution — acceptable but worth noting.

Each repository's version is resolved sequentially. For a handful of distinct action repos this is fine given the 15-minute timeout, but if the set grows, consider parallelizing with Promise.all (or a bounded concurrency helper) to reduce wall-clock time.

scripts/lib/actions-freshness.js (1)

164-169: Pipe characters in error messages could break the markdown table.

If an error.message ever contains a | character, the markdown table row will be malformed. Consider escaping pipe characters in the message.

Proposed fix
+  function escapeMarkdownPipe(value) {
+    return typeof value === 'string' ? value.replace(/\|/g, '\\|') : value;
+  }
+
     for (const error of errorsSorted) {
-      lines.push(`| ${error.repository} | ${error.message} |`);
+      lines.push(`| ${error.repository} | ${escapeMarkdownPipe(error.message)} |`);
     }

Comment thread .github/workflows/actions-freshness.yml Outdated
Comment thread scripts/audit-actions-freshness.js
@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Feb 11, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (4) 📘 Rule violations (2) 📎 Requirement gaps (0)

Grey Divider


Action required

✅ 1. resolutionErrors ignored for closing 📘 Rule violation ⛯ Reliability
Description
The tracker issue is closed whenever staleCount === 0, even if there were GitHub API resolution
errors, which can incorrectly report a clean audit and suppress follow-up. Resolution errors are
also not used to influence the script outcome (exit code), weakening robustness for failure/edge
cases.
Code

scripts/audit-actions-freshness.js[R407-420]

+  if (staleCount === 0) {
+    if (!trackingIssue) {
+      return;
+    }
+
+    await closeTrackingIssue({
+      owner,
+      repository,
+      issueNumber: trackingIssue.number,
+      token,
+    });
+    console.log(`[actions-freshness] Closed issue #${trackingIssue.number}`);
+    return;
+  }
Evidence
PR Compliance ID 3 requires handling failure points and edge cases with meaningful outcomes; here,
the logic treats staleCount === 0 as success and closes the tracking issue without considering
resolutionErrors, and the script only sets a non-zero exit code based on staleness (not resolution
failures).

Rule 3: Generic: Robust Error Handling and Edge Case Management
scripts/audit-actions-freshness.js[407-420]
scripts/audit-actions-freshness.js[476-490]
scripts/audit-actions-freshness.js[528-530]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The audit script can report a &amp;amp;amp;quot;clean&amp;amp;amp;quot; state and close the tracker issue when `staleCount === 0` even if GitHub API resolution failed for one or more action repositories (`resolutionErrors` is non-empty). The run also only sets a failure exit code based on staleness, not on resolution failures.
## Issue Context
`resolutionErrors` indicates the audit could not verify freshness for some pinned actions. Treating this as success can hide failures and break the expectation of robust error/edge-case handling.
## Fix Focus Areas
- scripts/audit-actions-freshness.js[391-531]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. tests/catalog.md missing actions-freshness 📘 Rule violation ⛯ Reliability
Description
A new unit test suite was added for the actions freshness audit helpers, but tests/catalog.md was
not updated to include this new test target/use case mapping. This leaves the test catalog stale
relative to the changes.
Code

tests/unit/scripts/actions-freshness.test.js[R1-6]

+const {
+  buildMarkdownReport,
+  collectWorkflowReferences,
+  parseWorkflowContent,
+  splitReferencesByPinning,
+} = require('../../../scripts/lib/actions-freshness');
Evidence
PR Compliance ID 9 requires test coverage alignment with tests/catalog.md and updating the catalog
when new targets/use cases are introduced. The PR adds
tests/unit/scripts/actions-freshness.test.js, but the Unit Tests table in tests/catalog.md does
not list this file/target.

AGENTS.md
tests/unit/scripts/actions-freshness.test.js[1-6]
tests/catalog.md[19-40]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`tests/unit/scripts/actions-freshness.test.js` was added, but the Unit Tests section of `tests/catalog.md` was not updated to reflect this new test target/use case mapping.
## Issue Context
The test catalog is the authoritative reference for what should be tested; adding new test targets should be reflected in the catalog to avoid staleness.
## Fix Focus Areas
- tests/catalog.md[19-40]
- tests/unit/scripts/actions-freshness.test.js[1-106]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

✅ 3. GitHub API headers brittle 🐞 Bug ⛯ Reliability
Description
The GitHub API helper always sends an Authorization header (empty when no token) and sends JSON
bodies without setting Content-Type. This can degrade to unauthenticated rate limits for
local/manual runs and is brittle for POST/PATCH issue-management calls if a stricter server/proxy
requires the JSON content type.
Code

scripts/audit-actions-freshness.js[R143-154]

+async function githubRequest({ endpoint, token, method = 'GET', body = null }) {
+  const url = `https://api.github.com${endpoint}`;
+  const response = await fetch(url, {
+    method,
+    headers: {
+      Accept: 'application/vnd.github+json',
+      Authorization: token ? `Bearer ${token}` : '',
+      'User-Agent': 'actions-freshness-audit',
+      'X-GitHub-Api-Version': '2022-11-28',
+    },
+    body: body ? JSON.stringify(body) : undefined,
+  });
Evidence
githubRequest unconditionally sets an Authorization header to an empty string when token is not
provided, and stringifies JSON bodies without an explicit Content-Type; the same helper is used for
POST/PATCH calls to create/update/close issues and comments.

scripts/audit-actions-freshness.js[143-154]
scripts/audit-actions-freshness.js[470-479]
scripts/audit-actions-freshness.js[373-444]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`githubRequest()` always sets an `Authorization` header (even when no token is available) and sends JSON bodies without an explicit `Content-Type`. This is brittle for POST/PATCH calls and can also unintentionally downgrade requests to unauthenticated behavior in local/manual runs.
### Issue Context
This helper is used for both read-only API calls (releases/tags/commits) and for issue management (POST/PATCH) when `--manage-issue` is enabled.
### Fix Focus Areas
- scripts/audit-actions-freshness.js[143-154]
- scripts/audit-actions-freshness.js[373-444]
### Suggested approach
- Construct `headers` as an object, then:
- `if (token) headers.Authorization = \`Bearer ${token}\`;`
- `if (body) headers[&amp;amp;amp;#x27;Content-Type&amp;amp;amp;#x27;] = &amp;amp;amp;#x27;application/json&amp;amp;amp;#x27;;`
- Keep the existing Accept/User-Agent/API-Version headers.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


4. Tracker search page-capped 🐞 Bug ⛯ Reliability
Description
Tracking-issue discovery only scans up to 5 pages (500) of open issues. In repositories with more
open issues, the script may miss the existing tracker and create duplicates.
Code

scripts/audit-actions-freshness.js[R342-346]

+async function findTrackingIssue({ owner, repository, token, issueTitle }) {
+  for (let page = 1; page <= 5; page += 1) {
+    const issues = await githubRequest({
+      endpoint: `/repos/${encodeURIComponent(owner)}/${encodeURIComponent(repository)}/issues?state=open&per_page=100&page=${page}`,
+      token,
Evidence
findTrackingIssue hard-codes an upper bound of 5 pages and uses the list-issues endpoint; if the
tracker exists beyond the first 500 open issues, it will not be found and upsert will fall back to
creating a new issue.

scripts/audit-actions-freshness.js[342-370]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`findTrackingIssue()` only checks the first 5 pages of open issues. On busy repositories this can miss the existing tracking issue and create duplicates.
### Issue Context
The tracking issue is identified by matching `issue.title === issueTitle` and a body marker (`TRACKING_ISSUE_MARKER`).
### Fix Focus Areas
- scripts/audit-actions-freshness.js[342-370]
### Suggested approach
Option A (simple): keep paging until `issues.length === 0` (and keep the existing early-break on `&amp;amp;amp;lt; 100`). Consider adding a safety cap but much higher / configurable.
Option B (better): use the Search API to directly find issues containing the marker and title, e.g. `GET /search/issues?q=repo:OWNER/REPO+is:issue+is:open+in:title:&amp;amp;amp;quot;...&amp;amp;amp;quot;+&amp;amp;amp;quot;actions-freshness-tracker&amp;amp;amp;quot;` and then validate marker in body.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Advisory comments

✅ 5. Unescaped error Markdown 🐞 Bug ✓ Correctness
Description
Resolution error messages are interpolated into a Markdown table without escaping. If an upstream
error message contains | or newlines, the report table can render incorrectly in the artifact,
step summary, and tracking issue.
Code

scripts/lib/actions-freshness.js[R158-169]

+  if (report.resolutionErrors.length > 0) {
+    lines.push('## Resolution errors');
+    lines.push('');
+    lines.push('| Action repository | Error |');
+    lines.push('| --- | --- |');
+
+    const errorsSorted = [...report.resolutionErrors].sort((left, right) =>
+      left.repository.localeCompare(right.repository)
+    );
+    for (const error of errorsSorted) {
+      lines.push(`| ${error.repository} | ${error.message} |`);
+    }
Evidence
The report renders error.message directly into a Markdown table cell, and those error messages
come from caught exceptions (including GitHub API errors) without sanitization.

scripts/audit-actions-freshness.js[247-278]
scripts/lib/actions-freshness.js[158-169]
scripts/lib/actions-freshness.js[1-4]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The resolution errors section injects `error.message` directly into a Markdown table row. Pipes (`|`) or newlines in the message can break the table formatting, reducing report readability.
### Issue Context
Action names/paths are regex constrained, but `error.message` can originate from thrown exceptions (including GitHub API responses).
### Fix Focus Areas
- scripts/lib/actions-freshness.js[158-169]
### Suggested approach
- Add a small helper like `escapeMarkdownTableCell(value)` that:
- converts to string
- replaces `\r?\n` with spaces
- replaces `|` with `\\|`
- Apply it to `error.message` (and optionally other table cells) before interpolating.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


6. Dependabot runs daily 🐞 Bug ⛯ Reliability
Description
Dependabot for GitHub Actions is switched from weekly to daily, which can increase PR churn and
notification load. Consider whether this cadence aligns with maintainer capacity given the open PR
limit of 10.
Code

.github/dependabot.yml[R15-21]

- package-ecosystem: github-actions
 directory: "/"
 schedule:
-      interval: weekly
-      day: monday
+      interval: daily
   time: "03:15"
   timezone: UTC
 open-pull-requests-limit: 10
Evidence
The configuration now schedules github-actions updates daily at 03:15 UTC with up to 10 open PRs,
which can create sustained PR volume if updates are frequent.

.github/dependabot.yml[15-21]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Daily Dependabot runs for GitHub Actions may create unnecessary PR churn.
### Issue Context
Config currently allows up to 10 open PRs.
### Fix Focus Areas
- .github/dependabot.yml[15-21]
### Suggested approach
- If needed, change `interval: daily` back to `weekly`, or add grouping/ignore rules for low-risk updates.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

ⓘ The new review experience is currently in Beta. Learn more

Grey Divider

Qodo Logo

Comment thread scripts/audit-actions-freshness.js Outdated
Comment on lines +407 to +420
if (staleCount === 0) {
if (!trackingIssue) {
return;
}

await closeTrackingIssue({
owner,
repository,
issueNumber: trackingIssue.number,
token,
});
console.log(`[actions-freshness] Closed issue #${trackingIssue.number}`);
return;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Action required

1. resolutionerrors ignored for closing 📘 Rule violation ⛯ Reliability

The tracker issue is closed whenever staleCount === 0, even if there were GitHub API resolution
errors, which can incorrectly report a clean audit and suppress follow-up. Resolution errors are
also not used to influence the script outcome (exit code), weakening robustness for failure/edge
cases.
Agent Prompt
## Issue description
The audit script can report a "clean" state and close the tracker issue when `staleCount === 0` even if GitHub API resolution failed for one or more action repositories (`resolutionErrors` is non-empty). The run also only sets a failure exit code based on staleness, not on resolution failures.

## Issue Context
`resolutionErrors` indicates the audit could not verify freshness for some pinned actions. Treating this as success can hide failures and break the expectation of robust error/edge-case handling.

## Fix Focus Areas
- scripts/audit-actions-freshness.js[391-531]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +1 to +6
const {
buildMarkdownReport,
collectWorkflowReferences,
parseWorkflowContent,
splitReferencesByPinning,
} = require('../../../scripts/lib/actions-freshness');

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Action required

2. tests/catalog.md missing actions-freshness 📘 Rule violation ⛯ Reliability

A new unit test suite was added for the actions freshness audit helpers, but tests/catalog.md was
not updated to include this new test target/use case mapping. This leaves the test catalog stale
relative to the changes.
Agent Prompt
## Issue description
`tests/unit/scripts/actions-freshness.test.js` was added, but the Unit Tests section of `tests/catalog.md` was not updated to reflect this new test target/use case mapping.

## Issue Context
The test catalog is the authoritative reference for what should be tested; adding new test targets should be reflected in the catalog to avoid staleness.

## Fix Focus Areas
- tests/catalog.md[19-40]
- tests/unit/scripts/actions-freshness.test.js[1-106]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

@Mehdi-Bl

Copy link
Copy Markdown
Contributor Author

Quality gate triage summary:

  • Sonar failure is from one Security Hotspot (javascript:S5852) on scripts/lib/actions-freshness.js:1 (hotspot key AZxLUvVRJp4avvT5DeuY).
  • Context review: this regex is used only by the workflow-audit utility against repository workflow files, not untrusted runtime input from production traffic. In this repository context, this is a false-positive candidate for exploitable DoS risk.
  • If we accept that scope, the hotspot should be reviewed in Sonar and marked Safe with rationale: constrained input domain / non-runtime attack surface.

Important: this PR is still blocked by a separate valid failure in zizmor (excessive-permissions on workflow-level issues: write), so Sonar triage alone will not make it merge-ready.

@sonarqubecloud

Copy link
Copy Markdown

@Mehdi-Bl Mehdi-Bl merged commit 3e001ee into main Feb 11, 2026
17 checks passed
@Mehdi-Bl Mehdi-Bl deleted the chore/actions-freshness-automation branch February 11, 2026 18:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant