* docs: prepare Vendoo 1.41.2 git-ignore boundary hotfix * fix: track native source modules with root-anchored runtime ignores
106 lines
4.5 KiB
JavaScript
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}`));
|