4.6 KiB
4.6 KiB
Exception System (API, Lifecycle, Policy Integration, Evidence-Backed Workflow)
Module
Policy
Status
IMPLEMENTED
Description
Full exception system: CRUD API with query by scope/owner/expiry/environment, auto-expiry with lifecycle state transitions and background workers, policy engine integration (deterministic outcome alteration with recheck gate), and auditable workflow with entity model (scope, subject, evidence refs, expiry), evidence requirement validation, and persistence (Postgres + in-memory).
Implementation Details
- ExceptionObject:
src/Policy/__Libraries/StellaOps.Policy.Exceptions/Models/ExceptionObject.cs(sealed record)- Id format:
EXC-{ulid}, Version (optimistic concurrency), Status, Type, Scope, OwnerId, RequesterId, ApproverIds, CreatedAt, UpdatedAt, ApprovedAt, ExpiresAt (required, max 1 year), ReasonCode, Rationale (min 50 chars), EvidenceRefs (sha256 or attestation URIs), EvidenceRequirements, CompensatingControls, Metadata, TicketRef, RecheckPolicyId, RecheckPolicy, LastRecheckResult, LastRecheckAt IsEffectiveAt(referenceTime)-- Active status AND not expiredHasExpiredAt(referenceTime)-- referenceTime >= ExpiresAtIsBlockedByRecheck/RequiresReapprovalcomputed properties
- Id format:
- ExceptionStatus enum: Proposed -> Approved -> Active -> Expired/Revoked (governed state machine)
- ExceptionType enum: Vulnerability, Policy, Unknown, Component
- ExceptionReason enum: FalsePositive, AcceptedRisk, CompensatingControl, TestOnly, VendorNotAffected, ScheduledFix, DeprecationInProgress, RuntimeMitigation, NetworkIsolation, Other
- ExceptionScope: ArtifactDigest, PurlPattern (wildcards:
pkg:npm/lodash@*), VulnerabilityId, PolicyRuleId, Environments, TenantId; AND logic for multiple constraints;IsValidchecks at least one constraint - ExceptionEvaluator:
src/Policy/__Libraries/StellaOps.Policy.Exceptions/Services/ExceptionEvaluator.cs(sealed class implementsIExceptionEvaluator)EvaluateAsync(FindingContext)-- queries active exceptions by scope, filters by context match (artifact, vuln, PURL pattern, policy rule, environment, tenant), orders by specificityEvaluateBatchAsync(contexts)-- evaluates multiple findings- Specificity scoring: ArtifactDigest=100, exact PURL=50, PURL pattern=20, VulnerabilityId=40, PolicyRuleId=30, Environments=10
- PURL wildcard matching via regex conversion
- Returns
ExceptionEvaluationResultwith HasException, MatchingExceptions, PrimaryReason, PrimaryRationale, AllEvidenceRefs
- EvidenceRequirementValidator:
src/Policy/__Libraries/StellaOps.Policy.Exceptions/Services/EvidenceRequirementValidator.cs-- validates evidence hooks before approval - RecheckEvaluationService:
src/Policy/__Libraries/StellaOps.Policy.Exceptions/Services/RecheckEvaluationService.cs-- 9 recheck condition types - Repositories:
IExceptionRepository/PostgresExceptionRepository:src/Policy/__Libraries/StellaOps.Policy.Exceptions/Repositories/IExceptionApplicationRepository/PostgresExceptionApplicationRepository: same directory -- audit trail persistence
- ExceptionEffectRegistry:
src/Policy/StellaOps.Policy.Engine/Adapters/ExceptionEffectRegistry.cs-- 40 type+reason -> effect mappings - ExceptionRecheckGate:
src/Policy/StellaOps.Policy.Engine/BuildGate/ExceptionRecheckGate.cs-- CI/CD build gate for recheck policies
E2E Test Plan
- Create exception with Type=Vulnerability, Scope=(VulnerabilityId="CVE-2024-1234", PurlPattern="pkg:npm/lodash@*"), Reason=FalsePositive; verify Status=Proposed
- Approve exception; verify Status transitions to Approved, ApprovedAt is set, ApproverIds populated
- Activate exception; verify Status=Active; evaluate finding with matching CVE and PURL; verify HasException=true
- Verify exception with PurlPattern="pkg:npm/lodash@*" matches "pkg:npm/lodash@4.17.21" but not "pkg:npm/underscore@1.0.0"
- Create two exceptions for same finding: one with ArtifactDigest (specificity=100) and one with VulnerabilityId (specificity=40); verify most specific is PrimaryReason
- Set exception ExpiresAt to past; call IsEffectiveAt(now); verify returns false; verify EvaluateAsync does not match
- Create exception with EnvironmentScope=["prod"]; evaluate in "dev"; verify no match
- Create exception with EvidenceRefs=["sha256:abc"]; verify AllEvidenceRefs in EvaluationResult contains the ref
- Use EvaluateBatchAsync with 3 FindingContexts; verify dictionary contains entries for indices 0, 1, 2
- Verify Scope.IsValid returns false when no constraints are set; returns true when VulnerabilityId is set