mirror of
https://github.com/affaan-m/everything-claude-code.git
synced 2026-05-14 00:23:04 +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'
|
||||
|
||||
- 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
|
||||
|
||||
lint:
|
||||
|
||||
1
.github/workflows/maintenance.yml
vendored
1
.github/workflows/maintenance.yml
vendored
@@ -34,6 +34,7 @@ jobs:
|
||||
run: |
|
||||
if [ -f package-lock.json ]; then
|
||||
npm ci --ignore-scripts
|
||||
npm audit signatures
|
||||
npm audit --audit-level=high
|
||||
else
|
||||
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 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 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;
|
||||
}
|
||||
|
||||
|
||||
@@ -122,6 +122,21 @@ function run() {
|
||||
assert.match(result.stderr, /id-token: write must not restore or save shared dependency caches/);
|
||||
})) 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(`Failed: ${failed}`);
|
||||
|
||||
|
||||
@@ -27,7 +27,8 @@ function read(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`);
|
||||
|
||||
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) {
|
||||
try {
|
||||
fn();
|
||||
@@ -117,7 +127,7 @@ function runTests() {
|
||||
|
||||
const parsed = JSON.parse(repairResult.stdout);
|
||||
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.ok(fs.existsSync(statePath));
|
||||
} finally {
|
||||
|
||||
Reference in New Issue
Block a user