mirror of
https://github.com/affaan-m/everything-claude-code.git
synced 2026-05-18 13:21:15 +08:00
Harden CI installs against supply-chain lifecycle hooks
This commit is contained in:
parent
6951b8d5d2
commit
f7035b5644
77
.github/workflows/ci.yml
vendored
77
.github/workflows/ci.yml
vendored
@ -68,73 +68,6 @@ jobs:
|
|||||||
if: matrix.pm == 'bun'
|
if: matrix.pm == 'bun'
|
||||||
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
|
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
|
||||||
|
|
||||||
# Cache configuration
|
|
||||||
- name: Get npm cache directory
|
|
||||||
if: matrix.pm == 'npm'
|
|
||||||
id: npm-cache-dir
|
|
||||||
shell: bash
|
|
||||||
run: echo "dir=$(npm config get cache)" >> $GITHUB_OUTPUT
|
|
||||||
|
|
||||||
- name: Restore npm cache
|
|
||||||
if: matrix.pm == 'npm'
|
|
||||||
continue-on-error: true
|
|
||||||
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
|
||||||
with:
|
|
||||||
path: ${{ steps.npm-cache-dir.outputs.dir }}
|
|
||||||
key: ${{ runner.os }}-node-${{ matrix.node }}-npm-${{ hashFiles('**/package-lock.json') }}
|
|
||||||
restore-keys: |
|
|
||||||
${{ runner.os }}-node-${{ matrix.node }}-npm-
|
|
||||||
|
|
||||||
- name: Get pnpm store directory
|
|
||||||
if: matrix.pm == 'pnpm'
|
|
||||||
id: pnpm-cache-dir
|
|
||||||
shell: bash
|
|
||||||
env:
|
|
||||||
COREPACK_ENABLE_STRICT: '0'
|
|
||||||
run: echo "dir=$(pnpm store path)" >> $GITHUB_OUTPUT
|
|
||||||
|
|
||||||
- name: Restore pnpm cache
|
|
||||||
if: matrix.pm == 'pnpm'
|
|
||||||
continue-on-error: true
|
|
||||||
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
|
||||||
with:
|
|
||||||
path: ${{ steps.pnpm-cache-dir.outputs.dir }}
|
|
||||||
key: ${{ runner.os }}-node-${{ matrix.node }}-pnpm-${{ hashFiles('**/pnpm-lock.yaml') }}
|
|
||||||
restore-keys: |
|
|
||||||
${{ runner.os }}-node-${{ matrix.node }}-pnpm-
|
|
||||||
|
|
||||||
- name: Get yarn cache directory
|
|
||||||
if: matrix.pm == 'yarn'
|
|
||||||
id: yarn-cache-dir
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
# Try Yarn Berry first, fall back to Yarn v1
|
|
||||||
if yarn config get cacheFolder >/dev/null 2>&1; then
|
|
||||||
echo "dir=$(yarn config get cacheFolder)" >> $GITHUB_OUTPUT
|
|
||||||
else
|
|
||||||
echo "dir=$(yarn cache dir)" >> $GITHUB_OUTPUT
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: Restore yarn cache
|
|
||||||
if: matrix.pm == 'yarn'
|
|
||||||
continue-on-error: true
|
|
||||||
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
|
||||||
with:
|
|
||||||
path: ${{ steps.yarn-cache-dir.outputs.dir }}
|
|
||||||
key: ${{ runner.os }}-node-${{ matrix.node }}-yarn-${{ hashFiles('**/yarn.lock') }}
|
|
||||||
restore-keys: |
|
|
||||||
${{ runner.os }}-node-${{ matrix.node }}-yarn-
|
|
||||||
|
|
||||||
- name: Restore bun cache
|
|
||||||
if: matrix.pm == 'bun'
|
|
||||||
continue-on-error: true
|
|
||||||
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
|
||||||
with:
|
|
||||||
path: ~/.bun/install/cache
|
|
||||||
key: ${{ runner.os }}-bun-${{ hashFiles('**/bun.lockb') }}
|
|
||||||
restore-keys: |
|
|
||||||
${{ runner.os }}-bun-
|
|
||||||
|
|
||||||
# Install dependencies
|
# Install dependencies
|
||||||
# COREPACK_ENABLE_STRICT=0 allows pnpm to install even though
|
# COREPACK_ENABLE_STRICT=0 allows pnpm to install even though
|
||||||
# package.json declares "packageManager": "yarn@..."
|
# package.json declares "packageManager": "yarn@..."
|
||||||
@ -142,16 +75,18 @@ jobs:
|
|||||||
shell: bash
|
shell: bash
|
||||||
env:
|
env:
|
||||||
COREPACK_ENABLE_STRICT: '0'
|
COREPACK_ENABLE_STRICT: '0'
|
||||||
|
npm_config_ignore_scripts: 'true'
|
||||||
|
YARN_ENABLE_SCRIPTS: 'false'
|
||||||
run: |
|
run: |
|
||||||
case "${{ matrix.pm }}" in
|
case "${{ matrix.pm }}" in
|
||||||
npm) npm ci ;;
|
npm) npm ci --ignore-scripts ;;
|
||||||
# pnpm v10 can fail CI on ignored native build scripts
|
# pnpm v10 can fail CI on ignored native build scripts
|
||||||
# (for example msgpackr-extract) even though this repo is Yarn-native
|
# (for example msgpackr-extract) even though this repo is Yarn-native
|
||||||
# and pnpm is only exercised here as a compatibility lane.
|
# and pnpm is only exercised here as a compatibility lane.
|
||||||
pnpm) pnpm install --config.strict-dep-builds=false --no-frozen-lockfile ;;
|
pnpm) pnpm install --ignore-scripts --config.strict-dep-builds=false --no-frozen-lockfile ;;
|
||||||
# Yarn Berry (v4+) removed --ignore-engines; engine checking is no longer a core feature
|
# Yarn Berry (v4+) removed --ignore-engines; engine checking is no longer a core feature
|
||||||
yarn) yarn install ;;
|
yarn) yarn install --mode=skip-build ;;
|
||||||
bun) bun install ;;
|
bun) bun install --ignore-scripts ;;
|
||||||
*) echo "Unsupported package manager: ${{ matrix.pm }}" && exit 1 ;;
|
*) echo "Unsupported package manager: ${{ matrix.pm }}" && exit 1 ;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
|
|||||||
76
.github/workflows/reusable-test.yml
vendored
76
.github/workflows/reusable-test.yml
vendored
@ -59,88 +59,24 @@ jobs:
|
|||||||
if: inputs.package-manager == 'bun'
|
if: inputs.package-manager == 'bun'
|
||||||
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
|
uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2
|
||||||
|
|
||||||
- name: Get npm cache directory
|
|
||||||
if: inputs.package-manager == 'npm'
|
|
||||||
id: npm-cache-dir
|
|
||||||
shell: bash
|
|
||||||
run: echo "dir=$(npm config get cache)" >> $GITHUB_OUTPUT
|
|
||||||
|
|
||||||
- name: Restore npm cache
|
|
||||||
if: inputs.package-manager == 'npm'
|
|
||||||
continue-on-error: true
|
|
||||||
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
|
||||||
with:
|
|
||||||
path: ${{ steps.npm-cache-dir.outputs.dir }}
|
|
||||||
key: ${{ runner.os }}-node-${{ inputs.node-version }}-npm-${{ hashFiles('**/package-lock.json') }}
|
|
||||||
restore-keys: |
|
|
||||||
${{ runner.os }}-node-${{ inputs.node-version }}-npm-
|
|
||||||
|
|
||||||
- name: Get pnpm store directory
|
|
||||||
if: inputs.package-manager == 'pnpm'
|
|
||||||
id: pnpm-cache-dir
|
|
||||||
shell: bash
|
|
||||||
env:
|
|
||||||
COREPACK_ENABLE_STRICT: '0'
|
|
||||||
run: echo "dir=$(pnpm store path)" >> $GITHUB_OUTPUT
|
|
||||||
|
|
||||||
- name: Restore pnpm cache
|
|
||||||
if: inputs.package-manager == 'pnpm'
|
|
||||||
continue-on-error: true
|
|
||||||
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
|
||||||
with:
|
|
||||||
path: ${{ steps.pnpm-cache-dir.outputs.dir }}
|
|
||||||
key: ${{ runner.os }}-node-${{ inputs.node-version }}-pnpm-${{ hashFiles('**/pnpm-lock.yaml') }}
|
|
||||||
restore-keys: |
|
|
||||||
${{ runner.os }}-node-${{ inputs.node-version }}-pnpm-
|
|
||||||
|
|
||||||
- name: Get yarn cache directory
|
|
||||||
if: inputs.package-manager == 'yarn'
|
|
||||||
id: yarn-cache-dir
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
# Try Yarn Berry first, fall back to Yarn v1
|
|
||||||
if yarn config get cacheFolder >/dev/null 2>&1; then
|
|
||||||
echo "dir=$(yarn config get cacheFolder)" >> $GITHUB_OUTPUT
|
|
||||||
else
|
|
||||||
echo "dir=$(yarn cache dir)" >> $GITHUB_OUTPUT
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: Restore yarn cache
|
|
||||||
if: inputs.package-manager == 'yarn'
|
|
||||||
continue-on-error: true
|
|
||||||
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
|
||||||
with:
|
|
||||||
path: ${{ steps.yarn-cache-dir.outputs.dir }}
|
|
||||||
key: ${{ runner.os }}-node-${{ inputs.node-version }}-yarn-${{ hashFiles('**/yarn.lock') }}
|
|
||||||
restore-keys: |
|
|
||||||
${{ runner.os }}-node-${{ inputs.node-version }}-yarn-
|
|
||||||
|
|
||||||
- name: Restore bun cache
|
|
||||||
if: inputs.package-manager == 'bun'
|
|
||||||
continue-on-error: true
|
|
||||||
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
|
||||||
with:
|
|
||||||
path: ~/.bun/install/cache
|
|
||||||
key: ${{ runner.os }}-bun-${{ hashFiles('**/bun.lockb') }}
|
|
||||||
restore-keys: |
|
|
||||||
${{ runner.os }}-bun-
|
|
||||||
|
|
||||||
# COREPACK_ENABLE_STRICT=0 allows pnpm to install even though
|
# COREPACK_ENABLE_STRICT=0 allows pnpm to install even though
|
||||||
# package.json declares "packageManager": "yarn@..."
|
# package.json declares "packageManager": "yarn@..."
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
shell: bash
|
shell: bash
|
||||||
env:
|
env:
|
||||||
COREPACK_ENABLE_STRICT: '0'
|
COREPACK_ENABLE_STRICT: '0'
|
||||||
|
npm_config_ignore_scripts: 'true'
|
||||||
|
YARN_ENABLE_SCRIPTS: 'false'
|
||||||
run: |
|
run: |
|
||||||
case "${{ inputs.package-manager }}" in
|
case "${{ inputs.package-manager }}" in
|
||||||
npm) npm ci ;;
|
npm) npm ci --ignore-scripts ;;
|
||||||
# pnpm v10 can fail CI on ignored native build scripts
|
# pnpm v10 can fail CI on ignored native build scripts
|
||||||
# (for example msgpackr-extract) even though this repo is Yarn-native
|
# (for example msgpackr-extract) even though this repo is Yarn-native
|
||||||
# and pnpm is only exercised here as a compatibility lane.
|
# and pnpm is only exercised here as a compatibility lane.
|
||||||
pnpm) pnpm install --config.strict-dep-builds=false --no-frozen-lockfile ;;
|
pnpm) pnpm install --ignore-scripts --config.strict-dep-builds=false --no-frozen-lockfile ;;
|
||||||
# Yarn Berry (v4+) removed --ignore-engines; engine checking is no longer a core feature
|
# Yarn Berry (v4+) removed --ignore-engines; engine checking is no longer a core feature
|
||||||
yarn) yarn install ;;
|
yarn) yarn install --mode=skip-build ;;
|
||||||
bun) bun install ;;
|
bun) bun install --ignore-scripts ;;
|
||||||
*) echo "Unsupported package manager: ${{ inputs.package-manager }}" && exit 1 ;;
|
*) echo "Unsupported package manager: ${{ inputs.package-manager }}" && exit 1 ;;
|
||||||
esac
|
esac
|
||||||
|
|
||||||
|
|||||||
@ -34,12 +34,12 @@ Run these from `everything-claude-code` unless a row says otherwise.
|
|||||||
| AgentShield PRs/issues | GitHub connector and `gh` readback | 0 open PRs; 0 open issues |
|
| AgentShield PRs/issues | GitHub connector and `gh` readback | 0 open PRs; 0 open issues |
|
||||||
| ECC Tools PRs/issues | Local `gh pr list` and `gh issue list` | 0 open PRs; 0 open issues |
|
| ECC Tools PRs/issues | Local `gh pr list` and `gh issue list` | 0 open PRs; 0 open issues |
|
||||||
| Discussion baseline | GraphQL discussion sweep | Main repo #1923 marked answered; no answerable Q&A missing an answer |
|
| Discussion baseline | GraphQL discussion sweep | Main repo #1923 marked answered; no answerable Q&A missing an answer |
|
||||||
| Supply-chain IOC scan | `node scripts/ci/scan-supply-chain-iocs.js --root <ECC-workspace> --home` | Passed; 1241 files inspected |
|
| Supply-chain IOC scan | `node scripts/ci/scan-supply-chain-iocs.js --root <ECC-workspace> --home` | Passed; repo/home targeted scan inspected 200 files after clean no-script reinstall |
|
||||||
| IOC unit tests | `node tests/ci/scan-supply-chain-iocs.test.js` | 15/15 passed |
|
| IOC unit tests | `node tests/ci/scan-supply-chain-iocs.test.js` | 15/15 passed |
|
||||||
| Dead-man switch persistence sweep | Process, LaunchAgent, and known payload filename sweep for Mini Shai-Hulud markers | No matches |
|
| Dead-man switch persistence sweep | Process, LaunchAgent, and known payload filename sweep for Mini Shai-Hulud markers | No matches |
|
||||||
| Workflow security gate | `node scripts/ci/validate-workflow-security.js` | Passed; 8 workflow files inspected |
|
| Workflow security gate | `node scripts/ci/validate-workflow-security.js` | Passed; 8 workflow files inspected; package-manager test installs disable lifecycle scripts and no Actions cache use remains |
|
||||||
| Supply-chain watch workflow | `.github/workflows/supply-chain-watch.yml` | Scheduled every 6 hours; emits `supply-chain-ioc-report.json` |
|
| Supply-chain watch workflow | `.github/workflows/supply-chain-watch.yml` | Scheduled every 6 hours; emits `supply-chain-ioc-report.json` |
|
||||||
| npm signatures and audit | `npm audit signatures && npm audit --audit-level=moderate` in main, AgentShield, ECC Tools | 0 vulnerabilities in each checked package |
|
| npm signatures and audit | `npm audit signatures && npm audit --audit-level=high` in main | 213 verified signatures, 17 verified attestations, 0 high vulnerabilities |
|
||||||
|
|
||||||
## Prompt-To-Artifact Checklist
|
## Prompt-To-Artifact Checklist
|
||||||
|
|
||||||
|
|||||||
@ -126,8 +126,10 @@ If ECC or a maintainer machine installed a known-bad package version:
|
|||||||
keys, and local `.npmrc` tokens;
|
keys, and local `.npmrc` tokens;
|
||||||
- any MCP, plugin, or harness credentials available in environment variables
|
- any MCP, plugin, or harness credentials available in environment variables
|
||||||
or user-scope config.
|
or user-scope config.
|
||||||
6. Purge GitHub Actions caches for affected repositories.
|
6. Purge GitHub Actions dependency caches for affected repositories.
|
||||||
7. Reinstall from a clean environment with `npm ci --ignore-scripts` first.
|
7. Reinstall from a clean environment with lifecycle scripts disabled first:
|
||||||
|
`npm ci --ignore-scripts`, `pnpm install --ignore-scripts`,
|
||||||
|
`yarn install --mode=skip-build`, or `bun install --ignore-scripts`.
|
||||||
8. Re-enable lifecycle scripts only after the dependency tree and package
|
8. Re-enable lifecycle scripts only after the dependency tree and package
|
||||||
versions are pinned to known-clean releases.
|
versions are pinned to known-clean releases.
|
||||||
|
|
||||||
@ -136,7 +138,9 @@ If ECC or a maintainer machine installed a known-bad package version:
|
|||||||
ECC enforces these rules through `scripts/ci/validate-workflow-security.js`:
|
ECC enforces these rules through `scripts/ci/validate-workflow-security.js`:
|
||||||
|
|
||||||
- privileged workflows must not checkout untrusted PR refs;
|
- privileged workflows must not checkout untrusted PR refs;
|
||||||
- workflows with write permissions must use `npm ci --ignore-scripts`;
|
- all workflow dependency installs must disable lifecycle scripts;
|
||||||
|
- workflows must not restore or save shared GitHub Actions dependency caches
|
||||||
|
during active supply-chain hardening;
|
||||||
- workflows with `id-token: write` must not restore or save shared dependency
|
- workflows with `id-token: write` must not restore or save shared dependency
|
||||||
caches;
|
caches;
|
||||||
- workflows that run `npm audit` must also run `npm audit signatures`;
|
- workflows that run `npm audit` must also run `npm audit signatures`;
|
||||||
|
|||||||
@ -25,11 +25,28 @@ const RULES = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
const WRITE_PERMISSION_PATTERN = /^\s*(?:contents|issues|pull-requests|actions|checks|deployments|discussions|id-token|packages|pages|repository-projects|security-events|statuses):\s*write\b/m;
|
const WRITE_PERMISSION_PATTERN = /^\s*(?:contents|issues|pull-requests|actions|checks|deployments|discussions|id-token|packages|pages|repository-projects|security-events|statuses):\s*write\b/m;
|
||||||
const NPM_CI_PATTERN = /\bnpm\s+ci\b(?![^\n]*--ignore-scripts)/g;
|
|
||||||
const NPM_AUDIT_PATTERN = /\bnpm\s+audit\b(?!\s+signatures\b)/;
|
const NPM_AUDIT_PATTERN = /\bnpm\s+audit\b(?!\s+signatures\b)/;
|
||||||
const NPM_AUDIT_SIGNATURES_PATTERN = /\bnpm\s+audit\s+signatures\b/;
|
const NPM_AUDIT_SIGNATURES_PATTERN = /\bnpm\s+audit\s+signatures\b/;
|
||||||
const ACTIONS_CACHE_PATTERN = /uses:\s*['"]?actions\/cache@/m;
|
const ACTIONS_CACHE_PATTERN = /uses:\s*['"]?actions\/cache@/m;
|
||||||
const ID_TOKEN_WRITE_PATTERN = /^\s*id-token:\s*write\b/m;
|
const ID_TOKEN_WRITE_PATTERN = /^\s*id-token:\s*write\b/m;
|
||||||
|
const UNSAFE_INSTALL_PATTERNS = [
|
||||||
|
{
|
||||||
|
pattern: /\bnpm\s+ci\b(?![^\n]*--ignore-scripts)/g,
|
||||||
|
description: 'npm ci must include --ignore-scripts',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
pattern: /\bpnpm\s+install\b(?![^\n]*--ignore-scripts)/g,
|
||||||
|
description: 'pnpm install must include --ignore-scripts',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
pattern: /\byarn\s+install\b(?![^\n]*--mode=skip-build)/g,
|
||||||
|
description: 'yarn install must use --mode=skip-build',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
pattern: /\bbun\s+install\b(?![^\n]*--ignore-scripts)/g,
|
||||||
|
description: 'bun install must include --ignore-scripts',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
function getWorkflowFiles(workflowsDir) {
|
function getWorkflowFiles(workflowsDir) {
|
||||||
if (!fs.existsSync(workflowsDir)) {
|
if (!fs.existsSync(workflowsDir)) {
|
||||||
@ -120,11 +137,14 @@ function findViolations(filePath, source) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const match of source.matchAll(NPM_CI_PATTERN)) {
|
}
|
||||||
|
|
||||||
|
for (const installRule of UNSAFE_INSTALL_PATTERNS) {
|
||||||
|
for (const match of source.matchAll(installRule.pattern)) {
|
||||||
violations.push({
|
violations.push({
|
||||||
filePath,
|
filePath,
|
||||||
event: 'write-permission install',
|
event: 'dependency install scripts',
|
||||||
description: 'workflows with write permissions must install npm dependencies with --ignore-scripts',
|
description: `workflow dependency installs must not run lifecycle scripts: ${installRule.description}`,
|
||||||
expression: match[0],
|
expression: match[0],
|
||||||
line: getLineNumber(source, match.index),
|
line: getLineNumber(source, match.index),
|
||||||
});
|
});
|
||||||
@ -141,6 +161,16 @@ function findViolations(filePath, source) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (ACTIONS_CACHE_PATTERN.test(source)) {
|
||||||
|
violations.push({
|
||||||
|
filePath,
|
||||||
|
event: 'dependency cache',
|
||||||
|
description: 'GitHub Actions dependency caches are disabled during active supply-chain hardening',
|
||||||
|
expression: 'actions/cache',
|
||||||
|
line: getLineNumber(source, source.search(ACTIONS_CACHE_PATTERN)),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (/\bpull_request_target\s*:/m.test(source) && ACTIONS_CACHE_PATTERN.test(source)) {
|
if (/\bpull_request_target\s*:/m.test(source) && ACTIONS_CACHE_PATTERN.test(source)) {
|
||||||
violations.push({
|
violations.push({
|
||||||
filePath,
|
filePath,
|
||||||
|
|||||||
@ -107,21 +107,39 @@ function run() {
|
|||||||
assert.match(result.stderr, /pull_request_target workflows must not restore or save shared dependency caches/);
|
assert.match(result.stderr, /pull_request_target workflows must not restore or save shared dependency caches/);
|
||||||
})) passed++; else failed++;
|
})) passed++; else failed++;
|
||||||
|
|
||||||
if (test('rejects npm ci without ignore-scripts in workflows with write permissions', () => {
|
if (test('rejects dependency cache use in ordinary workflows', () => {
|
||||||
const result = runValidator({
|
const result = runValidator({
|
||||||
'unsafe-write-install.yml': `name: Unsafe\non:\n workflow_dispatch:\npermissions:\n contents: read\n issues: write\njobs:\n audit:\n runs-on: ubuntu-latest\n steps:\n - run: npm ci\n`,
|
'unsafe-cache.yml': `name: Unsafe\non:\n pull_request:\njobs:\n test:\n runs-on: ubuntu-latest\n steps:\n - uses: actions/cache@v5\n with:\n path: ~/.npm\n key: cache\n`,
|
||||||
});
|
});
|
||||||
assert.notStrictEqual(result.status, 0, 'Expected validator to fail on npm ci without --ignore-scripts');
|
assert.notStrictEqual(result.status, 0, 'Expected validator to fail on actions/cache use');
|
||||||
assert.match(result.stderr, /write permissions must install npm dependencies with --ignore-scripts/);
|
assert.match(result.stderr, /dependency caches are disabled during active supply-chain hardening/);
|
||||||
})) passed++; else failed++;
|
})) passed++; else failed++;
|
||||||
|
|
||||||
if (test('allows npm ci with ignore-scripts in workflows with write permissions', () => {
|
if (test('rejects npm ci without ignore-scripts in any workflow', () => {
|
||||||
const result = runValidator({
|
const result = runValidator({
|
||||||
'safe-write-install.yml': `name: Safe\non:\n workflow_dispatch:\npermissions:\n contents: read\n issues: write\njobs:\n audit:\n runs-on: ubuntu-latest\n steps:\n - run: npm ci --ignore-scripts\n`,
|
'unsafe-install.yml': `name: Unsafe\non:\n pull_request:\njobs:\n audit:\n runs-on: ubuntu-latest\n steps:\n - run: npm ci\n`,
|
||||||
|
});
|
||||||
|
assert.notStrictEqual(result.status, 0, 'Expected validator to fail on npm ci without --ignore-scripts');
|
||||||
|
assert.match(result.stderr, /npm ci must include --ignore-scripts/);
|
||||||
|
})) passed++; else failed++;
|
||||||
|
|
||||||
|
if (test('allows package-manager installs with lifecycle scripts disabled', () => {
|
||||||
|
const result = runValidator({
|
||||||
|
'safe-install.yml': `name: Safe\non:\n pull_request:\njobs:\n audit:\n runs-on: ubuntu-latest\n steps:\n - run: |\n npm ci --ignore-scripts\n pnpm install --ignore-scripts --no-frozen-lockfile\n yarn install --mode=skip-build\n bun install --ignore-scripts\n`,
|
||||||
});
|
});
|
||||||
assert.strictEqual(result.status, 0, result.stderr || result.stdout);
|
assert.strictEqual(result.status, 0, result.stderr || result.stdout);
|
||||||
})) passed++; else failed++;
|
})) passed++; else failed++;
|
||||||
|
|
||||||
|
if (test('rejects pnpm, yarn, and bun installs that run lifecycle scripts', () => {
|
||||||
|
const result = runValidator({
|
||||||
|
'unsafe-matrix-install.yml': `name: Unsafe\non:\n pull_request:\njobs:\n audit:\n runs-on: ubuntu-latest\n steps:\n - run: |\n pnpm install --no-frozen-lockfile\n yarn install\n bun install\n`,
|
||||||
|
});
|
||||||
|
assert.notStrictEqual(result.status, 0, 'Expected validator to fail on script-running installs');
|
||||||
|
assert.match(result.stderr, /pnpm install must include --ignore-scripts/);
|
||||||
|
assert.match(result.stderr, /yarn install must use --mode=skip-build/);
|
||||||
|
assert.match(result.stderr, /bun install must include --ignore-scripts/);
|
||||||
|
})) passed++; else failed++;
|
||||||
|
|
||||||
if (test('rejects checkout credential persistence in workflows with write permissions', () => {
|
if (test('rejects checkout credential persistence in workflows with write permissions', () => {
|
||||||
const result = runValidator({
|
const result = runValidator({
|
||||||
'unsafe-write-checkout.yml': `name: Unsafe\non:\n workflow_dispatch:\npermissions:\n contents: write\njobs:\n release:\n runs-on: ubuntu-latest\n steps:\n - uses: actions/checkout@v4\n - run: npm ci --ignore-scripts\n`,
|
'unsafe-write-checkout.yml': `name: Unsafe\non:\n workflow_dispatch:\npermissions:\n contents: write\njobs:\n release:\n runs-on: ubuntu-latest\n steps:\n - uses: actions/checkout@v4\n - run: npm ci --ignore-scripts\n`,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user