feat: initial open marketplace with llm-security, config-audit, ultraplan-local
This commit is contained in:
commit
f93d6abdae
380 changed files with 65935 additions and 0 deletions
124
plugins/config-audit/scanners/lib/baseline.mjs
Normal file
124
plugins/config-audit/scanners/lib/baseline.mjs
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
/**
|
||||
* Baseline manager for config-audit.
|
||||
* Stores and retrieves scanner envelopes as named baselines.
|
||||
* Zero external dependencies.
|
||||
*/
|
||||
|
||||
import { readFile, writeFile, readdir, unlink, mkdir, stat } from 'node:fs/promises';
|
||||
import { join } from 'node:path';
|
||||
import { homedir } from 'node:os';
|
||||
|
||||
const BASELINES_DIR = join(homedir(), '.config-audit', 'baselines');
|
||||
|
||||
/**
|
||||
* Get the baselines directory path.
|
||||
* @returns {string}
|
||||
*/
|
||||
export function getBaselinesDir() {
|
||||
return BASELINES_DIR;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save a scanner envelope as a named baseline.
|
||||
* @param {object} envelope - Full envelope from scan-orchestrator
|
||||
* @param {string} [name='default'] - Baseline name
|
||||
* @returns {Promise<{ path: string, name: string }>}
|
||||
*/
|
||||
export async function saveBaseline(envelope, name = 'default') {
|
||||
await mkdir(BASELINES_DIR, { recursive: true });
|
||||
|
||||
const enriched = {
|
||||
...envelope,
|
||||
_baseline: {
|
||||
saved_at: new Date().toISOString(),
|
||||
target_path: envelope.meta?.target || '',
|
||||
finding_count: envelope.aggregate?.total_findings || 0,
|
||||
score: avgScore(envelope),
|
||||
},
|
||||
};
|
||||
|
||||
const filePath = join(BASELINES_DIR, `${name}.json`);
|
||||
await writeFile(filePath, JSON.stringify(enriched, null, 2), 'utf-8');
|
||||
|
||||
return { path: filePath, name };
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a named baseline.
|
||||
* @param {string} [name='default'] - Baseline name
|
||||
* @returns {Promise<object|null>} Envelope or null if not found
|
||||
*/
|
||||
export async function loadBaseline(name = 'default') {
|
||||
const filePath = join(BASELINES_DIR, `${name}.json`);
|
||||
try {
|
||||
const content = await readFile(filePath, 'utf-8');
|
||||
return JSON.parse(content);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* List all saved baselines.
|
||||
* @returns {Promise<{ baselines: Array<{ name: string, savedAt: string, targetPath: string, findingCount: number, score: number }> }>}
|
||||
*/
|
||||
export async function listBaselines() {
|
||||
try {
|
||||
await stat(BASELINES_DIR);
|
||||
} catch {
|
||||
return { baselines: [] };
|
||||
}
|
||||
|
||||
const entries = await readdir(BASELINES_DIR);
|
||||
const baselines = [];
|
||||
|
||||
for (const entry of entries) {
|
||||
if (!entry.endsWith('.json')) continue;
|
||||
const name = entry.replace(/\.json$/, '');
|
||||
const filePath = join(BASELINES_DIR, entry);
|
||||
|
||||
try {
|
||||
const content = await readFile(filePath, 'utf-8');
|
||||
const data = JSON.parse(content);
|
||||
const meta = data._baseline || {};
|
||||
baselines.push({
|
||||
name,
|
||||
savedAt: meta.saved_at || '',
|
||||
targetPath: meta.target_path || '',
|
||||
findingCount: meta.finding_count || 0,
|
||||
score: meta.score || 0,
|
||||
});
|
||||
} catch {
|
||||
// Skip corrupt baselines
|
||||
baselines.push({ name, savedAt: '', targetPath: '', findingCount: 0, score: 0 });
|
||||
}
|
||||
}
|
||||
|
||||
return { baselines };
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a named baseline.
|
||||
* @param {string} name - Baseline name
|
||||
* @returns {Promise<{ deleted: boolean }>}
|
||||
*/
|
||||
export async function deleteBaseline(name) {
|
||||
const filePath = join(BASELINES_DIR, `${name}.json`);
|
||||
try {
|
||||
await unlink(filePath);
|
||||
return { deleted: true };
|
||||
} catch {
|
||||
return { deleted: false };
|
||||
}
|
||||
}
|
||||
|
||||
// --- Internal helpers ---
|
||||
|
||||
function avgScore(envelope) {
|
||||
const scanners = envelope.scanners || [];
|
||||
if (scanners.length === 0) return 0;
|
||||
// Simple: count findings as proxy for score
|
||||
const total = envelope.aggregate?.total_findings || 0;
|
||||
// Lower findings = higher score. Cap at 100.
|
||||
return Math.max(0, 100 - total * 3);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue