blob: 834511268c0725846a5d34f2321872064af45561 [file]
import assert from 'node:assert/strict';
import { readFile } from 'node:fs/promises';
import test from 'node:test';
const auditWorkflowUrl = new URL('../.github/workflows/dependency-audit.yml', import.meta.url);
const releaseWorkflowUrl = new URL('../.github/workflows/release-desktop.yml', import.meta.url);
test('dependency audit runs daily and when its security inputs change', async () => {
const workflow = await readFile(auditWorkflowUrl, 'utf8');
assert.match(workflow, /^\s+- cron: '17 3 \* \* \*'$/mu);
assert.match(workflow, /^\s+workflow_dispatch:$/mu);
assert.match(workflow, /^\s+pull_request:$/mu);
assert.match(workflow, /^\s+push:\n\s+branches: \[main\]$/mu);
const pullRequestTrigger = workflow.slice(
workflow.indexOf(' pull_request:'),
workflow.indexOf(' push:'),
);
assert.match(pullRequestTrigger, /\.github\/workflows\/dependency-audit\.yml/u);
});
test('dependency audit cancels superseded runs for the same ref', async () => {
const workflow = await readFile(auditWorkflowUrl, 'utf8');
assert.match(
workflow,
/^concurrency:\n group: dependency-audit-\$\{\{ github\.ref \}\}\n cancel-in-progress: true$/mu,
);
});
test('dependency audit has only read-only repository access', async () => {
const workflow = await readFile(auditWorkflowUrl, 'utf8');
assert.equal([...workflow.matchAll(/^\s*permissions:/gmu)].length, 1);
assert.match(workflow, /^permissions:\n contents: read$/mu);
assert.doesNotMatch(workflow, /^\s+(?:env|environment):|\$\{\{\s*(?:secrets|vars)\./mu);
const actionReferences = [...workflow.matchAll(/^\s+uses: [^@\s]+@([^\s#]+)/gmu)].map(
(match) => match[1],
);
assert.ok(actionReferences.length > 0);
assert.ok(actionReferences.every((reference) => /^[0-9a-f]{40}$/u.test(reference)));
assert.match(workflow, /^\s+persist-credentials: false$/mu);
});
test('dependency audit installs only production packages without lifecycle scripts', async () => {
const workflow = await readFile(auditWorkflowUrl, 'utf8');
assert.match(workflow, /^\s+run: npm ci --ignore-scripts --omit=dev$/mu);
assert.match(workflow, /^\s+run: npm audit --omit=dev --audit-level=moderate$/mu);
assert.match(workflow, /^\s+run: npm audit signatures --omit=dev$/mu);
assert.doesNotMatch(workflow, /^\s+continue-on-error: true$/mu);
});
test('desktop release blocks moderate advisories before signing credentials and packaging', async () => {
const workflow = await readFile(releaseWorkflowUrl, 'utf8');
const audit = workflow.indexOf('npm audit --omit=dev --audit-level=moderate');
assert.notEqual(audit, -1);
for (const laterStep of [
'Write App Store Connect API key',
'Package notarized app and signed DMG',
'Package the Windows installer and ZIP',
]) {
assert.ok(audit < workflow.indexOf(laterStep), laterStep);
}
});