Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
version: 2

updates:
# Actions are pinned to commit hashes, which means they never move on their own —
# a pinned action is frozen at whatever it was when someone typed it, security
# fixes included. Dependabot is what makes pinning safe rather than merely static:
# it opens a PR when the tag a pin tracks moves, keeping the trailing version
# comment and the hash in step.
- package-ecosystem: github-actions

Check warning

Code scanning / zizmor

insufficient cooldown in Dependabot updates: missing cooldown configuration Warning

insufficient cooldown in Dependabot updates: missing cooldown configuration
directory: /
schedule:
interval: weekly
commit-message:
prefix: 'ci'
4 changes: 2 additions & 2 deletions .github/workflows/create-release-pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ jobs:
pull-requests: write
steps:
- name: Checkout and setup environment
uses: MetaMask/action-checkout-and-setup@v3
uses: MetaMask/action-checkout-and-setup@0543b5929698c71e3ccc6ed24eac87825669b5de # v3.5.0
with:
is-high-risk-environment: true

Expand All @@ -40,7 +40,7 @@ jobs:
# branch for all git operations and the release PR.
ref: ${{ github.event.inputs.base-branch }}

- uses: MetaMask/action-create-release-pr@v5
- uses: MetaMask/action-create-release-pr@15b416cc72f8fde9f784896a79d42defba2e2251 # v5.1.0
with:
release-type: ${{ github.event.inputs.release-type }}
release-version: ${{ github.event.inputs.release-version }}
Expand Down
10 changes: 5 additions & 5 deletions .github/workflows/lint-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ jobs:
node-version: [20.x, 22.x, 24.x]
steps:
- name: Checkout and setup environment
uses: MetaMask/action-checkout-and-setup@v3
uses: MetaMask/action-checkout-and-setup@0543b5929698c71e3ccc6ed24eac87825669b5de # v3.5.0
with:
is-high-risk-environment: false
node-version: ${{ matrix.node-version }}
Expand All @@ -30,7 +30,7 @@ jobs:
node-version: [24.x]
steps:
- name: Checkout and setup environment
uses: MetaMask/action-checkout-and-setup@v3
uses: MetaMask/action-checkout-and-setup@0543b5929698c71e3ccc6ed24eac87825669b5de # v3.5.0
with:
is-high-risk-environment: false
node-version: ${{ matrix.node-version }}
Expand Down Expand Up @@ -58,7 +58,7 @@ jobs:
node-version: [20.x, 22.x, 24.x]
steps:
- name: Checkout and setup environment
uses: MetaMask/action-checkout-and-setup@v3
uses: MetaMask/action-checkout-and-setup@0543b5929698c71e3ccc6ed24eac87825669b5de # v3.5.0
with:
is-high-risk-environment: false
node-version: ${{ matrix.node-version }}
Expand All @@ -80,7 +80,7 @@ jobs:
node-version: [20.x, 22.x, 24.x]
steps:
- name: Checkout and setup environment
uses: MetaMask/action-checkout-and-setup@v3
uses: MetaMask/action-checkout-and-setup@0543b5929698c71e3ccc6ed24eac87825669b5de # v3.5.0
with:
is-high-risk-environment: false
node-version: ${{ matrix.node-version }}
Expand All @@ -105,7 +105,7 @@ jobs:
node-version: [24.x]
steps:
- name: Checkout and setup environment
uses: MetaMask/action-checkout-and-setup@v3
uses: MetaMask/action-checkout-and-setup@0543b5929698c71e3ccc6ed24eac87825669b5de # v3.5.0
with:
is-high-risk-environment: false
node-version: ${{ matrix.node-version }}
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout and setup environment
uses: MetaMask/action-checkout-and-setup@v3
uses: MetaMask/action-checkout-and-setup@0543b5929698c71e3ccc6ed24eac87825669b5de # v3.5.0
with:
is-high-risk-environment: false
- name: Download actionlint
Expand All @@ -32,7 +32,7 @@ jobs:
analyse-code:
name: Analyse code
needs: check-workflows
uses: MetaMask/action-security-code-scanner/.github/workflows/security-scan.yml@v2
uses: MetaMask/action-security-code-scanner/.github/workflows/security-scan.yml@becb242930b3cc271c26da0280050db9c157e291 # v2.1.1
with:
scanner-ref: v2
paths-ignored: |
Expand Down Expand Up @@ -93,7 +93,7 @@ jobs:
IS_RELEASE: ${{ steps.is-release.outputs.IS_RELEASE }}
runs-on: ubuntu-latest
steps:
- uses: MetaMask/action-is-release@v2
- uses: MetaMask/action-is-release@3cd51b98fa98d1347d06f5961299b0172ee31ae8 # v2.3.0
id: is-release

