guanghulab/scripts/skyeye/credential-audit.js
Guanghu Domestic Migration d1e47f4565
Some checks are pending
自动更新代码和重启 / update-and-restart (push) Waiting to run
CI检查 + 自动部署 / check (push) Waiting to run
CI检查 + 自动部署 / deploy (push) Blocked by required conditions
重启聊天服务 / restart (push) Waiting to run
chore: import sanitized domestic snapshot for REPO-002
Source snapshot: ca48d3ddf926d79aa138306164169baf764bb829
2026-07-17 15:54:41 +08:00

267 lines
9.6 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env node
/**
* scripts/skyeye/credential-audit.js
*
* 天眼全域系统审计 (Sky-Eye Credential Audit)
*
* 功能:
* ① 校验 OAuth2 凭据环境变量完整性
* ② 扫描所有依赖 Drive 凭据的工作流和脚本
* ③ 验证 YAML 语法(.github/workflows/ 下所有配置文件)
* ④ 生成审计报告写入 System_Logs/
*
* 环境变量:
* - GDRIVE_CLIENT_ID: (可选) OAuth 客户端 ID
* - GDRIVE_CLIENT_SECRET: (可选) OAuth 客户端密钥
* - GDRIVE_REFRESH_TOKEN: (可选) 长效刷新令牌
*
* 用法node scripts/skyeye/credential-audit.js
*
* 守护: PER-ZY001 铸渊
* 系统: SYS-GLW-0001
* 主控: TCS-0002∞ (冰朔)
*/
'use strict';
const fs = require('fs');
const path = require('path');
const { validateOAuth2Credentials, formatDiagnosticReport } = require('./credential-validator');
const ROOT = path.resolve(__dirname, '../..');
const WORKFLOWS_DIR = path.join(ROOT, '.github/workflows');
const SYSTEM_LOGS_DIR = path.join(ROOT, 'System_Logs');
function getTimestamp() {
return new Date().toISOString();
}
function getDateStr() {
return new Date().toISOString().slice(0, 10).replace(/-/g, '');
}
// ═══════════════════════════════════════════════
// ① 密钥流校验
// ═══════════════════════════════════════════════
function auditCredential() {
const hasAnyVar = process.env.GDRIVE_CLIENT_ID || process.env.GDRIVE_CLIENT_SECRET || process.env.GDRIVE_REFRESH_TOKEN;
if (!hasAnyVar) {
return {
status: 'skipped',
reason: 'OAuth2 credentials not available in environment (GDRIVE_CLIENT_ID / GDRIVE_CLIENT_SECRET / GDRIVE_REFRESH_TOKEN)',
recommendation: 'Run this audit in a GitHub Actions workflow with the secrets configured'
};
}
const validation = validateOAuth2Credentials();
return {
status: validation.valid ? 'pass' : 'fail',
diagnostics: validation.diagnostics,
issues: validation.issues,
report: formatDiagnosticReport(validation)
};
}
// ═══════════════════════════════════════════════
// ② 依赖图扫描
// ═══════════════════════════════════════════════
function scanCredentialDependencies() {
const results = {
workflows: [],
scripts: [],
total_dependents: 0
};
const SEARCH_PATTERN = 'GDRIVE_CLIENT_ID';
// 扫描 workflows
if (fs.existsSync(WORKFLOWS_DIR)) {
const files = fs.readdirSync(WORKFLOWS_DIR).filter(f => f.endsWith('.yml') || f.endsWith('.yaml'));
for (const file of files) {
try {
const content = fs.readFileSync(path.join(WORKFLOWS_DIR, file), 'utf8');
if (content.includes(SEARCH_PATTERN)) {
results.workflows.push(file);
}
} catch (e) {
// Skip unreadable files
}
}
}
// 扫描 scripts
const scriptDirs = [
path.join(ROOT, 'scripts'),
path.join(ROOT, 'scripts/grid-db'),
path.join(ROOT, 'scripts/skyeye')
];
for (const dir of scriptDirs) {
if (!fs.existsSync(dir)) continue;
const files = fs.readdirSync(dir).filter(f => f.endsWith('.js'));
for (const file of files) {
try {
const content = fs.readFileSync(path.join(dir, file), 'utf8');
if (content.includes(SEARCH_PATTERN)) {
const relPath = path.relative(ROOT, path.join(dir, file));
results.scripts.push(relPath);
}
} catch (e) {
// Skip unreadable files
}
}
}
results.total_dependents = results.workflows.length + results.scripts.length;
return results;
}
// ═══════════════════════════════════════════════
// ③ YAML 语法扫描
// ═══════════════════════════════════════════════
function scanYAMLSyntax() {
const results = {
total: 0,
valid: 0,
issues: []
};
if (!fs.existsSync(WORKFLOWS_DIR)) {
results.issues.push({ file: '.github/workflows/', error: 'Directory not found' });
return results;
}
const files = fs.readdirSync(WORKFLOWS_DIR).filter(f => f.endsWith('.yml') || f.endsWith('.yaml'));
results.total = files.length;
for (const file of files) {
const filePath = path.join(WORKFLOWS_DIR, file);
try {
const content = fs.readFileSync(filePath, 'utf8');
// Basic YAML structural checks (without full YAML parser)
const lines = content.split('\n');
let hasName = false;
let hasOn = false;
let hasJobs = false;
let tabIssues = 0;
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
if (/^name:/.test(line)) hasName = true;
if (/^on:/.test(line) || /^'on':/.test(line) || /^"on":/.test(line)) hasOn = true;
if (/^jobs:/.test(line)) hasJobs = true;
// Check for tabs (YAML should use spaces)
if (line.includes('\t')) tabIssues++;
}
const fileIssues = [];
if (!hasName) fileIssues.push('Missing top-level "name:" field');
if (!hasOn) fileIssues.push('Missing top-level "on:" trigger');
if (!hasJobs) fileIssues.push('Missing top-level "jobs:" section');
if (tabIssues > 0) fileIssues.push(`${tabIssues} line(s) contain tabs (use spaces)`);
if (fileIssues.length > 0) {
results.issues.push({ file, errors: fileIssues });
} else {
results.valid++;
}
} catch (e) {
results.issues.push({ file, errors: [`Read error: ${e.message}`] });
}
}
return results;
}
// ═══════════════════════════════════════════════
// ④ 生成审计报告
// ═══════════════════════════════════════════════
function run() {
console.log('╔════════════════════════════════════════════════════════╗');
console.log('║ 🛰️ 天眼全域系统审计 (Sky-Eye Audit) ║');
console.log('║ TCS-0002∞ → PER-ZY001 ║');
console.log('╚════════════════════════════════════════════════════════╝');
console.log('');
const auditReport = {
audit_id: `AUDIT-${getDateStr()}`,
timestamp: getTimestamp(),
system: 'SYS-GLW-0001',
guardian: 'PER-ZY001',
controller: 'TCS-0002∞',
credential_audit: auditCredential(),
dependency_scan: scanCredentialDependencies(),
yaml_scan: scanYAMLSyntax(),
summary: {}
};
// Summary
const credStatus = auditReport.credential_audit.status;
const yamlIssues = auditReport.yaml_scan.issues.length;
const totalDeps = auditReport.dependency_scan.total_dependents;
auditReport.summary = {
credential_status: credStatus,
yaml_issues: yamlIssues,
yaml_total: auditReport.yaml_scan.total,
yaml_valid: auditReport.yaml_scan.valid,
total_credential_dependents: totalDeps,
overall_status: credStatus === 'pass' && yamlIssues === 0 ? 'GREEN' : 'RED'
};
// Console output
console.log(`[Sky-Eye Audit] Credential status: ${credStatus}`);
if (auditReport.credential_audit.report) {
console.log('');
console.log(auditReport.credential_audit.report);
}
console.log('');
console.log(`[Sky-Eye Audit] Credential dependents: ${totalDeps} (${auditReport.dependency_scan.workflows.length} workflows, ${auditReport.dependency_scan.scripts.length} scripts)`);
console.log(`[Sky-Eye Audit] YAML scan: ${auditReport.yaml_scan.valid}/${auditReport.yaml_scan.total} valid`);
if (yamlIssues > 0) {
console.log(`[Sky-Eye Audit] YAML issues found in ${yamlIssues} file(s):`);
for (const issue of auditReport.yaml_scan.issues) {
console.log(` - ${issue.file}: ${issue.errors ? issue.errors.join('; ') : issue.error}`);
}
}
console.log('');
console.log(`[Sky-Eye Audit] Overall: ${auditReport.summary.overall_status === 'GREEN' ? '✅ 绿色信号' : '🔴 逻辑红区'}`);
// Write report to System_Logs/
if (!fs.existsSync(SYSTEM_LOGS_DIR)) {
fs.mkdirSync(SYSTEM_LOGS_DIR, { recursive: true });
}
const reportPath = path.join(SYSTEM_LOGS_DIR, `credential-audit-${getDateStr()}.json`);
// Ensure no credential data leaks into the report
const safeReport = JSON.parse(JSON.stringify(auditReport));
if (safeReport.credential_audit && safeReport.credential_audit.diagnostics) {
delete safeReport.credential_audit.diagnostics.context_at_error;
}
fs.writeFileSync(reportPath, JSON.stringify(safeReport, null, 2) + '\n', 'utf8');
console.log(`[Sky-Eye Audit] Report saved: ${reportPath}`);
// Also write to skyeye logs
const skyeyeLogDir = path.join(ROOT, 'skyeye/logs/daily');
if (!fs.existsSync(skyeyeLogDir)) {
fs.mkdirSync(skyeyeLogDir, { recursive: true });
}
const skyeyeLogPath = path.join(skyeyeLogDir, `credential-audit-${getDateStr()}.json`);
fs.writeFileSync(skyeyeLogPath, JSON.stringify(safeReport, null, 2) + '\n', 'utf8');
console.log('');
console.log('---AUDIT_RESULT_JSON---');
console.log(JSON.stringify(safeReport, null, 2));
return auditReport;
}
run();