mirror of
https://github.com/Studio-Saelix/sencho.git
synced 2026-09-07 07:56:05 +00:00
fix(security): tie fixable CVE posture to image-update evidence (#1815)
* fix(security): tie fixable CVE posture to image-update evidence Stop treating Trivy fixed_version alone as an Update affected images CTA. Reuse persisted ImageUpdateService status so Security only offers Review update when an applicable image update is confirmed, and otherwise surfaces waiting or uncertain remediation with truthful affordances. * fix(security): move image-update recheck helper out of OverviewTab Satisfy react-refresh/only-export-components so Frontend lint passes. * fix(security): preserve posture reason image targets in Images drill-down Carry affected image refs on overview reasons so public exposure and related CTAs open a clearable targeted Images list instead of an unfiltered hunt. * fix(security): attach Networking exposure intent to posture targets Preserve stack/service context and intentional classification on network-exposed Security reasons without suppressing risk or claiming Internet reachability. * fix(security): persist Images exposure intent and triage scope Standing image summaries carry Networking intent context with cap-safe aggregates, Anatomy Networking links, and scan-sheet triage that defaults to the current image. * fix(security): clear CI lint errors for exposure helpers * fix(security): stop intentional exposure from forcing Action needed Separate exposure fact, intent correctness, and vulnerability drivers so package fixed_version cannot recreate a permanent public_exposure blocker. * fix(security): define Monitoring residual-risk narrative * fix(security): define Secure via residual Crit/High triage Replace triage-blind raw Crit/High Secure gating with residual material risk so accepted and ignored stay Monitoring, while not_affected, false positive, and fixed can clear residual without claiming no detections. * fix(security): exclude rollback-hold images from Security scans Hold-only sencho-rb tags are recovery state; keep them out of Trivy node scans, Security inventory, and Overview posture while dual-tagged images remain under their registry tag. * fix(security): keep authoritative no-update rows after preview Opening a stack page must not delete ok+false stack_update_status evidence; Security treats a missing row as uncertain and would flip waiting-upstream to unknown.
This commit is contained in:
@@ -778,6 +778,44 @@ export class ImageUpdateService {
|
||||
};
|
||||
}
|
||||
|
||||
private static readonly MIN_REMEDIATION_FRESHNESS_MS = 30 * 60 * 1000;
|
||||
private static readonly MAX_REMEDIATION_FRESHNESS_MS = 48 * 60 * 60 * 1000;
|
||||
private static readonly CRON_CADENCE_FALLBACK_MS = 24 * 60 * 60 * 1000;
|
||||
|
||||
/**
|
||||
* Freshness window for Security remediation classification: 2× the
|
||||
* expected check cadence. Interval mode uses the live intervalMs. Cron
|
||||
* mode uses the gap between two successive fire times (not the unused
|
||||
* interval setting). Missing/unparseable cron falls back to 24h. Clamped
|
||||
* to [30m, 48h].
|
||||
*/
|
||||
public getRemediationFreshnessWindowMs(): number {
|
||||
let cadenceMs = this.intervalMs;
|
||||
if (this.mode === 'cron') {
|
||||
// Default to 24h; override only when two successive fire times yield a positive gap.
|
||||
cadenceMs = ImageUpdateService.CRON_CADENCE_FALLBACK_MS;
|
||||
if (this.cronExpression) {
|
||||
try {
|
||||
const expr = CronExpressionParser.parse(this.cronExpression);
|
||||
const first = expr.next().toDate().getTime();
|
||||
const second = expr.next().toDate().getTime();
|
||||
const gap = second - first;
|
||||
if (gap > 0) cadenceMs = gap;
|
||||
} catch (e) {
|
||||
console.warn(
|
||||
'[ImageUpdateService] Could not derive cron cadence for remediation freshness; using 24h fallback:',
|
||||
getErrorMessage(e, String(e)),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
const windowMs = 2 * cadenceMs;
|
||||
return Math.min(
|
||||
ImageUpdateService.MAX_REMEDIATION_FRESHNESS_MS,
|
||||
Math.max(ImageUpdateService.MIN_REMEDIATION_FRESHNESS_MS, windowMs),
|
||||
);
|
||||
}
|
||||
|
||||
// ─── Core check ──────────────────────────────────────────────────────────
|
||||
|
||||
private async check() {
|
||||
@@ -1230,11 +1268,14 @@ export class ImageUpdateService {
|
||||
* - If memory generation advanced after observation, abort (stale).
|
||||
* - If memory generation still equals the observation watermark, advance
|
||||
* (tombstone) so an equal-generation writer reserved before observation
|
||||
* cannot commit after the clear (SF-4).
|
||||
* cannot commit after the clear.
|
||||
* - If the persisted row generation advanced after observation, keep the row.
|
||||
* - Otherwise delete partial, failed, and confirmed ok+true rows.
|
||||
* - If the row is already ok with no update, keep it. Deleting that row
|
||||
* would make Security treat a confirmed no-update as unknown.
|
||||
* - Otherwise delete partial, failed, and ok+true rows.
|
||||
*
|
||||
* Returns cleared | stale | absent.
|
||||
* Returns cleared (row deleted), stale (memory generation raced), or
|
||||
* absent (no row, generation advanced, or the row is already ok+false).
|
||||
*/
|
||||
public async commitPreviewClear(
|
||||
nodeId: number,
|
||||
@@ -1270,6 +1311,7 @@ export class ImageUpdateService {
|
||||
// pre-preview snapshot). Memory peek resets on restart; SQLite does not.
|
||||
const rowGeneration = db.getStackUpdateWriteGeneration(nodeId, stackName);
|
||||
if (rowGeneration > observedRowGeneration) return;
|
||||
if (detail.checkStatus === 'ok' && !detail.hasUpdate) return;
|
||||
deleted = db.clearStackUpdateStatus(nodeId, stackName);
|
||||
});
|
||||
if (!committed) return 'stale';
|
||||
|
||||
Reference in New Issue
Block a user