Files
vendoo/tools/verify-git-safety.mjs
Masterluke77andGitHub 6f48827f4d Vendoo 1.41.2 – Git Ignore Boundary & Module Tracking Hotfix (#18)
* docs: prepare Vendoo 1.41.2 git-ignore boundary hotfix

* fix: track native source modules with root-anchored runtime ignores
2026-07-09 17:09:00 +02:00

106 lines
4.5 KiB
JavaScript

import { existsSync, lstatSync, readFileSync, readdirSync } from 'node:fs';
import { extname, join, relative, sep, dirname } from 'node:path';
import { fileURLToPath } from 'node:url';
const root = dirname(dirname(fileURLToPath(import.meta.url)));
const failures = [];
const warnings = [];
const normalize = value => value.split(sep).join('/').replace(/^\.\//, '');
const forbiddenPathPatterns = [
/(^|\/)node_modules(\/|$)/i,
/(^|\/)runtime(\/|$)/i,
/(^|\/)docker-data(\/|$)/i,
/(^|\/)release-output(\/|$)/i,
/(^|\/)operations\/(staging|updates|rollback|apply-[^/]+)(\/|$)/i,
/(^|\/)operations\/(pending-operation|last-operation|first-admin-code)\.json$/i,
/(^|\/)operations\/first-admin-code\.txt$/i,
/(^|\/)(uploads|backups|logs|tmp|temp)(\/|$)/i,
/^sessions(\/|$)/i,
/(^|\/)updates(\/|$)/i,
/^modules(\/|$)/i,
/(^|\/)db\/.*\.(db|sqlite|sqlite3)(-wal|-shm)?$/i,
/(^|\/)local-ai\/(comfyui|downloads|extract-temp)(\/|$)/i,
/(^|\/)\.vendoo-install-mode$/i,
/(^|\/)\.vendoo-github-state\.json$/i,
];
const allowedEnv = new Set(['.env.example', '.env.docker.example']);
const binaryExt = new Set(['.png', '.jpg', '.jpeg', '.gif', '.ico', '.webp', '.zip', '.pdf', '.woff', '.woff2']);
const secretPatterns = [
['OpenAI-/kompatibler Schlüssel', /\bsk-[A-Za-z0-9_-]{20,}\b/g],
['GitHub Classic Token', /\bgh[pousr]_[A-Za-z0-9]{30,}\b/g],
['GitHub Fine-grained Token', /\bgithub_pat_[A-Za-z0-9_]{30,}\b/g],
['Slack Token', /\bxox[baprs]-[A-Za-z0-9-]{20,}\b/g],
['Private Key', /-----BEGIN (?:RSA |EC |OPENSSH )?PRIVATE KEY-----/g],
['AWS Access Key', /\bAKIA[0-9A-Z]{16}\b/g],
];
function walk(dir, out = []) {
for (const name of readdirSync(dir)) {
if (['.git', 'node_modules', 'release-output', 'archiv', 'archive', 'archives'].includes(name.toLowerCase())) continue;
const abs = join(dir, name);
const rel = normalize(relative(root, abs));
const stat = lstatSync(abs);
if (stat.isSymbolicLink()) {
warnings.push(`Symlink wird nicht geprüft oder gepackt: ${rel}`);
continue;
}
if (stat.isDirectory()) walk(abs, out);
else out.push(rel);
}
return out;
}
// Absichtlich immer den kompletten Arbeitsbaum prüfen. Ein Git-Erstimport enthält
// viele noch unversionierte Dateien; "git ls-files" würde diese nicht erfassen.
const files = walk(root);
for (const rel of files) {
const envLike = /(^|\/)\.env(?:\..+)?$/i.test(rel);
if (envLike && !allowedEnv.has(rel)) failures.push(`Verbotene Environment-Datei: ${rel}`);
if (!allowedEnv.has(rel) && forbiddenPathPatterns.some(pattern => pattern.test(rel))) {
failures.push(`Verbotene Laufzeit-/Secret-Datei: ${rel}`);
}
const abs = join(root, rel);
if (!existsSync(abs)) continue;
const stat = lstatSync(abs);
if (stat.size > 50 * 1024 * 1024) failures.push(`Datei größer als 50 MiB: ${rel}`);
if (binaryExt.has(extname(rel).toLowerCase()) || stat.size > 5 * 1024 * 1024) continue;
let text;
try { text = readFileSync(abs, 'utf8'); } catch { continue; }
for (const [label, pattern] of secretPatterns) {
pattern.lastIndex = 0;
if (pattern.test(text)) failures.push(`${label} in ${rel}`);
}
if (!allowedEnv.has(rel)) {
const envAssignment = /^(?:ANTHROPIC_API_KEY|OPENAI_API_KEY|OPENROUTER_API_KEY|ETSY_API_KEY|EBAY_CLIENT_SECRET|SMTP_PASS|VENDOO_SETUP_TOKEN|LOCAL_IMAGE_TOKEN)=(.+)$/gm;
for (const match of text.matchAll(envAssignment)) {
const value = String(match[1] || '').trim();
if (value && !/^\$\{|^<|^CHANGEME$/i.test(value)) failures.push(`Befülltes Secret-Feld in ${rel}: ${match[0].split('=')[0]}`);
}
}
}
const gitignorePath = join(root, '.gitignore');
if (!existsSync(gitignorePath)) {
failures.push('.gitignore fehlt.');
} else {
const gitignore = readFileSync(gitignorePath, 'utf8');
for (const marker of ['.env', 'node_modules/', 'runtime/', 'modules/', '/Archiv/', '/archive/', '/archives/', 'operations/pending-operation.json', 'local-ai/comfyui/', '.vendoo-install-mode']) {
if (!gitignore.includes(marker)) failures.push(`.gitignore enthält nicht: ${marker}`);
}
}
const uniqueFailures = [...new Set(failures)];
if (uniqueFailures.length) {
console.error(`Git-Sicherheitsgate fehlgeschlagen (${uniqueFailures.length}):`);
uniqueFailures.slice(0, 100).forEach(item => console.error(`- ${item}`));
if (uniqueFailures.length > 100) console.error(`- ... ${uniqueFailures.length - 100} weitere Treffer`);
process.exit(1);
}
console.log(`Git-Sicherheitsgate erfolgreich: ${files.length} Dateien geprüft; keine Secrets oder Laufzeitdaten gefunden.`);
warnings.forEach(item => console.warn(`[WARN] ${item}`));