Files
git.stella-ops.org/src/Policy/StellaOps.Policy.Engine/Endpoints/PolicyLintEndpoints.cs
StellaOps Bot 5e514532df Implement VEX document verification system with issuer management and signature verification
- Added IIssuerDirectory interface for managing VEX document issuers, including methods for registration, revocation, and trust validation.
- Created InMemoryIssuerDirectory class as an in-memory implementation of IIssuerDirectory for testing and single-instance deployments.
- Introduced ISignatureVerifier interface for verifying signatures on VEX documents, with support for multiple signature formats.
- Developed SignatureVerifier class as the default implementation of ISignatureVerifier, allowing extensibility for different signature formats.
- Implemented handlers for DSSE and JWS signature formats, including methods for verification and signature extraction.
- Defined various records and enums for issuer and signature metadata, enhancing the structure and clarity of the verification process.
2025-12-06 13:41:22 +02:00

242 lines
8.8 KiB
C#

using Microsoft.AspNetCore.Mvc;
using StellaOps.Policy.Engine.DeterminismGuard;
namespace StellaOps.Policy.Engine.Endpoints;
/// <summary>
/// Endpoints for policy code linting and determinism analysis.
/// Implements POLICY-AOC-19-001 per docs/modules/policy/design/policy-aoc-linting-rules.md.
/// </summary>
public static class PolicyLintEndpoints
{
public static IEndpointRouteBuilder MapPolicyLint(this IEndpointRouteBuilder routes)
{
var group = routes.MapGroup("/api/v1/policy/lint");
group.MapPost("/analyze", AnalyzeSourceAsync)
.WithName("Policy.Lint.Analyze")
.WithDescription("Analyze source code for determinism violations")
.RequireAuthorization(policy => policy.RequireClaim("scope", "policy:read"));
group.MapPost("/analyze-batch", AnalyzeBatchAsync)
.WithName("Policy.Lint.AnalyzeBatch")
.WithDescription("Analyze multiple source files for determinism violations")
.RequireAuthorization(policy => policy.RequireClaim("scope", "policy:read"));
group.MapGet("/rules", GetLintRulesAsync)
.WithName("Policy.Lint.GetRules")
.WithDescription("Get available lint rules and their severities")
.AllowAnonymous();
return routes;
}
private static Task<IResult> AnalyzeSourceAsync(
[FromBody] LintSourceRequest request,
CancellationToken cancellationToken)
{
if (request is null || string.IsNullOrWhiteSpace(request.Source))
{
return Task.FromResult(Results.BadRequest(new
{
error = "LINT_SOURCE_REQUIRED",
message = "Source code is required"
}));
}
var analyzer = new ProhibitedPatternAnalyzer();
var options = new DeterminismGuardOptions
{
EnforcementEnabled = request.EnforceErrors ?? true,
FailOnSeverity = ParseSeverity(request.MinSeverity),
EnableStaticAnalysis = true,
EnableRuntimeMonitoring = false
};
var result = analyzer.AnalyzeSource(request.Source, request.FileName, options);
return Task.FromResult(Results.Ok(new LintResultResponse
{
Passed = result.Passed,
Violations = result.Violations.Select(MapViolation).ToList(),
CountBySeverity = result.CountBySeverity.ToDictionary(
kvp => kvp.Key.ToString().ToLowerInvariant(),
kvp => kvp.Value),
AnalysisDurationMs = result.AnalysisDurationMs,
EnforcementEnabled = result.EnforcementEnabled
}));
}
private static Task<IResult> AnalyzeBatchAsync(
[FromBody] LintBatchRequest request,
CancellationToken cancellationToken)
{
if (request?.Files is null || request.Files.Count == 0)
{
return Task.FromResult(Results.BadRequest(new
{
error = "LINT_FILES_REQUIRED",
message = "At least one file is required"
}));
}
var analyzer = new ProhibitedPatternAnalyzer();
var options = new DeterminismGuardOptions
{
EnforcementEnabled = request.EnforceErrors ?? true,
FailOnSeverity = ParseSeverity(request.MinSeverity),
EnableStaticAnalysis = true,
EnableRuntimeMonitoring = false
};
var sources = request.Files.Select(f => (f.Source, f.FileName));
var result = analyzer.AnalyzeMultiple(sources, options);
return Task.FromResult(Results.Ok(new LintResultResponse
{
Passed = result.Passed,
Violations = result.Violations.Select(MapViolation).ToList(),
CountBySeverity = result.CountBySeverity.ToDictionary(
kvp => kvp.Key.ToString().ToLowerInvariant(),
kvp => kvp.Value),
AnalysisDurationMs = result.AnalysisDurationMs,
EnforcementEnabled = result.EnforcementEnabled
}));
}
private static Task<IResult> GetLintRulesAsync(CancellationToken cancellationToken)
{
var rules = new List<LintRuleInfo>
{
// Wall-clock rules
new("DET-001", "DateTime.Now", "error", "WallClock", "Use TimeProvider.GetUtcNow()"),
new("DET-002", "DateTime.UtcNow", "error", "WallClock", "Use TimeProvider.GetUtcNow()"),
new("DET-003", "DateTimeOffset.Now", "error", "WallClock", "Use TimeProvider.GetUtcNow()"),
new("DET-004", "DateTimeOffset.UtcNow", "error", "WallClock", "Use TimeProvider.GetUtcNow()"),
// Random/GUID rules
new("DET-005", "Guid.NewGuid()", "error", "GuidGeneration", "Use StableIdGenerator or content hash"),
new("DET-006", "new Random()", "error", "RandomNumber", "Use seeded random or remove"),
new("DET-007", "RandomNumberGenerator", "error", "RandomNumber", "Remove from evaluation path"),
// Network/Filesystem rules
new("DET-008", "HttpClient in eval", "critical", "NetworkAccess", "Remove network from eval path"),
new("DET-009", "File.Read* in eval", "critical", "FileSystemAccess", "Remove filesystem from eval path"),
// Ordering rules
new("DET-010", "Dictionary iteration", "warning", "UnstableIteration", "Use OrderBy or SortedDictionary"),
new("DET-011", "HashSet iteration", "warning", "UnstableIteration", "Use OrderBy or SortedSet"),
// Environment rules
new("DET-012", "Environment.GetEnvironmentVariable", "error", "EnvironmentAccess", "Use evaluation context"),
new("DET-013", "Environment.MachineName", "warning", "EnvironmentAccess", "Remove host-specific info")
};
return Task.FromResult(Results.Ok(new
{
rules,
categories = new[]
{
"WallClock",
"RandomNumber",
"GuidGeneration",
"NetworkAccess",
"FileSystemAccess",
"EnvironmentAccess",
"UnstableIteration",
"FloatingPointHazard",
"ConcurrencyHazard"
},
severities = new[] { "info", "warning", "error", "critical" }
}));
}
private static DeterminismViolationSeverity ParseSeverity(string? severity)
{
return severity?.ToLowerInvariant() switch
{
"info" => DeterminismViolationSeverity.Info,
"warning" => DeterminismViolationSeverity.Warning,
"error" => DeterminismViolationSeverity.Error,
"critical" => DeterminismViolationSeverity.Critical,
_ => DeterminismViolationSeverity.Error
};
}
private static LintViolationResponse MapViolation(DeterminismViolation v)
{
return new LintViolationResponse
{
Category = v.Category.ToString(),
ViolationType = v.ViolationType,
Message = v.Message,
Severity = v.Severity.ToString().ToLowerInvariant(),
SourceFile = v.SourceFile,
LineNumber = v.LineNumber,
MemberName = v.MemberName,
Remediation = v.Remediation
};
}
}
/// <summary>
/// Request for single source analysis.
/// </summary>
public sealed record LintSourceRequest(
string Source,
string? FileName = null,
string? MinSeverity = null,
bool? EnforceErrors = null);
/// <summary>
/// Request for batch source analysis.
/// </summary>
public sealed record LintBatchRequest(
List<LintFileInput> Files,
string? MinSeverity = null,
bool? EnforceErrors = null);
/// <summary>
/// Single file input for batch analysis.
/// </summary>
public sealed record LintFileInput(
string Source,
string FileName);
/// <summary>
/// Response for lint analysis.
/// </summary>
public sealed record LintResultResponse
{
public required bool Passed { get; init; }
public required List<LintViolationResponse> Violations { get; init; }
public required Dictionary<string, int> CountBySeverity { get; init; }
public required long AnalysisDurationMs { get; init; }
public required bool EnforcementEnabled { get; init; }
}
/// <summary>
/// Single violation in lint response.
/// </summary>
public sealed record LintViolationResponse
{
public required string Category { get; init; }
public required string ViolationType { get; init; }
public required string Message { get; init; }
public required string Severity { get; init; }
public string? SourceFile { get; init; }
public int? LineNumber { get; init; }
public string? MemberName { get; init; }
public string? Remediation { get; init; }
}
/// <summary>
/// Lint rule information.
/// </summary>
public sealed record LintRuleInfo(
string RuleId,
string Name,
string DefaultSeverity,
string Category,
string Remediation);