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:
Anso
2026-08-12 15:02:14 -04:00
committed by GitHub
parent c47b8eb8e9
commit fcd44f5693
49 changed files with 5596 additions and 308 deletions
+26
View File
@@ -0,0 +1,26 @@
/**
* Sencho rollback-hold image identity.
*
* Full-stack recovery tags images as `sencho-rb/<generation>/<service>:hold`.
* Those refs are Sencho-internal recovery state, not operator inventory or
* Security scan targets. Shared so Resources, Trivy, and Security agree.
*/
export const SENCHO_ROLLBACK_HOLD_PREFIX = 'sencho-rb/';
/** SQLite LIKE pattern matching any Sencho rollback-hold image_ref. */
export const SENCHO_ROLLBACK_HOLD_SQL_LIKE = `${SENCHO_ROLLBACK_HOLD_PREFIX}%`;
/** True when an image reference is a Sencho synthetic rollback-hold tag. */
export function isSenchoRollbackHoldRef(imageRef: string): boolean {
return imageRef.startsWith(SENCHO_ROLLBACK_HOLD_PREFIX);
}
/**
* True when every visible RepoTag is a synthetic hold tag (hold-only image).
* Dual-tagged images (registry tag + hold) return false so they stay visible
* under the real tag.
*/
export function isFullySyntheticHoldImage(repoTags: string[]): boolean {
return repoTags.length > 0 && repoTags.every((tag) => isSenchoRollbackHoldRef(tag));
}
+30
View File
@@ -26,6 +26,36 @@ export const DISMISSING_STATUSES: ReadonlySet<TriageStatus> = new Set([
'not_affected', 'accepted', 'fixed', 'false_positive', 'ignored',
]);
/**
* Dismissing statuses that clear Crit/High from residual Secure-gate risk.
* Accepted and ignored stay residual (operator is still carrying the risk).
* Fixed clears under the current Secure contract; re-verifying Fixed over time
* is a separate evidence-freshness concern.
*/
export const SECURE_CLEARING_STATUS_LIST = [
'not_affected', 'false_positive', 'fixed',
] as const;
export type SecureClearingStatus = typeof SECURE_CLEARING_STATUS_LIST[number];
export const SECURE_CLEARING_STATUSES: ReadonlySet<SecureClearingStatus> = new Set(
SECURE_CLEARING_STATUS_LIST,
);
type _SecureClearingIsDismissing = SecureClearingStatus extends (
'not_affected' | 'accepted' | 'fixed' | 'false_positive' | 'ignored'
) ? true : never;
const _secureClearingIsDismissing: _SecureClearingIsDismissing = true;
void _secureClearingIsDismissing;
/** True when a Crit/High finding still blocks Secure (residual material risk). */
export function countsTowardResidualCriticalHigh(
decision: { suppressed: boolean; triage_status?: TriageStatus },
): boolean {
if (!decision.suppressed) return true;
const status = decision.triage_status;
if (status == null) return true;
return !(SECURE_CLEARING_STATUS_LIST as readonly string[]).includes(status);
}
/** Optional OpenVEX-aligned justification taxonomy (never required). */
export const TRIAGE_JUSTIFICATIONS = [
'vulnerable_code_not_in_execute_path', 'vulnerable_code_not_present',