mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-08-21 22:55:16 +00:00
* chore(ci): add security hygiene: CODEOWNERS for release workflows, least-privilege permissions, security checks and scorecard workflows * chore(ci): pin TruffleHog scanner version and drop invalid path input * fix(ci): close security workflow review gaps * fix(ci): fail package audit on install errors * test(ci): pin security workflow guardrails * fix(ci): pin security workflow test assertions for SHA refs, status edges, and push trigger * test(ci): pin security workflow edge guards * test(ci): pin security workflow contracts * test(ci): pin secret-scan push guard * fix(ci): quote secret-scan condition * fix(ci): audit workspace package locks directly * fix(ci): scope security checks concurrency * docs(ci): explain mobile audit skip * test(ci): link trufflehog version pin
112 lines
4.5 KiB
JavaScript
112 lines
4.5 KiB
JavaScript
/**
|
|
* @license
|
|
* Copyright 2026 Qwen Team
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
import { readFileSync } from 'node:fs';
|
|
import path from 'node:path';
|
|
import { fileURLToPath } from 'node:url';
|
|
import { describe, expect, it } from 'vitest';
|
|
import { getWorkflowJob, getWorkflowStep } from './workflow-helpers.js';
|
|
|
|
const repoRoot = path.resolve(
|
|
path.dirname(fileURLToPath(import.meta.url)),
|
|
'../..',
|
|
);
|
|
|
|
const readWorkflow = (name) =>
|
|
readFileSync(path.join(repoRoot, `.github/workflows/${name}`), 'utf8');
|
|
|
|
describe('security workflows', () => {
|
|
it('keeps Scorecard monthly and reporting-only', () => {
|
|
const workflow = readWorkflow('scorecard-monthly.yml');
|
|
|
|
expect(workflow).toContain("- cron: '0 2 1 * *'");
|
|
expect(workflow).toContain('workflow_dispatch: {}');
|
|
expect(workflow).not.toContain('pull_request');
|
|
expect(workflow).toContain('publish_results: false');
|
|
expect(workflow).toContain('retention-days: 90');
|
|
expect(workflow).toContain(
|
|
'ossf/scorecard-action@2d1146689b8cda280b9bc96326124645441f03bc',
|
|
);
|
|
expect(workflow).toContain(
|
|
'actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10',
|
|
);
|
|
expect(workflow).toContain(
|
|
'actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02',
|
|
);
|
|
expect(workflow).toContain('persist-credentials: false');
|
|
});
|
|
|
|
it('keeps Security Checks reporting-only and audits package locks', () => {
|
|
const workflow = readWorkflow('security-checks.yml');
|
|
const dependencyJob = getWorkflowJob(workflow, 'dependency-cve');
|
|
const dependencyCheckoutStep = getWorkflowStep(dependencyJob, 'Checkout');
|
|
const installStep = getWorkflowStep(dependencyJob, 'Install dependencies');
|
|
const auditStep = getWorkflowStep(
|
|
dependencyJob,
|
|
'Audit production dependencies',
|
|
);
|
|
const secretScanJob = getWorkflowJob(workflow, 'secret-scan');
|
|
const checkoutStep = getWorkflowStep(secretScanJob, 'Checkout');
|
|
const trufflehogStep = getWorkflowStep(
|
|
secretScanJob,
|
|
'Scan for verified secrets',
|
|
);
|
|
|
|
expect(workflow).toContain('pull_request:');
|
|
expect(workflow).toContain('push:');
|
|
expect(workflow).toContain(
|
|
"group: '${{ github.workflow }}-${{ github.event.pull_request.head.repo.full_name || github.repository }}-${{ github.head_ref || github.ref }}'",
|
|
);
|
|
expect(workflow).toContain(
|
|
'cancel-in-progress: "${{ github.event_name == \'pull_request\' }}"',
|
|
);
|
|
expect(workflow).toContain(
|
|
'actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10',
|
|
);
|
|
expect(workflow).toContain(
|
|
'actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e',
|
|
);
|
|
expect(dependencyCheckoutStep).toContain('persist-credentials: false');
|
|
expect(checkoutStep).toContain('persist-credentials: false');
|
|
expect(installStep).toContain(
|
|
"run: 'npm ci --ignore-scripts --no-audit --progress=false'",
|
|
);
|
|
expect(auditStep).toContain('continue-on-error: true');
|
|
expect(auditStep).toContain('status=0');
|
|
expect(auditStep).toContain('exit "$status"');
|
|
expect(auditStep).toContain('npm audit --omit=dev --audit-level=high');
|
|
expect(auditStep).toContain(
|
|
'npm audit --omit=dev --audit-level=high || status=$?',
|
|
);
|
|
expect(auditStep).toContain(') || status=$?');
|
|
expect(auditStep).toContain('for lockfile in packages/*/package-lock.json');
|
|
expect(auditStep).toContain('[ -f "$lockfile" ] || continue');
|
|
expect(auditStep).toContain(
|
|
'[ "$lockfile" != "packages/mobile-mcp/package-lock.json" ] || continue',
|
|
);
|
|
expect(auditStep).toContain('cd "$package_dir"');
|
|
expect(auditStep).toContain(
|
|
'npm ci --ignore-scripts --no-audit --progress=false --workspaces=false &&',
|
|
);
|
|
expect(auditStep).toContain(
|
|
'npm audit --omit=dev --audit-level=high --workspaces=false',
|
|
);
|
|
expect(trufflehogStep).toContain('continue-on-error: true');
|
|
const trufflehogPin = trufflehogStep.match(
|
|
/trufflesecurity\/trufflehog@[0-9a-f]{40}' # v([\d.]+)/,
|
|
);
|
|
expect(trufflehogPin).not.toBeNull();
|
|
expect(trufflehogStep).toContain(`version: '${trufflehogPin?.[1]}'`);
|
|
expect(trufflehogStep).toContain(
|
|
"if: \"github.event_name == 'pull_request' || github.event.before != '0000000000000000000000000000000000000000'\"",
|
|
);
|
|
expect(trufflehogStep).toContain("extra_args: '--only-verified'");
|
|
expect(trufflehogStep).toContain(
|
|
'trufflesecurity/trufflehog@6f3c981e7b77f235fd2702dd74af25fc4b72bf11',
|
|
);
|
|
expect(checkoutStep).toContain('fetch-depth: 0');
|
|
});
|
|
});
|