sprints completion. new product advisories prepared
This commit is contained in:
@@ -0,0 +1,137 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// AiCodeGuardOptions.cs
|
||||
// Sprint: SPRINT_20260112_010_SCANNER_ai_code_guard_core
|
||||
// Task: SCANNER-AIGUARD-001
|
||||
// Description: AI Code Guard options with deterministic defaults.
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
using System.Collections.Immutable;
|
||||
|
||||
namespace StellaOps.Scanner.AiCodeGuard;
|
||||
|
||||
/// <summary>
|
||||
/// Configuration options for AI Code Guard analysis.
|
||||
/// </summary>
|
||||
public sealed class AiCodeGuardOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Configuration section name.
|
||||
/// </summary>
|
||||
public const string SectionName = "AiCodeGuard";
|
||||
|
||||
/// <summary>
|
||||
/// Whether AI Code Guard is enabled.
|
||||
/// </summary>
|
||||
public bool Enabled { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Detection confidence threshold (0.0-1.0).
|
||||
/// Findings below this threshold are excluded.
|
||||
/// </summary>
|
||||
public double ConfidenceThreshold { get; set; } = 0.7;
|
||||
|
||||
/// <summary>
|
||||
/// Enabled detection categories.
|
||||
/// </summary>
|
||||
public IReadOnlyList<string> EnabledCategories { get; set; } = new[]
|
||||
{
|
||||
"AiGenerated",
|
||||
"InsecurePattern",
|
||||
"Hallucination",
|
||||
"LicenseRisk",
|
||||
"UntrustedDependency",
|
||||
"QualityIssue"
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Severity threshold for blocking (findings at or above this level block).
|
||||
/// </summary>
|
||||
public string BlockingSeverity { get; set; } = "High";
|
||||
|
||||
/// <summary>
|
||||
/// Maximum number of hunks to analyze per file.
|
||||
/// </summary>
|
||||
public int MaxHunksPerFile { get; set; } = 100;
|
||||
|
||||
/// <summary>
|
||||
/// Maximum total lines to analyze per scan.
|
||||
/// </summary>
|
||||
public int MaxTotalLines { get; set; } = 50000;
|
||||
|
||||
/// <summary>
|
||||
/// Path to allowlist corpus for similarity checking.
|
||||
/// </summary>
|
||||
public string? AllowlistCorpusPath { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Path to denylist corpus for similarity checking.
|
||||
/// </summary>
|
||||
public string? DenylistCorpusPath { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Similarity threshold for snippet matching (0.0-1.0).
|
||||
/// </summary>
|
||||
public double SimilarityThreshold { get; set; } = 0.85;
|
||||
|
||||
/// <summary>
|
||||
/// License hygiene configuration.
|
||||
/// </summary>
|
||||
public LicenseHygieneOptions LicenseHygiene { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// Rule sets to apply (null = all default rules).
|
||||
/// </summary>
|
||||
public IReadOnlyList<string>? RuleSets { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Scanner version identifier for reproducibility.
|
||||
/// </summary>
|
||||
public string ScannerVersion { get; set; } = "1.0.0";
|
||||
|
||||
/// <summary>
|
||||
/// Model version identifier for reproducibility.
|
||||
/// </summary>
|
||||
public string ModelVersion { get; set; } = "1.0.0";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// License hygiene check options.
|
||||
/// </summary>
|
||||
public sealed class LicenseHygieneOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Whether license hygiene checks are enabled.
|
||||
/// </summary>
|
||||
public bool Enabled { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Allowed license SPDX identifiers.
|
||||
/// </summary>
|
||||
public IReadOnlyList<string> AllowedLicenses { get; set; } = new[]
|
||||
{
|
||||
"MIT",
|
||||
"Apache-2.0",
|
||||
"BSD-2-Clause",
|
||||
"BSD-3-Clause",
|
||||
"ISC",
|
||||
"CC0-1.0",
|
||||
"Unlicense"
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Denied license SPDX identifiers (block if detected).
|
||||
/// </summary>
|
||||
public IReadOnlyList<string> DeniedLicenses { get; set; } = new[]
|
||||
{
|
||||
"GPL-2.0-only",
|
||||
"GPL-3.0-only",
|
||||
"AGPL-3.0-only",
|
||||
"LGPL-2.1-only",
|
||||
"LGPL-3.0-only"
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Action when unknown license is detected.
|
||||
/// </summary>
|
||||
public string UnknownLicenseAction { get; set; } = "RequireReview";
|
||||
}
|
||||
@@ -0,0 +1,214 @@
|
||||
// -----------------------------------------------------------------------------
|
||||
// IAiCodeGuardService.cs
|
||||
// Sprint: SPRINT_20260112_010_SCANNER_ai_code_guard_core
|
||||
// Task: SCANNER-AIGUARD-002/006
|
||||
// Description: AI Code Guard service interface for Scanner.
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
using System.Collections.Immutable;
|
||||
|
||||
namespace StellaOps.Scanner.AiCodeGuard;
|
||||
|
||||
/// <summary>
|
||||
/// Service for AI Code Guard analysis.
|
||||
/// </summary>
|
||||
public interface IAiCodeGuardService
|
||||
{
|
||||
/// <summary>
|
||||
/// Analyzes changed hunks for AI-generated code issues.
|
||||
/// </summary>
|
||||
/// <param name="request">Analysis request with hunks and options.</param>
|
||||
/// <param name="cancellationToken">Cancellation token.</param>
|
||||
/// <returns>Analysis result with findings and verdict.</returns>
|
||||
Task<AiCodeGuardAnalysisResult> AnalyzeAsync(
|
||||
AiCodeGuardAnalysisRequest request,
|
||||
CancellationToken cancellationToken = default);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Analysis request for AI Code Guard.
|
||||
/// </summary>
|
||||
public sealed record AiCodeGuardAnalysisRequest
|
||||
{
|
||||
/// <summary>
|
||||
/// Repository URI.
|
||||
/// </summary>
|
||||
public required string RepositoryUri { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Commit SHA being analyzed.
|
||||
/// </summary>
|
||||
public required string CommitSha { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Branch name (optional).
|
||||
/// </summary>
|
||||
public string? Branch { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Base commit for diff comparison (optional, for PR analysis).
|
||||
/// </summary>
|
||||
public string? BaseCommitSha { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Changed hunks to analyze.
|
||||
/// </summary>
|
||||
public required IReadOnlyList<CodeHunk> Hunks { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Analysis timestamp (input, not wall-clock for determinism).
|
||||
/// </summary>
|
||||
public required DateTimeOffset AnalysisTimestamp { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Optional options override (uses defaults if null).
|
||||
/// </summary>
|
||||
public AiCodeGuardOptions? Options { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A code hunk to analyze.
|
||||
/// </summary>
|
||||
public sealed record CodeHunk
|
||||
{
|
||||
/// <summary>
|
||||
/// File path relative to repository root.
|
||||
/// </summary>
|
||||
public required string FilePath { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Programming language (detected or specified).
|
||||
/// </summary>
|
||||
public required string Language { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Start line in the file (1-based).
|
||||
/// </summary>
|
||||
public required int StartLine { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// End line in the file (1-based).
|
||||
/// </summary>
|
||||
public required int EndLine { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Hunk content (source code).
|
||||
/// </summary>
|
||||
public required string Content { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether this is new code (added) vs existing.
|
||||
/// </summary>
|
||||
public required bool IsNew { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// SHA-256 hash of normalized content for deterministic hunk ID.
|
||||
/// </summary>
|
||||
public string? ContentHash { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// AI Code Guard analysis result.
|
||||
/// </summary>
|
||||
public sealed record AiCodeGuardAnalysisResult
|
||||
{
|
||||
/// <summary>
|
||||
/// Whether analysis completed successfully.
|
||||
/// </summary>
|
||||
public required bool Success { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Scanner configuration used.
|
||||
/// </summary>
|
||||
public required AiCodeGuardScannerConfigResult ScannerConfig { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Files analyzed.
|
||||
/// </summary>
|
||||
public required ImmutableList<AiCodeGuardFileResult> Files { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Detected findings.
|
||||
/// </summary>
|
||||
public required ImmutableList<AiCodeGuardFindingResult> Findings { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Overall verdict.
|
||||
/// </summary>
|
||||
public required AiCodeGuardVerdictResult Verdict { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Total lines analyzed.
|
||||
/// </summary>
|
||||
public required long TotalLinesAnalyzed { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Error message if Success is false.
|
||||
/// </summary>
|
||||
public string? Error { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Content digest for the analysis result (SHA-256).
|
||||
/// </summary>
|
||||
public string? ContentDigest { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Scanner configuration in result.
|
||||
/// </summary>
|
||||
public sealed record AiCodeGuardScannerConfigResult
|
||||
{
|
||||
public required string ScannerVersion { get; init; }
|
||||
public required string ModelVersion { get; init; }
|
||||
public required double ConfidenceThreshold { get; init; }
|
||||
public required ImmutableList<string> EnabledCategories { get; init; }
|
||||
public ImmutableList<string>? RuleSets { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// File analyzed in result.
|
||||
/// </summary>
|
||||
public sealed record AiCodeGuardFileResult
|
||||
{
|
||||
public required string Path { get; init; }
|
||||
public required string Digest { get; init; }
|
||||
public required int LineCount { get; init; }
|
||||
public string? Language { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Finding in result.
|
||||
/// </summary>
|
||||
public sealed record AiCodeGuardFindingResult
|
||||
{
|
||||
public required string Id { get; init; }
|
||||
public required string Category { get; init; }
|
||||
public required string Severity { get; init; }
|
||||
public required double Confidence { get; init; }
|
||||
public required string FilePath { get; init; }
|
||||
public required int StartLine { get; init; }
|
||||
public required int EndLine { get; init; }
|
||||
public int? StartColumn { get; init; }
|
||||
public int? EndColumn { get; init; }
|
||||
public string? Snippet { get; init; }
|
||||
public required string Description { get; init; }
|
||||
public required string RuleId { get; init; }
|
||||
public string? DetectionMethod { get; init; }
|
||||
public ImmutableList<string>? Indicators { get; init; }
|
||||
public double? PerplexityScore { get; init; }
|
||||
public ImmutableList<string>? PatternMatches { get; init; }
|
||||
public string? Remediation { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Verdict in result.
|
||||
/// </summary>
|
||||
public sealed record AiCodeGuardVerdictResult
|
||||
{
|
||||
public required string Status { get; init; }
|
||||
public required int TotalFindings { get; init; }
|
||||
public required ImmutableDictionary<string, int> FindingsBySeverity { get; init; }
|
||||
public double? AiGeneratedPercentage { get; init; }
|
||||
public required string Message { get; init; }
|
||||
public string? Recommendation { get; init; }
|
||||
}
|
||||
@@ -49,6 +49,30 @@ public static class RichGraphSemanticAttributes
|
||||
|
||||
/// <summary>CWE ID if applicable.</summary>
|
||||
public const string CweId = "cwe_id";
|
||||
|
||||
// Sprint: SPRINT_20260112_004_SCANNER_reachability_trace_runtime_evidence
|
||||
// Runtime evidence overlay attributes (do not alter lattice precedence)
|
||||
|
||||
/// <summary>Reachability score (0.0-1.0) - computed from path confidence.</summary>
|
||||
public const string ReachabilityScore = "reachability_score";
|
||||
|
||||
/// <summary>Whether this node/edge was confirmed at runtime ("true"/"false").</summary>
|
||||
public const string RuntimeConfirmed = "runtime_confirmed";
|
||||
|
||||
/// <summary>Number of runtime observations for this node/edge.</summary>
|
||||
public const string RuntimeObservationCount = "runtime_observation_count";
|
||||
|
||||
/// <summary>Timestamp of first runtime observation (ISO 8601).</summary>
|
||||
public const string RuntimeFirstObserved = "runtime_first_observed";
|
||||
|
||||
/// <summary>Timestamp of last runtime observation (ISO 8601).</summary>
|
||||
public const string RuntimeLastObserved = "runtime_last_observed";
|
||||
|
||||
/// <summary>Runtime evidence URI reference.</summary>
|
||||
public const string RuntimeEvidenceUri = "runtime_evidence_uri";
|
||||
|
||||
/// <summary>Runtime confirmation type (confirmed/partial/none).</summary>
|
||||
public const string RuntimeConfirmationType = "runtime_confirmation_type";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -162,6 +186,88 @@ public static class RichGraphSemanticExtensions
|
||||
// Use max risk score as overall
|
||||
return riskScores.Max();
|
||||
}
|
||||
|
||||
// Sprint: SPRINT_20260112_004_SCANNER_reachability_trace_runtime_evidence
|
||||
// Extension methods for runtime evidence overlay attributes
|
||||
|
||||
/// <summary>Gets the reachability score (0.0-1.0).</summary>
|
||||
public static double? GetReachabilityScore(this RichGraphNode node)
|
||||
{
|
||||
if (node.Attributes?.TryGetValue(RichGraphSemanticAttributes.ReachabilityScore, out var value) != true ||
|
||||
string.IsNullOrWhiteSpace(value))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return double.TryParse(value, NumberStyles.Float, CultureInfo.InvariantCulture, out var score) ? score : null;
|
||||
}
|
||||
|
||||
/// <summary>Gets whether this node was confirmed at runtime.</summary>
|
||||
public static bool? GetRuntimeConfirmed(this RichGraphNode node)
|
||||
{
|
||||
if (node.Attributes?.TryGetValue(RichGraphSemanticAttributes.RuntimeConfirmed, out var value) != true ||
|
||||
string.IsNullOrWhiteSpace(value))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return bool.TryParse(value, out var result) ? result : null;
|
||||
}
|
||||
|
||||
/// <summary>Gets the runtime observation count.</summary>
|
||||
public static ulong? GetRuntimeObservationCount(this RichGraphNode node)
|
||||
{
|
||||
if (node.Attributes?.TryGetValue(RichGraphSemanticAttributes.RuntimeObservationCount, out var value) != true ||
|
||||
string.IsNullOrWhiteSpace(value))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return ulong.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var count) ? count : null;
|
||||
}
|
||||
|
||||
/// <summary>Gets the runtime confirmation type (confirmed/partial/none).</summary>
|
||||
public static string? GetRuntimeConfirmationType(this RichGraphNode node)
|
||||
{
|
||||
return node.Attributes?.TryGetValue(RichGraphSemanticAttributes.RuntimeConfirmationType, out var value) == true ? value : null;
|
||||
}
|
||||
|
||||
/// <summary>Gets the runtime evidence URI.</summary>
|
||||
public static string? GetRuntimeEvidenceUri(this RichGraphNode node)
|
||||
{
|
||||
return node.Attributes?.TryGetValue(RichGraphSemanticAttributes.RuntimeEvidenceUri, out var value) == true ? value : null;
|
||||
}
|
||||
|
||||
/// <summary>Gets nodes with runtime confirmation.</summary>
|
||||
public static IReadOnlyList<RichGraphNode> GetRuntimeConfirmedNodes(this RichGraph graph)
|
||||
{
|
||||
return graph.Nodes.Where(n => n.GetRuntimeConfirmed() == true).ToList();
|
||||
}
|
||||
|
||||
/// <summary>Calculates the graph-level runtime coverage percentage.</summary>
|
||||
public static double CalculateRuntimeCoverage(this RichGraph graph)
|
||||
{
|
||||
if (graph.Nodes.Count == 0)
|
||||
return 0.0;
|
||||
|
||||
var confirmedCount = graph.Nodes.Count(n => n.GetRuntimeConfirmed() == true);
|
||||
return (double)confirmedCount / graph.Nodes.Count * 100.0;
|
||||
}
|
||||
|
||||
/// <summary>Gets the average reachability score for the graph.</summary>
|
||||
public static double? CalculateAverageReachabilityScore(this RichGraph graph)
|
||||
{
|
||||
var scores = graph.Nodes
|
||||
.Select(n => n.GetReachabilityScore())
|
||||
.Where(s => s.HasValue)
|
||||
.Select(s => s!.Value)
|
||||
.ToList();
|
||||
|
||||
if (scores.Count == 0)
|
||||
return null;
|
||||
|
||||
return scores.Average();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -230,6 +336,52 @@ public sealed class RichGraphNodeSemanticBuilder
|
||||
return this;
|
||||
}
|
||||
|
||||
// Sprint: SPRINT_20260112_004_SCANNER_reachability_trace_runtime_evidence
|
||||
// Builder methods for runtime evidence overlay attributes
|
||||
|
||||
/// <summary>Sets the reachability score (0.0-1.0).</summary>
|
||||
public RichGraphNodeSemanticBuilder WithReachabilityScore(double score)
|
||||
{
|
||||
_attributes[RichGraphSemanticAttributes.ReachabilityScore] = Math.Clamp(score, 0.0, 1.0).ToString("F3", CultureInfo.InvariantCulture);
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>Sets the runtime confirmed flag.</summary>
|
||||
public RichGraphNodeSemanticBuilder WithRuntimeConfirmed(bool confirmed)
|
||||
{
|
||||
_attributes[RichGraphSemanticAttributes.RuntimeConfirmed] = confirmed.ToString().ToLowerInvariant();
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>Sets the runtime observation count.</summary>
|
||||
public RichGraphNodeSemanticBuilder WithRuntimeObservationCount(ulong count)
|
||||
{
|
||||
_attributes[RichGraphSemanticAttributes.RuntimeObservationCount] = count.ToString(CultureInfo.InvariantCulture);
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>Sets the runtime observation timestamps.</summary>
|
||||
public RichGraphNodeSemanticBuilder WithRuntimeObservationTimes(DateTimeOffset firstObserved, DateTimeOffset lastObserved)
|
||||
{
|
||||
_attributes[RichGraphSemanticAttributes.RuntimeFirstObserved] = firstObserved.ToString("O", CultureInfo.InvariantCulture);
|
||||
_attributes[RichGraphSemanticAttributes.RuntimeLastObserved] = lastObserved.ToString("O", CultureInfo.InvariantCulture);
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>Sets the runtime evidence URI.</summary>
|
||||
public RichGraphNodeSemanticBuilder WithRuntimeEvidenceUri(string uri)
|
||||
{
|
||||
_attributes[RichGraphSemanticAttributes.RuntimeEvidenceUri] = uri;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>Sets the runtime confirmation type (confirmed/partial/none).</summary>
|
||||
public RichGraphNodeSemanticBuilder WithRuntimeConfirmationType(string confirmationType)
|
||||
{
|
||||
_attributes[RichGraphSemanticAttributes.RuntimeConfirmationType] = confirmationType;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>Builds the attributes dictionary.</summary>
|
||||
public IReadOnlyDictionary<string, string> Build()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user