old sprints work, new sprints for exposing functionality via cli, improve code_of_conduct and other agents instructions
This commit is contained in:
@@ -269,6 +269,9 @@ public sealed class EvidenceBundleExporter : IEvidenceBundleExporter
|
||||
sb.AppendLine(" │ ├── manifest.json");
|
||||
sb.AppendLine(" │ ├── sbom.cdx.json");
|
||||
sb.AppendLine(" │ ├── reachability.json");
|
||||
sb.AppendLine(" │ ├── binary-diff.json # Binary diff evidence");
|
||||
sb.AppendLine(" │ ├── binary-diff.dsse.json # Signed binary diff (if attested)");
|
||||
sb.AppendLine(" │ ├── delta-proof.json # Semantic diff summary");
|
||||
sb.AppendLine(" │ ├── vex/");
|
||||
sb.AppendLine(" │ ├── attestations/");
|
||||
sb.AppendLine(" │ ├── policy/");
|
||||
@@ -359,6 +362,42 @@ public sealed class EvidenceBundleExporter : IEvidenceBundleExporter
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
// Binary diff evidence - Sprint: SPRINT_20260112_009_SCANNER_binary_diff_bundle_export (BINDIFF-SCAN-002)
|
||||
if (evidence.BinaryDiff is not null)
|
||||
{
|
||||
await AddJsonFileAsync("binary-diff.json", evidence.BinaryDiff, streams, entries, ct)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
// Add DSSE-signed binary diff if attestation refs are present
|
||||
if (evidence.BinaryDiff.AttestationRef is not null)
|
||||
{
|
||||
var dsseWrapper = new
|
||||
{
|
||||
payloadType = "application/vnd.stellaops.binary-diff+json",
|
||||
payload = evidence.BinaryDiff,
|
||||
attestationRef = evidence.BinaryDiff.AttestationRef
|
||||
};
|
||||
await AddJsonFileAsync("binary-diff.dsse.json", dsseWrapper, streams, entries, ct)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
// Add delta proof summary for semantic fingerprint changes
|
||||
if (evidence.BinaryDiff.SemanticDiff is not null)
|
||||
{
|
||||
var deltaProof = new
|
||||
{
|
||||
previousFingerprint = evidence.BinaryDiff.SemanticDiff.PreviousFingerprint,
|
||||
currentFingerprint = evidence.BinaryDiff.SemanticDiff.CurrentFingerprint,
|
||||
similarityScore = evidence.BinaryDiff.SemanticDiff.SimilarityScore,
|
||||
semanticChanges = evidence.BinaryDiff.SemanticDiff.SemanticChanges,
|
||||
functionChangeCount = evidence.BinaryDiff.FunctionChangeCount,
|
||||
securityChangeCount = evidence.BinaryDiff.SecurityChangeCount
|
||||
};
|
||||
await AddJsonFileAsync("delta-proof.json", deltaProof, streams, entries, ct)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
// Policy evidence
|
||||
if (evidence.Policy is not null)
|
||||
{
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// PrAnnotationService.cs
|
||||
// Sprint: SPRINT_3700_0005_0001_witness_ui_cli
|
||||
// Tasks: PR-001, PR-002
|
||||
// Sprint: SPRINT_20260112_007_SCANNER_pr_mr_annotations (SCANNER-PR-002)
|
||||
// Tasks: PR-001, PR-002, SCANNER-PR-002
|
||||
// Description: Service for generating PR annotations with reachability state flips.
|
||||
// Updated: ASCII-only output, evidence anchors (attestation digest, witness id, policy verdict)
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
using StellaOps.Scanner.Reachability;
|
||||
@@ -114,6 +116,47 @@ public sealed record StateFlipSummary
|
||||
/// Individual state flips.
|
||||
/// </summary>
|
||||
public required IReadOnlyList<StateFlip> Flips { get; init; }
|
||||
|
||||
// Sprint: SPRINT_20260112_007_SCANNER_pr_mr_annotations (SCANNER-PR-002)
|
||||
// Evidence anchor fields
|
||||
|
||||
/// <summary>
|
||||
/// DSSE attestation digest for the head scan.
|
||||
/// </summary>
|
||||
public string? AttestationDigest { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Policy verdict for the PR (pass/fail/warn).
|
||||
/// </summary>
|
||||
public string? PolicyVerdict { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Policy verdict reason code.
|
||||
/// </summary>
|
||||
public string? PolicyReasonCode { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Verify command for reproducibility.
|
||||
/// </summary>
|
||||
public string? VerifyCommand { get; init; }
|
||||
}
|
||||
/// </summary>
|
||||
public required int NetChange { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether this PR should be blocked based on policy.
|
||||
/// </summary>
|
||||
public required bool ShouldBlockPr { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Human-readable summary.
|
||||
/// </summary>
|
||||
public required string Summary { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Individual state flips.
|
||||
/// </summary>
|
||||
public required IReadOnlyList<StateFlip> Flips { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -321,29 +364,57 @@ public sealed class PrAnnotationService : IPrAnnotationService
|
||||
{
|
||||
var sb = new System.Text.StringBuilder();
|
||||
|
||||
// Header
|
||||
sb.AppendLine("## 🔍 Reachability Analysis");
|
||||
// Sprint: SPRINT_20260112_007_SCANNER_pr_mr_annotations (SCANNER-PR-002)
|
||||
// ASCII-only output with evidence anchors
|
||||
|
||||
// Header (ASCII-only)
|
||||
sb.AppendLine("## Reachability Analysis");
|
||||
sb.AppendLine();
|
||||
|
||||
// Status badge
|
||||
// Status badge (ASCII-only)
|
||||
if (summary.ShouldBlockPr)
|
||||
{
|
||||
sb.AppendLine("⛔ **Status: BLOCKING** - New reachable vulnerabilities detected");
|
||||
sb.AppendLine("[BLOCKING] **Status: BLOCKING** - New reachable vulnerabilities detected");
|
||||
}
|
||||
else if (summary.NewRiskCount > 0)
|
||||
{
|
||||
sb.AppendLine("⚠️ **Status: WARNING** - Reachability changes detected");
|
||||
sb.AppendLine("[WARNING] **Status: WARNING** - Reachability changes detected");
|
||||
}
|
||||
else if (summary.MitigatedCount > 0)
|
||||
{
|
||||
sb.AppendLine("✅ **Status: IMPROVED** - Vulnerabilities became unreachable");
|
||||
sb.AppendLine("[OK] **Status: IMPROVED** - Vulnerabilities became unreachable");
|
||||
}
|
||||
else
|
||||
{
|
||||
sb.AppendLine("✅ **Status: NO CHANGE** - No reachability changes");
|
||||
sb.AppendLine("[OK] **Status: NO CHANGE** - No reachability changes");
|
||||
}
|
||||
sb.AppendLine();
|
||||
|
||||
// Evidence anchors section (SCANNER-PR-002)
|
||||
if (!string.IsNullOrEmpty(summary.AttestationDigest) ||
|
||||
!string.IsNullOrEmpty(summary.PolicyVerdict) ||
|
||||
!string.IsNullOrEmpty(summary.VerifyCommand))
|
||||
{
|
||||
sb.AppendLine("### Evidence");
|
||||
sb.AppendLine();
|
||||
if (!string.IsNullOrEmpty(summary.AttestationDigest))
|
||||
{
|
||||
sb.AppendLine($"- **Attestation**: `{summary.AttestationDigest}`");
|
||||
}
|
||||
if (!string.IsNullOrEmpty(summary.PolicyVerdict))
|
||||
{
|
||||
var reasonPart = !string.IsNullOrEmpty(summary.PolicyReasonCode)
|
||||
? $" ({summary.PolicyReasonCode})"
|
||||
: "";
|
||||
sb.AppendLine($"- **Policy Verdict**: {summary.PolicyVerdict}{reasonPart}");
|
||||
}
|
||||
if (!string.IsNullOrEmpty(summary.VerifyCommand))
|
||||
{
|
||||
sb.AppendLine($"- **Verify**: `{summary.VerifyCommand}`");
|
||||
}
|
||||
sb.AppendLine();
|
||||
}
|
||||
|
||||
// Summary stats
|
||||
sb.AppendLine("### Summary");
|
||||
sb.AppendLine($"| Metric | Count |");
|
||||
@@ -353,7 +424,7 @@ public sealed class PrAnnotationService : IPrAnnotationService
|
||||
sb.AppendLine($"| Net Change | {(summary.NetChange >= 0 ? "+" : "")}{summary.NetChange} |");
|
||||
sb.AppendLine();
|
||||
|
||||
// Flips table
|
||||
// Flips table (ASCII-only, deterministic ordering)
|
||||
if (summary.Flips.Count > 0)
|
||||
{
|
||||
sb.AppendLine("### State Flips");
|
||||
@@ -361,22 +432,29 @@ public sealed class PrAnnotationService : IPrAnnotationService
|
||||
sb.AppendLine("| CVE | Package | Change | Confidence | Witness |");
|
||||
sb.AppendLine("|-----|---------|--------|------------|---------|");
|
||||
|
||||
foreach (var flip in summary.Flips.Take(20)) // Limit to 20 entries
|
||||
// Deterministic ordering: became reachable first, then by CVE ID
|
||||
var orderedFlips = summary.Flips
|
||||
.OrderByDescending(f => f.FlipType == StateFlipType.BecameReachable)
|
||||
.ThenBy(f => f.CveId, StringComparer.Ordinal)
|
||||
.Take(20);
|
||||
|
||||
foreach (var flip in orderedFlips)
|
||||
{
|
||||
var changeIcon = flip.FlipType switch
|
||||
// ASCII-only change indicators
|
||||
var changeText = flip.FlipType switch
|
||||
{
|
||||
StateFlipType.BecameReachable => "🔴 Became Reachable",
|
||||
StateFlipType.BecameUnreachable => "🟢 Became Unreachable",
|
||||
StateFlipType.TierIncreased => "🟡 Tier ↑",
|
||||
StateFlipType.TierDecreased => "🟢 Tier ↓",
|
||||
_ => "?"
|
||||
StateFlipType.BecameReachable => "[+] Became Reachable",
|
||||
StateFlipType.BecameUnreachable => "[-] Became Unreachable",
|
||||
StateFlipType.TierIncreased => "[^] Tier Increased",
|
||||
StateFlipType.TierDecreased => "[v] Tier Decreased",
|
||||
_ => "[?]"
|
||||
};
|
||||
|
||||
var witnessLink = !string.IsNullOrEmpty(flip.WitnessId)
|
||||
? $"[View](?witness={flip.WitnessId})"
|
||||
: "-";
|
||||
|
||||
sb.AppendLine($"| {flip.CveId} | `{TruncatePurl(flip.Purl)}` | {changeIcon} | {flip.NewTier} | {witnessLink} |");
|
||||
sb.AppendLine($"| {flip.CveId} | `{TruncatePurl(flip.Purl)}` | {changeText} | {flip.NewTier} | {witnessLink} |");
|
||||
}
|
||||
|
||||
if (summary.Flips.Count > 20)
|
||||
@@ -454,7 +532,15 @@ public sealed class PrAnnotationService : IPrAnnotationService
|
||||
{
|
||||
var annotations = new List<InlineAnnotation>();
|
||||
|
||||
foreach (var flip in flips.Where(f => !string.IsNullOrEmpty(f.FilePath) && f.LineNumber > 0))
|
||||
// Sprint: SPRINT_20260112_007_SCANNER_pr_mr_annotations (SCANNER-PR-002)
|
||||
// Deterministic ordering and ASCII-only output
|
||||
var orderedFlips = flips
|
||||
.Where(f => !string.IsNullOrEmpty(f.FilePath) && f.LineNumber > 0)
|
||||
.OrderByDescending(f => f.FlipType == StateFlipType.BecameReachable)
|
||||
.ThenBy(f => f.FilePath, StringComparer.Ordinal)
|
||||
.ThenBy(f => f.LineNumber);
|
||||
|
||||
foreach (var flip in orderedFlips)
|
||||
{
|
||||
var level = flip.FlipType switch
|
||||
{
|
||||
@@ -465,17 +551,18 @@ public sealed class PrAnnotationService : IPrAnnotationService
|
||||
_ => AnnotationLevel.Notice
|
||||
};
|
||||
|
||||
// ASCII-only titles (no emoji)
|
||||
var title = flip.FlipType switch
|
||||
{
|
||||
StateFlipType.BecameReachable => $"🔴 {flip.CveId} is now reachable",
|
||||
StateFlipType.BecameUnreachable => $"🟢 {flip.CveId} is no longer reachable",
|
||||
StateFlipType.TierIncreased => $"🟡 {flip.CveId} reachability increased",
|
||||
StateFlipType.TierDecreased => $"🟢 {flip.CveId} reachability decreased",
|
||||
StateFlipType.BecameReachable => $"[+] {flip.CveId} is now reachable",
|
||||
StateFlipType.BecameUnreachable => $"[-] {flip.CveId} is no longer reachable",
|
||||
StateFlipType.TierIncreased => $"[^] {flip.CveId} reachability increased",
|
||||
StateFlipType.TierDecreased => $"[v] {flip.CveId} reachability decreased",
|
||||
_ => flip.CveId
|
||||
};
|
||||
|
||||
var message = $"Package: {flip.Purl}\n" +
|
||||
$"Confidence: {flip.PreviousTier ?? "N/A"} → {flip.NewTier}\n" +
|
||||
$"Confidence: {flip.PreviousTier ?? "N/A"} -> {flip.NewTier}\n" +
|
||||
(flip.Entrypoint != null ? $"Entrypoint: {flip.Entrypoint}\n" : "") +
|
||||
(flip.WitnessId != null ? $"Witness: {flip.WitnessId}" : "");
|
||||
|
||||
|
||||
Reference in New Issue
Block a user