| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125 |
- import { execFileSync } from 'node:child_process';
- import { existsSync, readFileSync } from 'node:fs';
- import { fileURLToPath } from 'node:url';
- import { relative, resolve } from 'node:path';
- const root = resolve(fileURLToPath(new URL('..', import.meta.url)));
- const failures = [];
- const warnings = [];
- const secretTargets = [
- 'server.js',
- 'server',
- 'src',
- 'package.json',
- 'proxy.conf.json',
- ];
- const cloudFallbackFiles = [
- 'cloud-functions/06-voiceManager.js',
- 'cloud-functions/07-quicklyVideo.js',
- 'cloud-functions/08-proxyHub.js',
- 'cloud-functions/09-uploadManager.js',
- 'cloud-functions/11-jimengManager.js',
- 'cloud-functions/12-douyinManager.js',
- 'cloud-functions/13-douyinInsightManager.js',
- ];
- const largeFileBudgets = [
- { file: 'src/app/app.ts', maxLines: 3000 },
- { file: 'src/app/app.css', maxLines: 7000 },
- { file: 'server.js', maxLines: 1800 },
- { file: 'src/app/pages/pipelines/topic-to-video/topic-to-video.component.ts', maxLines: 1200 },
- ];
- const secretPatterns = [
- { name: 'Bearer r token', pattern: /Bearer\s+r:[A-Za-z0-9_-]{20,}/g },
- { name: 'r token', pattern: /['"`]r:[A-Za-z0-9_-]{20,}['"`]/g },
- { name: 'sk key', pattern: /['"`]sk-[A-Za-z0-9_-]{20,}['"`]/g },
- ];
- const contextualSecretPatterns = [
- {
- name: 'long token/key literal',
- pattern: /\b(token|secret|apiKey|appSecret|accessKey|currentToken)\b[^;\n=:{]*[:=]\s*['"`][A-Za-z0-9+/=_-]{32,}['"`]/gi,
- },
- ];
- function runGit(args) {
- try {
- return execFileSync('git', args, { cwd: root, encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] }).trim();
- } catch {
- return '';
- }
- }
- function listTrackedFiles(paths) {
- const files = runGit(['ls-files', ...paths]);
- return files ? files.split(/\r?\n/).filter(Boolean) : [];
- }
- function read(file) {
- return readFileSync(resolve(root, file), 'utf8');
- }
- function lineOf(content, index) {
- return content.slice(0, index).split(/\r?\n/).length;
- }
- function formatFile(file) {
- return relative(root, resolve(root, file)).replace(/\\/g, '/');
- }
- const trackedEnv = runGit(['ls-files', '.env']);
- if (trackedEnv) {
- failures.push('根目录 .env 已被 Git 跟踪,请移出版本库,只保留 .env.example。');
- }
- for (const file of listTrackedFiles(secretTargets)) {
- if (!existsSync(resolve(root, file))) continue;
- if (file.endsWith('.map') || file.endsWith('.png') || file.endsWith('.jpg') || file.endsWith('.jpeg') || file.endsWith('.webp')) continue;
- const content = read(file);
- for (const item of secretPatterns) {
- for (const match of content.matchAll(item.pattern)) {
- failures.push(`${formatFile(file)}:${lineOf(content, match.index || 0)} 命中疑似真实凭证:${item.name}`);
- }
- }
- for (const item of contextualSecretPatterns) {
- for (const match of content.matchAll(item.pattern)) {
- failures.push(`${formatFile(file)}:${lineOf(content, match.index || 0)} 命中疑似真实凭证:${item.name}`);
- }
- }
- }
- for (const file of cloudFallbackFiles) {
- if (!existsSync(resolve(root, file))) continue;
- const content = read(file);
- for (const item of secretPatterns) {
- if (item.pattern.test(content)) {
- warnings.push(`${formatFile(file)} 保留云函数源码兜底凭证。当前按用户要求暂缓治理,后续确认云函数部署窗口后再处理。`);
- break;
- }
- }
- }
- for (const item of largeFileBudgets) {
- if (!existsSync(resolve(root, item.file))) continue;
- const count = read(item.file).split(/\r?\n/).length;
- if (count > item.maxLines) {
- warnings.push(`${formatFile(item.file)} 当前 ${count} 行,超过治理阈值 ${item.maxLines} 行。`);
- }
- }
- if (warnings.length) {
- console.warn('\nReview guard warnings:');
- for (const warning of warnings) console.warn(`- ${warning}`);
- }
- if (failures.length) {
- console.error('\nReview guard failed:');
- for (const failure of failures) console.error(`- ${failure}`);
- process.exit(1);
- }
- console.log('Review guard passed');
|