new advisories work and features gaps work

This commit is contained in:
master
2026-01-14 18:39:19 +02:00
parent 95d5898650
commit 15aeac8e8b
148 changed files with 16731 additions and 554 deletions

View File

@@ -232,7 +232,14 @@ internal sealed record PolicyEvaluationReachability(
bool HasRuntimeEvidence,
string? Source,
string? Method,
string? EvidenceRef)
string? EvidenceRef,
// Sprint: SPRINT_20260112_007_POLICY_path_gate_inputs (PW-POL-002)
string? PathHash = null,
ImmutableArray<string>? NodeHashes = null,
string? EntryNodeHash = null,
string? SinkNodeHash = null,
DateTimeOffset? RuntimeEvidenceAt = null,
bool? ObservedAtRuntime = null)
{
/// <summary>
/// Default unknown reachability state.

View File

@@ -117,6 +117,38 @@ public sealed record ReachabilityInput
/// Raw reachability score from advanced engine.
/// </summary>
public double? AdvancedScore { get; init; }
// --- Sprint: SPRINT_20260112_007_POLICY_path_gate_inputs (PW-POL-001) ---
/// <summary>
/// Canonical path hash (sha256:hex) for the reachability path.
/// </summary>
public string? PathHash { get; init; }
/// <summary>
/// Node hashes for symbols along the path (top-K for efficiency).
/// </summary>
public IReadOnlyList<string>? NodeHashes { get; init; }
/// <summary>
/// Entry point node hash.
/// </summary>
public string? EntryNodeHash { get; init; }
/// <summary>
/// Sink (vulnerable function) node hash.
/// </summary>
public string? SinkNodeHash { get; init; }
/// <summary>
/// Timestamp when runtime evidence was last captured (for freshness checks).
/// </summary>
public DateTimeOffset? RuntimeEvidenceAt { get; init; }
/// <summary>
/// Whether the path was observed at runtime (not just static analysis).
/// </summary>
public bool? ObservedAtRuntime { get; init; }
}
/// <summary>

View File

