qwen-code/scripts/tests/security-workflows.test.js
易良 e40263ee55
chore(deps): Clear high-severity CVE baseline and harden the security gate (#9584)
* chore(deps): Clear high-severity CVE baseline and harden the security gate

- Bump OpenTelemetry stack to 0.221.x (fixes @opentelemetry/core advisories)
- Bump @larksuiteoapi/node-sdk to ^1.73.0 and override axios to ^1.19.0
- Bump mobilewright to ^0.0.53 (drops vulnerable sharp 0.34.x)
- Bump markdown-it to ^15.0.0 (drops vulnerable linkify-it 5.x)
- Update undici/fast-uri/brace-expansion/ip-address within range
- Adapt telemetry code to OTel API changes (forceFlush, processor options)
- Make security-checks a hard gate now that the high baseline is clean

* chore(deps): Refresh mobile-mcp vendored lockfile to drop vulnerable sharp

* fix(telemetry): stub sdk-node 0.221 env auto-config helper packages

sdk-node 0.221 extracted its env-based auto-configuration into
@opentelemetry/configuration, otlp-exporter-base, and
otlp-grpc-exporter-base, which it now requires eagerly. The existing
esbuild stub only covered the exporter-* packages, so the OTLP protocol
chain (grpc-js, protobufjs, otlp-transformer) re-entered the sdk-impl
static closure and tripped the serve fast-path bundle guard.

Stub the three helper packages when imported by sdk-node only; our own
protocol modules keep resolving the real packages. qwen-code never
reaches these helpers at runtime (explicit exporters + env scrub).

* fix(telemetry): disable metrics fallback without reader

* fix(vscode): restore nested dependency notices

* fix(deps): declare bundled punycode so its notice survives regeneration

The CLI esbuild config aliases punycode to the userland package
(esbuild.config.js), so the shipped CLI bundle contains MIT-licensed
punycode@2.3.1. Its NOTICES.txt section was lost because the only
lockfile paths reaching punycode were dev-only; the notice walker
(rooted at vscode-ide-companion) never sees a production declaration.

Declare punycode as a direct production dependency of the CLI (the
bundle input) and of vscode-ide-companion (which packages the bundled
CLI into the VSIX and owns NOTICES.txt), then regenerate the lockfile
and notices so the MIT notice is restored.
2026-08-21 07:43:32 +00:00

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 a hard gate 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).not.toContain('continue-on-error');
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).not.toContain('continue-on-error');
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');
});
});