Skip to content

Conversation

@fulldecent
Copy link
Contributor

This PR adds a tool to detect outdated uses: dependency versions in this repo. (For references to actions and also references to whole workflows.)

It also provides a methodical process of how to consider and implment and document each upgrade.

Supply chain security is an important job. And this repo is the start point for supply chain security for most projects on earth.

This script helps make your job easier to benefit us all!

For example:

Screenshot 2025-12-11 at 14 43 11
  • Add TypeScript tool to check for outdated action versions
  • Handle SHA-pinned commits with version comments
  • Show latest release commit information
  • Add comprehensive UPGRADE_GUIDE.md documenting upgrade best practices

Pre-requisites

  • Prior to submitting a new workflow, please apply to join the GitHub Technology Partner Program: partner.github.com/apply. / applied

Please note that at this time we are only accepting new starter workflows for Code Scanning. Updates to existing starter workflows are fine.


Tasks

Some general notes:

  • This workflow must only use actions that are produced by GitHub, in the actions organization, or
  • This workflow must only use actions that are produced by the language or ecosystem that the workflow supports. These actions must be published to the GitHub Marketplace. We require that these actions be referenced using the full 40 character hash of the action's commit instead of a tag. Additionally, workflows must include the following comment at the top of the workflow file:
    # This workflow uses actions that are not certified by GitHub.
    # They are provided by a third-party and are governed by
    # separate terms of service, privacy policy, and support
    # documentation.
    
  • Automation and CI workflows should not send data to any 3rd party service except for the purposes of installing dependencies.
  • Automation and CI workflows cannot be dependent on a paid service or product.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR introduces a TypeScript-based tool to detect outdated action versions in GitHub Actions workflows, along with comprehensive documentation for upgrading actions safely. The tool scans workflow files to identify actions that have newer releases available and provides a systematic upgrade process to maintain supply chain security.

  • Adds automated detection of outdated action versions with support for both tag-based and SHA-pinned references
  • Includes detailed UPGRADE_GUIDE.md with best practices, version specificity patterns, and a case study of the actions/checkout v5→v6 upgrade
  • Implements GitHub API integration with rate limit handling and progress tracking

Reviewed changes

Copilot reviewed 5 out of 7 changed files in this pull request and generated 15 comments.

Show a summary per file
File Description
script/check-outdated-actions/package.json Defines Node.js project dependencies for TypeScript and YAML parsing
script/check-outdated-actions/package-lock.json Locks dependency versions for reproducible installations
script/check-outdated-actions/tsconfig.json Minimal TypeScript compiler configuration
script/check-outdated-actions/settings.json Configuration specifying workflow directories to scan
script/check-outdated-actions/index.ts Main TypeScript script that scans workflows, fetches release data via GitHub API, and reports outdated actions
script/check-outdated-actions/UPGRADE_GUIDE.md Comprehensive documentation covering upgrade methodology, version format patterns, breaking change analysis, and verification steps
package-lock.json Root-level lockfile initialization for the repository
Files not reviewed (1)
  • script/check-outdated-actions/package-lock.json: Language not supported
Comments suppressed due to low confidence (2)

script/check-outdated-actions/index.ts:11

  • Unused variable commitCache.
const commitCache = new Map<string, string | null>();

script/check-outdated-actions/index.ts:12

  • Unused variable releaseCache.
const releaseCache = new Map<string, { tag: string; commit: string } | null>();

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@@ -0,0 +1,239 @@
#!/usr/bin/env npx ts-node
import { promises as fs } from "fs";
import { safeLoad } from "js-yaml";
Copy link

Copilot AI Dec 11, 2025

Choose a reason for hiding this comment

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

The safeLoad function from js-yaml is deprecated. Use load function instead. Note that if updating to js-yaml v4.x, the import will also need to change to import { load } from "js-yaml".

Copilot uses AI. Check for mistakes.
const commitCache = new Map<string, string | null>();
const releaseCache = new Map<string, { tag: string; commit: string } | null>();

async function findActionsInWorkflows(folders: string[]): Promise<Map<string, string[]>> {
Copy link

Copilot AI Dec 11, 2025

Choose a reason for hiding this comment

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

The releaseCache variable is declared but never used in the code. Consider implementing caching for getLatestRelease to avoid fetching the same release information multiple times for actions from the same repository.

Suggested change
async function findActionsInWorkflows(folders: string[]): Promise<Map<string, string[]>> {

Copilot uses AI. Check for mistakes.
withFileTypes: true,
});

for (const e of dir) {
Copy link

Copilot AI Dec 11, 2025

Choose a reason for hiding this comment

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

The variable name 'e' is too generic for a directory entry. Consider using a more descriptive name like 'entry' or 'file' to improve code readability.

Copilot uses AI. Check for mistakes.
const execAsync = promisify(exec);

// Cache for API results to avoid duplicate calls
const commitCache = new Map<string, string | null>();
Copy link

Copilot AI Dec 11, 2025

Choose a reason for hiding this comment

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

The commitCache variable is declared but never used in the code. Consider removing it or implementing the caching logic to improve performance by avoiding duplicate API calls.

Suggested change
const commitCache = new Map<string, string | null>();

Copilot uses AI. Check for mistakes.
const GITHUB_TOKEN = process.env.GITHUB_TOKEN || process.env.GH_TOKEN || "";

function getAuthHeader(): string {
return GITHUB_TOKEN ? `-H "Authorization: Bearer ${GITHUB_TOKEN}"` : "";
Copy link

Copilot AI Dec 11, 2025

Choose a reason for hiding this comment

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

The GITHUB_TOKEN is being interpolated directly into a shell command string, which could lead to command injection vulnerabilities if the token contains special shell characters. Consider using a more secure approach such as passing the token through stdin or using a proper HTTP client library instead of curl.

Copilot uses AI. Check for mistakes.
Comment on lines 11 to 14
"@types/js-yaml": "^3.12.4",
"@types/node": "^14.0.1",
"ts-node": "^8.10.1",
"typescript": "^3.9.2"
Copy link

Copilot AI Dec 11, 2025

Choose a reason for hiding this comment

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

The specified dependency versions are severely outdated and contain known security vulnerabilities. Consider updating to more recent versions:

  • @types/js-yaml: ^3.12.4 is from 2019, current is ^4.0.9
  • @types/node: ^14.0.1 targets Node 14 which reached end-of-life in April 2023
  • ts-node: ^8.10.1 is from 2020, current is ^10.9.2
  • typescript: ^3.9.2 is from 2020, current is ^5.7.2

These outdated versions may have security vulnerabilities and lack modern TypeScript features.

Suggested change
"@types/js-yaml": "^3.12.4",
"@types/node": "^14.0.1",
"ts-node": "^8.10.1",
"typescript": "^3.9.2"
"@types/js-yaml": "^4.0.9",
"@types/node": "^20.9.0",
"ts-node": "^10.9.2",
"typescript": "^5.7.2"

Copilot uses AI. Check for mistakes.
"typescript": "^3.9.2"
},
"dependencies": {
"js-yaml": "^3.13.1"
Copy link

Copilot AI Dec 11, 2025

Choose a reason for hiding this comment

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

The js-yaml version ^3.13.1 has a known security vulnerability (CVE-2021-21434) related to arbitrary code execution. This version is from 2019 and should be updated to ^4.1.0 or later which addresses this vulnerability.

Suggested change
"js-yaml": "^3.13.1"
"js-yaml": "^4.1.0"

Copilot uses AI. Check for mistakes.
Comment on lines 61 to 87
function getAuthHeader(): string {
return GITHUB_TOKEN ? `-H "Authorization: Bearer ${GITHUB_TOKEN}"` : "";
}

async function getCommitForRef(owner: string, repo: string, ref: string): Promise<string | null> {
try {
const apiUrl = `https://api.github.com/repos/${owner}/${repo}/commits/${ref}`;
const authHeader = getAuthHeader();
const { stdout } = await execAsync(`curl -s -w "\n%{http_code}" ${authHeader} "${apiUrl}"`);

const lines = stdout.trim().split("\n");
const httpCode = lines[lines.length - 1];
const responseBody = lines.slice(0, -1).join("\n");

if (httpCode === "403") {
console.error(`\nGitHub API rate limit exceeded!`);
console.error(`Unauthenticated: 60 requests/hour | Authenticated: 5,000 requests/hour`);
console.error(`\nCreate a token at: https://github.com/settings/tokens/new?description=check-outdated-actions&scopes=public_repo`);
console.error(`Then set: export GITHUB_TOKEN="your_token"`);
process.exit(1);
}

if (httpCode !== "200") {
return null;
}

const data = JSON.parse(responseBody);
Copy link

Copilot AI Dec 11, 2025

Choose a reason for hiding this comment

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

Using shell commands with curl is fragile and platform-dependent. Consider using a proper HTTP client library like node-fetch or the built-in fetch API (available in Node.js 18+) for more reliable and secure API requests. This would also eliminate the need for shell escaping concerns.

Suggested change
function getAuthHeader(): string {
return GITHUB_TOKEN ? `-H "Authorization: Bearer ${GITHUB_TOKEN}"` : "";
}
async function getCommitForRef(owner: string, repo: string, ref: string): Promise<string | null> {
try {
const apiUrl = `https://api.github.com/repos/${owner}/${repo}/commits/${ref}`;
const authHeader = getAuthHeader();
const { stdout } = await execAsync(`curl -s -w "\n%{http_code}" ${authHeader} "${apiUrl}"`);
const lines = stdout.trim().split("\n");
const httpCode = lines[lines.length - 1];
const responseBody = lines.slice(0, -1).join("\n");
if (httpCode === "403") {
console.error(`\nGitHub API rate limit exceeded!`);
console.error(`Unauthenticated: 60 requests/hour | Authenticated: 5,000 requests/hour`);
console.error(`\nCreate a token at: https://github.com/settings/tokens/new?description=check-outdated-actions&scopes=public_repo`);
console.error(`Then set: export GITHUB_TOKEN="your_token"`);
process.exit(1);
}
if (httpCode !== "200") {
return null;
}
const data = JSON.parse(responseBody);
// Remove getAuthHeader, use fetch directly
async function getCommitForRef(owner: string, repo: string, ref: string): Promise<string | null> {
try {
const apiUrl = `https://api.github.com/repos/${owner}/${repo}/commits/${ref}`;
const headers: Record<string, string> = {
"Accept": "application/vnd.github.v3+json"
};
if (GITHUB_TOKEN) {
headers["Authorization"] = `Bearer ${GITHUB_TOKEN}`;
}
const response = await fetch(apiUrl, { headers });
if (response.status === 403) {
console.error(`\nGitHub API rate limit exceeded!`);
console.error(`Unauthenticated: 60 requests/hour | Authenticated: 5,000 requests/hour`);
console.error(`\nCreate a token at: https://github.com/settings/tokens/new?description=check-outdated-actions&scopes=public_repo`);
console.error(`Then set: export GITHUB_TOKEN="your_token"`);
process.exit(1);
}
if (response.status !== 200) {
return null;
}
const data = await response.json();

Copilot uses AI. Check for mistakes.
Comment on lines 46 to 47
} catch (e) {
console.error(`Error reading ${join(folder, e.name)}:`, e);
Copy link

Copilot AI Dec 11, 2025

Choose a reason for hiding this comment

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

The error is caught using the same variable name 'e' as the loop variable from the directory iteration (line 23). This shadowing could cause confusion during debugging. Use a different variable name for the caught error, such as 'error' or 'err'.

Suggested change
} catch (e) {
console.error(`Error reading ${join(folder, e.name)}:`, e);
} catch (error) {
console.error(`Error reading ${join(folder, e.name)}:`, error);

Copilot uses AI. Check for mistakes.
async function getLatestRelease(actionRef: string): Promise<{ tag: string; commit: string } | null> {
try {
// Parse the action reference (e.g., "actions/checkout@v5" or "owner/repo/.github/workflows/workflow.yml@v1")
const [fullAction, currentVersion] = actionRef.split("@");
Copy link

Copilot AI Dec 11, 2025

Choose a reason for hiding this comment

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

Unused variable currentVersion.

Suggested change
const [fullAction, currentVersion] = actionRef.split("@");
const [fullAction] = actionRef.split("@");

Copilot uses AI. Check for mistakes.
@fulldecent fulldecent force-pushed the outdated-dependencies-checker branch from 71c395c to 50b7464 Compare December 11, 2025 20:48
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