mirror of
https://github.com/affaan-m/everything-claude-code.git
synced 2026-05-15 13:23:13 +08:00
ci: require npm audit signature checks
Require npm registry signature verification wherever workflow npm audit checks run. - add npm audit signatures to CI Security Scan and maintenance security audit jobs - teach the workflow security validator to reject npm audit without signature verification - keep the repair and Copilot prompt tests portable across Windows path/case and CRLF frontmatter behavior Validation: - node tests/run-all.js (2376 passed, 0 failed) - CI current-head matrix green on #1846
This commit is contained in:
4
.github/workflows/ci.yml
vendored
4
.github/workflows/ci.yml
vendored
@@ -243,7 +243,9 @@ jobs:
|
|||||||
node-version: '20.x'
|
node-version: '20.x'
|
||||||
|
|
||||||
- name: Run npm audit
|
- name: Run npm audit
|
||||||
run: npm audit --audit-level=high
|
run: |
|
||||||
|
npm audit signatures
|
||||||
|
npm audit --audit-level=high
|
||||||
continue-on-error: true # Allows PR to proceed, but marks job as failed if vulnerabilities found
|
continue-on-error: true # Allows PR to proceed, but marks job as failed if vulnerabilities found
|
||||||
|
|
||||||
lint:
|
lint:
|
||||||
|
|||||||
1
.github/workflows/maintenance.yml
vendored
1
.github/workflows/maintenance.yml
vendored
@@ -34,6 +34,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
if [ -f package-lock.json ]; then
|
if [ -f package-lock.json ]; then
|
||||||
npm ci --ignore-scripts
|
npm ci --ignore-scripts
|
||||||
|
npm audit signatures
|
||||||
npm audit --audit-level=high
|
npm audit --audit-level=high
|
||||||
else
|
else
|
||||||
echo "No package-lock.json found; skipping npm audit"
|
echo "No package-lock.json found; skipping npm audit"
|
||||||
|
|||||||
@@ -26,6 +26,8 @@ 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_CI_PATTERN = /\bnpm\s+ci\b(?![^\n]*--ignore-scripts)/g;
|
||||||
|
const NPM_AUDIT_PATTERN = /\bnpm\s+audit\b(?!\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;
|
||||||
|
|
||||||
@@ -127,6 +129,16 @@ function findViolations(filePath, source) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (NPM_AUDIT_PATTERN.test(source) && !NPM_AUDIT_SIGNATURES_PATTERN.test(source)) {
|
||||||
|
violations.push({
|
||||||
|
filePath,
|
||||||
|
event: 'npm audit signatures',
|
||||||
|
description: 'workflows that run npm audit must also verify registry signatures',
|
||||||
|
expression: 'npm audit without npm audit signatures',
|
||||||
|
line: getLineNumber(source, source.search(NPM_AUDIT_PATTERN)),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return violations;
|
return violations;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -122,6 +122,21 @@ function run() {
|
|||||||
assert.match(result.stderr, /id-token: write must not restore or save shared dependency caches/);
|
assert.match(result.stderr, /id-token: write must not restore or save shared dependency caches/);
|
||||||
})) passed++; else failed++;
|
})) passed++; else failed++;
|
||||||
|
|
||||||
|
if (test('rejects npm audit without registry signature verification', () => {
|
||||||
|
const result = runValidator({
|
||||||
|
'unsafe-audit.yml': `name: Unsafe\non:\n push:\njobs:\n audit:\n runs-on: ubuntu-latest\n steps:\n - run: npm audit --audit-level=high\n`,
|
||||||
|
});
|
||||||
|
assert.notStrictEqual(result.status, 0, 'Expected validator to fail when npm audit signatures is missing');
|
||||||
|
assert.match(result.stderr, /npm audit must also verify registry signatures/);
|
||||||
|
})) passed++; else failed++;
|
||||||
|
|
||||||
|
if (test('allows npm audit when registry signatures are verified', () => {
|
||||||
|
const result = runValidator({
|
||||||
|
'safe-audit.yml': `name: Safe\non:\n push:\njobs:\n audit:\n runs-on: ubuntu-latest\n steps:\n - run: |\n npm audit signatures\n npm audit --audit-level=high\n`,
|
||||||
|
});
|
||||||
|
assert.strictEqual(result.status, 0, result.stderr || result.stdout);
|
||||||
|
})) passed++; else failed++;
|
||||||
|
|
||||||
console.log(`\nPassed: ${passed}`);
|
console.log(`\nPassed: ${passed}`);
|
||||||
console.log(`Failed: ${failed}`);
|
console.log(`Failed: ${failed}`);
|
||||||
|
|
||||||
|
|||||||
@@ -27,7 +27,8 @@ function read(relativePath) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function parseSimpleFrontmatter(source, relativePath) {
|
function parseSimpleFrontmatter(source, relativePath) {
|
||||||
const match = source.match(/^---\n([\s\S]*?)\n---\n/);
|
const normalizedSource = source.replace(/^\uFEFF/, '').replace(/\r\n/g, '\n');
|
||||||
|
const match = normalizedSource.match(/^---\n([\s\S]*?)\n---\n/);
|
||||||
assert.ok(match, `${relativePath} must start with YAML frontmatter`);
|
assert.ok(match, `${relativePath} must start with YAML frontmatter`);
|
||||||
|
|
||||||
const fields = {};
|
const fields = {};
|
||||||
|
|||||||
@@ -64,6 +64,16 @@ function runNode(scriptPath, args = [], options = {}) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function normalizeComparablePath(filePath) {
|
||||||
|
const normalized = path.normalize(filePath);
|
||||||
|
return process.platform === 'win32' ? normalized.toLowerCase() : normalized;
|
||||||
|
}
|
||||||
|
|
||||||
|
function pathListIncludes(paths, expectedPath) {
|
||||||
|
const normalizedExpected = normalizeComparablePath(expectedPath);
|
||||||
|
return paths.some(filePath => normalizeComparablePath(filePath) === normalizedExpected);
|
||||||
|
}
|
||||||
|
|
||||||
function test(name, fn) {
|
function test(name, fn) {
|
||||||
try {
|
try {
|
||||||
fn();
|
fn();
|
||||||
@@ -117,7 +127,7 @@ function runTests() {
|
|||||||
|
|
||||||
const parsed = JSON.parse(repairResult.stdout);
|
const parsed = JSON.parse(repairResult.stdout);
|
||||||
assert.strictEqual(parsed.results[0].status, 'repaired');
|
assert.strictEqual(parsed.results[0].status, 'repaired');
|
||||||
assert.ok(parsed.results[0].repairedPaths.includes(managedPath));
|
assert.ok(pathListIncludes(parsed.results[0].repairedPaths, managedPath));
|
||||||
assert.strictEqual(fs.readFileSync(managedPath, 'utf8'), expectedContent);
|
assert.strictEqual(fs.readFileSync(managedPath, 'utf8'), expectedContent);
|
||||||
assert.ok(fs.existsSync(statePath));
|
assert.ok(fs.existsSync(statePath));
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
Reference in New Issue
Block a user