publish-release:
Expand Down
12 changes: 6 additions & 6 deletions .github/workflows/publish-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,12 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout and setup environment
uses: MetaMask/action-checkout-and-setup@v3
uses: MetaMask/action-checkout-and-setup@0543b5929698c71e3ccc6ed24eac87825669b5de # v3.5.0
with:
is-high-risk-environment: true
ref: ${{ github.sha }}
- name: Dry Run Publish
uses: MetaMask/action-npm-publish@v6
uses: MetaMask/action-npm-publish@18df42148c35aabb98e00f9fda127d421af141df # v6.5.0
with:
slack-webhook-url: ${{ secrets.SLACK_WEBHOOK_URL }}
subteam: S042S7RE4AE # @metamask-npm-publishers
Expand All @@ -37,12 +37,12 @@ jobs:
id-token: write
steps:
- name: Checkout and setup environment
uses: MetaMask/action-checkout-and-setup@v3
uses: MetaMask/action-checkout-and-setup@0543b5929698c71e3ccc6ed24eac87825669b5de # v3.5.0
with:
is-high-risk-environment: true
ref: ${{ github.sha }}
- name: Publish
uses: MetaMask/action-npm-publish@v6
uses: MetaMask/action-npm-publish@18df42148c35aabb98e00f9fda127d421af141df # v6.5.0
with:
# This `NPM_TOKEN` needs to be manually set to publish a package for
# the first time only.
Expand All @@ -59,10 +59,10 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout and setup environment
uses: MetaMask/action-checkout-and-setup@v3
uses: MetaMask/action-checkout-and-setup@0543b5929698c71e3ccc6ed24eac87825669b5de # v3.5.0
with:
is-high-risk-environment: true
ref: ${{ github.sha }}
- uses: MetaMask/action-publish-release@v3
- uses: MetaMask/action-publish-release@f01f1be110d60fb07d86c880ce3d6bdb353524d3 # v3.3.1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
66 changes: 66 additions & 0 deletions test/workflows.test.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import assert from 'node:assert/strict';
import { readFileSync, readdirSync } from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { describe, test } from 'node:test';

const WORKFLOWS = path.resolve(
path.dirname(fileURLToPath(import.meta.url)),
'..',
'.github',
'workflows',
);

// `uses: owner/repo@ref` and `uses: owner/repo/path/to.yml@ref`, but not `uses: ./local`.
const EXTERNAL_USES = /^\s*-?\s*uses:\s*([A-Za-z0-9_.-]+\/[A-Za-z0-9_./-]+)@(\S+)/u;
const COMMIT_SHA = /^[0-9a-f]{40}$/u;

function workflowFiles() {
return readdirSync(WORKFLOWS)
.filter((name) => name.endsWith('.yml') || name.endsWith('.yaml'))
.map((name) => ({ name, body: readFileSync(path.join(WORKFLOWS, name), 'utf8') }));
}

function externalRefs() {
const refs = [];
for (const { name, body } of workflowFiles()) {
body.split('\n').forEach((line, i) => {
const m = EXTERNAL_USES.exec(line);
if (m) {
refs.push({ file: name, line: i + 1, action: m[1], ref: m[2], raw: line.trim() });
}
});
}
return refs;
}

describe('workflow action references', () => {
// A tag is a moving pointer: whoever controls the action repo can repoint `@v3` at new
// code, and every workflow here picks it up on the next run with no diff and no review.
// That is the vector behind the tj-actions/changed-files compromise. A hash cannot move.
test('every external action is pinned to a commit hash', () => {
const floating = externalRefs()
.filter((r) => !COMMIT_SHA.test(r.ref))
.map((r) => `${r.file}:${r.line} → ${r.action}@${r.ref}`);
assert.deepEqual(
floating,
[],
'pin to the full 40-character commit hash, with the tag in a trailing comment '
+ '(e.g. `uses: owner/action@<sha> # v1.2.3`)',
);
});

// A bare hash is unreadable — nobody can tell v3.5.0 from a random commit at a glance,
// and Dependabot uses the comment to know which tag the pin is tracking.
test('every pinned action records its version in a trailing comment', () => {
const unlabelled = externalRefs()
.filter((r) => COMMIT_SHA.test(r.ref))
.filter((r) => !/#\s*v?\d+(\.\d+)*/u.test(r.raw))
.map((r) => `${r.file}:${r.line} → ${r.action}`);
assert.deepEqual(unlabelled, [], 'add a trailing `# v<version>` comment beside the hash');
});

test('at least one external reference exists, so the checks above are not vacuous', () => {
assert.ok(externalRefs().length > 0, 'expected external action references to check');
});
});