mirror of
https://github.com/Studio-Saelix/sencho.git
synced 2026-08-09 10:21:03 +00:00
a95bf1ff33
Extends Trivy scans with secret detection in image filesystems and misconfiguration scanning for Compose stacks. Adds tabs to the scan drawer for vulnerabilities, secrets, and misconfigs. Secret matches are redacted server-side (first 8 chars + ellipsis) before storage. - TrivyService: --scanners vuln,secret for images; trivy config for stacks - DB: scanners_used/secret_count/misconfig_count cols; secret_findings, misconfig_findings tables; cache key scoped by scanners - Routes: POST /security/scan accepts scanners array (requirePaid when secret requested); POST /security/scan/stack; GET .../secrets and .../misconfigs (paid-tier reads) - UI: tabs in VulnerabilityScanSheet; scan-options dropdown on images; Scan config button on stack header
77 lines
2.6 KiB
TypeScript
77 lines
2.6 KiB
TypeScript
/**
|
|
* Verifies the `scanners_used` dimension of the digest cache key.
|
|
*
|
|
* A vuln-only row must not satisfy a lookup for a vuln+secret scan; otherwise
|
|
* a secret-scan request would silently reuse a stale finding set.
|
|
*/
|
|
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
|
|
import { setupTestDb, cleanupTestDb } from './helpers/setupTestDb';
|
|
|
|
let tmpDir: string;
|
|
let DatabaseService: typeof import('../services/DatabaseService').DatabaseService;
|
|
|
|
beforeAll(async () => {
|
|
tmpDir = await setupTestDb();
|
|
({ DatabaseService } = await import('../services/DatabaseService'));
|
|
});
|
|
|
|
afterAll(() => {
|
|
cleanupTestDb(tmpDir);
|
|
});
|
|
|
|
function seedScan(overrides: Partial<{ scanners_used: string; scanned_at: number }> = {}): number {
|
|
const db = DatabaseService.getInstance();
|
|
return db.createVulnerabilityScan({
|
|
node_id: 1,
|
|
image_ref: 'alpine:3.19',
|
|
image_digest: 'sha256:deadbeef',
|
|
scanned_at: overrides.scanned_at ?? Date.now(),
|
|
total_vulnerabilities: 0,
|
|
critical_count: 0,
|
|
high_count: 0,
|
|
medium_count: 0,
|
|
low_count: 0,
|
|
unknown_count: 0,
|
|
fixable_count: 0,
|
|
secret_count: 0,
|
|
misconfig_count: 0,
|
|
scanners_used: overrides.scanners_used ?? 'vuln',
|
|
highest_severity: null,
|
|
os_info: 'alpine 3.19',
|
|
trivy_version: '0.56.0',
|
|
scan_duration_ms: 1200,
|
|
triggered_by: 'manual',
|
|
status: 'completed',
|
|
error: null,
|
|
stack_context: null,
|
|
});
|
|
}
|
|
|
|
describe('getLatestScanByDigest scanners scoping', () => {
|
|
it('returns null for a vuln+secret lookup when only a vuln row exists', () => {
|
|
const db = DatabaseService.getInstance();
|
|
seedScan({ scanners_used: 'vuln' });
|
|
expect(
|
|
db.getLatestScanByDigest('sha256:deadbeef', 'vuln,secret'),
|
|
).toBeNull();
|
|
});
|
|
|
|
it('returns the matching row when scanners_used matches exactly', () => {
|
|
const db = DatabaseService.getInstance();
|
|
const id = seedScan({ scanners_used: 'vuln,secret', scanned_at: Date.now() + 10 });
|
|
const row = db.getLatestScanByDigest('sha256:deadbeef', 'vuln,secret');
|
|
expect(row?.id).toBe(id);
|
|
});
|
|
|
|
it('falls back to latest completed row when scannersUsed omitted', () => {
|
|
const db = DatabaseService.getInstance();
|
|
const row = db.getLatestScanByDigest('sha256:deadbeef');
|
|
expect(row).not.toBeNull();
|
|
});
|
|
|
|
it('returns null for digest with no completed scans', () => {
|
|
const db = DatabaseService.getInstance();
|
|
expect(db.getLatestScanByDigest('sha256:notreal', 'vuln')).toBeNull();
|
|
});
|
|
});
|