@@ -0,0 +1,301 @@
// <copyright file="VexOverrideSignals.cs" company="StellaOps">
// SPDX-License-Identifier: AGPL-3.0-or-later
// Sprint: SPRINT_20260112_004_POLICY_signed_override_enforcement (POL-OVR-001, POL-OVR-002)
// </copyright>
using System.Collections.Immutable;
using System.Text.Json.Serialization;
namespace StellaOps.Policy.Engine.Vex;
/// <summary>
/// VEX override signature validation result for policy evaluation.
/// </summary>
public sealed record VexOverrideSignalInput
{
/// <summary>
/// Whether the override is signed with a valid DSSE envelope.
/// </summary>
[JsonPropertyName("overrideSigned")]
public required bool OverrideSigned { get; init; }
/// <summary>
/// Whether the override has verified Rekor inclusion proof.
/// </summary>
[JsonPropertyName("overrideRekorVerified")]
public required bool OverrideRekorVerified { get; init; }
/// <summary>
/// Signing key ID if signed.
/// </summary>
[JsonPropertyName("signingKeyId")]
public string? SigningKeyId { get; init; }
/// <summary>
/// Issuer identity from the signature.
/// </summary>
[JsonPropertyName("signerIdentity")]
public string? SignerIdentity { get; init; }
/// <summary>
/// DSSE envelope digest if signed.
/// </summary>
[JsonPropertyName("envelopeDigest")]
public string? EnvelopeDigest { get; init; }
/// <summary>
/// Rekor log index if verified.
/// </summary>
[JsonPropertyName("rekorLogIndex")]
public long? RekorLogIndex { get; init; }
/// <summary>
/// Rekor integrated time (Unix seconds) if verified.
/// </summary>
[JsonPropertyName("rekorIntegratedTime")]
public long? RekorIntegratedTime { get; init; }
/// <summary>
/// Override validity period (start).
/// </summary>
[JsonPropertyName("validFrom")]
public DateTimeOffset? ValidFrom { get; init; }
/// <summary>
/// Override validity period (end).
/// </summary>
[JsonPropertyName("validUntil")]
public DateTimeOffset? ValidUntil { get; init; }
/// <summary>
/// Whether the override is currently within its validity period.
/// </summary>
[JsonPropertyName("withinValidityPeriod")]
public required bool WithinValidityPeriod { get; init; }
/// <summary>
/// Trust level of the signing key (trusted, unknown, revoked).
/// </summary>
[JsonPropertyName("keyTrustLevel")]
public required VexKeyTrustLevel KeyTrustLevel { get; init; }
/// <summary>
/// Validation error message if failed.
/// </summary>
[JsonPropertyName("validationError")]
public string? ValidationError { get; init; }
}
/// <summary>
/// Trust level of a signing key.
/// </summary>
[JsonConverter(typeof(JsonStringEnumConverter))]
public enum VexKeyTrustLevel
{
/// <summary>Key is in trusted keyring.</summary>
Trusted,
/// <summary>Key is not in keyring but signature is valid.</summary>
Unknown,
/// <summary>Key has been revoked.</summary>
Revoked,
/// <summary>Key trust could not be determined (offline mode).</summary>
Unavailable
}
/// <summary>
/// Override enforcement policy configuration.
/// </summary>
public sealed record VexOverrideEnforcementPolicy
{
/// <summary>
/// Require signed overrides (reject unsigned).
/// </summary>
[JsonPropertyName("requireSigned")]
public bool RequireSigned { get; init; } = true;
/// <summary>
/// Require Rekor verification.
/// </summary>
[JsonPropertyName("requireRekorVerified")]
public bool RequireRekorVerified { get; init; }
/// <summary>
/// Allow unknown keys (not in keyring) if signature is valid.
/// </summary>
[JsonPropertyName("allowUnknownKeys")]
public bool AllowUnknownKeys { get; init; }
/// <summary>
/// Maximum age for override validity (zero = no limit).
/// </summary>
[JsonPropertyName("maxOverrideAge")]
public TimeSpan MaxOverrideAge { get; init; } = TimeSpan.Zero;
/// <summary>
/// Allowed signer identities (empty = all allowed).
/// </summary>
[JsonPropertyName("allowedSigners")]
public ImmutableArray<string> AllowedSigners { get; init; } = [];
}
/// <summary>
/// Result of VEX override enforcement check.
/// </summary>
public sealed record VexOverrideEnforcementResult
{
/// <summary>
/// Whether the override is allowed by policy.
/// </summary>
[JsonPropertyName("allowed")]
public required bool Allowed { get; init; }
/// <summary>
/// Reason if rejected.
/// </summary>
[JsonPropertyName("rejectionReason")]
public string? RejectionReason { get; init; }
/// <summary>
/// Enforcement rule that triggered rejection.
/// </summary>
[JsonPropertyName("enforcementRule")]
public string? EnforcementRule { get; init; }
/// <summary>
/// The input signals used for evaluation.
/// </summary>
[JsonPropertyName("signals")]
public required VexOverrideSignalInput Signals { get; init; }
/// <summary>
/// Creates an allowed result.
/// </summary>
public static VexOverrideEnforcementResult Allow(VexOverrideSignalInput signals) => new()
{
Allowed = true,
Signals = signals
};
/// <summary>
/// Creates a rejected result.
/// </summary>
public static VexOverrideEnforcementResult Reject(
VexOverrideSignalInput signals,
string reason,
string rule) => new()
{
Allowed = false,
RejectionReason = reason,
EnforcementRule = rule,
Signals = signals
};
}
/// <summary>
/// Service for validating VEX override signatures and enforcing policy.
/// </summary>
public interface IVexOverrideSignatureValidator
{
/// <summary>
/// Validates override signature and produces policy signals.
/// </summary>
Task<VexOverrideSignalInput> ValidateSignatureAsync(
string envelopeBase64,
CancellationToken cancellationToken = default);
/// <summary>
/// Checks if override is allowed by enforcement policy.
/// </summary>
VexOverrideEnforcementResult CheckEnforcement(
VexOverrideSignalInput signals,
VexOverrideEnforcementPolicy policy,
DateTimeOffset evaluationTime);
}
/// <summary>
/// Factory for creating VEX override signal inputs.
/// </summary>
public static class VexOverrideSignalFactory
{
/// <summary>
/// Creates a signal input for an unsigned override.
/// </summary>
public static VexOverrideSignalInput CreateUnsigned() => new()
{
OverrideSigned = false,
OverrideRekorVerified = false,
WithinValidityPeriod = true,
KeyTrustLevel = VexKeyTrustLevel.Unavailable
};
/// <summary>
/// Creates a signal input for a signed but unverified override.
/// </summary>
public static VexOverrideSignalInput CreateSignedUnverified(
string signingKeyId,
string? signerIdentity,
string envelopeDigest,
VexKeyTrustLevel keyTrustLevel,
DateTimeOffset? validFrom,
DateTimeOffset? validUntil,
DateTimeOffset evaluationTime) => new()
{
OverrideSigned = true,
OverrideRekorVerified = false,
SigningKeyId = signingKeyId,
SignerIdentity = signerIdentity,
EnvelopeDigest = envelopeDigest,
KeyTrustLevel = keyTrustLevel,
ValidFrom = validFrom,
ValidUntil = validUntil,
WithinValidityPeriod = IsWithinValidityPeriod(validFrom, validUntil, evaluationTime)
};
/// <summary>
/// Creates a signal input for a fully verified override with Rekor inclusion.
/// </summary>
public static VexOverrideSignalInput CreateFullyVerified(
string signingKeyId,
string? signerIdentity,
string envelopeDigest,
VexKeyTrustLevel keyTrustLevel,
long rekorLogIndex,
long rekorIntegratedTime,
DateTimeOffset? validFrom,
DateTimeOffset? validUntil,
DateTimeOffset evaluationTime) => new()
{
OverrideSigned = true,
OverrideRekorVerified = true,
SigningKeyId = signingKeyId,
SignerIdentity = signerIdentity,
EnvelopeDigest = envelopeDigest,
RekorLogIndex = rekorLogIndex,
RekorIntegratedTime = rekorIntegratedTime,
KeyTrustLevel = keyTrustLevel,
ValidFrom = validFrom,
ValidUntil = validUntil,
WithinValidityPeriod = IsWithinValidityPeriod(validFrom, validUntil, evaluationTime)
};
private static bool IsWithinValidityPeriod(
DateTimeOffset? validFrom,
DateTimeOffset? validUntil,
DateTimeOffset evaluationTime)
{
if (validFrom.HasValue && evaluationTime < validFrom.Value)
{
return false;
}
if (validUntil.HasValue && evaluationTime > validUntil.Value)
{
return false;
}
return true;
}
}