clienvy
Version:
Secret Detection → Validation → Environment Migration → Code Remediation
92 lines (78 loc) • 3.15 kB
JavaScript
import chalk from 'chalk';
import fs from 'fs-extra';
import path from 'path';
import { findFiles } from '../core/scanners/fileScanner.js';
import { scanFiles } from '../core/scanners/contentScanner.js';
import { loadSecrets } from '../core/utils/secretsStore.js';
import { getEnvPath } from '../core/utils/paths.js';
import { filterValidated } from '../core/validators/Validate.js';
function parseEnvFile(content) {
const keys = new Set();
for (const line of content.split('\n')) {
const trimmed = line.trim();
if (!trimmed || trimmed.startsWith('#')) continue;
const eq = trimmed.indexOf('=');
if (eq > 0) keys.add(trimmed.slice(0, eq).trim());
}
return keys;
}
function secretsRequireEnv(secrets) {
if (!secrets.length) return false;
const validated = filterValidated(secrets.filter((s) => s.confidence != null));
const extracted = secrets.filter((s) => s.confidence == null && s.value);
return validated.length > 0 || extracted.length > 0;
}
export async function checkCommand(cwd = process.cwd()) {
let failed = false;
const envPath = getEnvPath(cwd);
const envExists = await fs.pathExists(envPath);
const secrets = await loadSecrets(cwd);
const validated = filterValidated(secrets.filter((s) => s.confidence != null));
const requiresEnv = secretsRequireEnv(secrets);
const files = await findFiles(cwd);
const newMatches = await scanFiles(files, cwd);
if (newMatches.length > 0) {
const known = new Set(secrets.map((s) => `${s.file}:${s.line}:${s.value}`));
const unknown = newMatches.filter((m) => !known.has(`${m.file}:${m.line}:${m.value}`));
if (unknown.length > 0) {
console.log(chalk.red(`New hardcoded secrets detected: ${unknown.length}`));
for (const u of unknown.slice(0, 5)) {
console.log(chalk.red(` ${u.file}:${u.line} (${u.type})`));
}
failed = true;
}
}
if (requiresEnv && !envExists) {
console.log(
chalk.red('Secrets found in storage/secrets.json but .env is missing. Run: clenv generate')
);
failed = true;
}
if (envExists && requiresEnv) {
const envKeys = parseEnvFile(await fs.readFile(envPath, 'utf8'));
const requiredKeys = new Set(
secrets.filter((s) => s.envKey && (s.confidence ?? 0) >= 50).map((s) => s.envKey)
);
for (const key of requiredKeys) {
if (!envKeys.has(key)) {
console.log(chalk.red(`Missing env var in .env: ${key}`));
failed = true;
}
}
const secretKeys = new Set(secrets.map((s) => s.envKey).filter(Boolean));
for (const key of envKeys) {
if (!secretKeys.has(key) && !key.startsWith('NODE_')) {
console.log(chalk.yellow(`Unused env var: ${key}`));
}
}
} else if (!requiresEnv && !envExists) {
console.log(chalk.gray('No secrets tracked — .env not required.'));
}
if (failed) {
console.log(chalk.red('\nCheck failed — commit blocked.\n'));
process.exitCode = 1;
return false;
}
console.log(chalk.green('Check passed — no issues found.'));
return true;
}