Add comprehensive security tests for OWASP A02, A05, A07, and A08 categories
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
Export Center CI / export-ci (push) Has been cancelled
Findings Ledger CI / build-test (push) Has been cancelled
Findings Ledger CI / migration-validation (push) Has been cancelled
Findings Ledger CI / generate-manifest (push) Has been cancelled
Manifest Integrity / Validate Schema Integrity (push) Has been cancelled
Lighthouse CI / Lighthouse Audit (push) Has been cancelled
Lighthouse CI / Axe Accessibility Audit (push) Has been cancelled
Manifest Integrity / Validate Contract Documents (push) Has been cancelled
Manifest Integrity / Validate Pack Fixtures (push) Has been cancelled
Manifest Integrity / Audit SHA256SUMS Files (push) Has been cancelled
Manifest Integrity / Verify Merkle Roots (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled
Policy Simulation / policy-simulate (push) Has been cancelled

- Implemented tests for Cryptographic Failures (A02) to ensure proper handling of sensitive data, secure algorithms, and key management.
- Added tests for Security Misconfiguration (A05) to validate production configurations, security headers, CORS settings, and feature management.
- Developed tests for Authentication Failures (A07) to enforce strong password policies, rate limiting, session management, and MFA support.
- Created tests for Software and Data Integrity Failures (A08) to verify artifact signatures, SBOM integrity, attestation chains, and feed updates.
This commit is contained in:
master
2025-12-16 16:40:19 +02:00
parent 415eff1207
commit 2170a58734
206 changed files with 30547 additions and 534 deletions

View File

@@ -0,0 +1,168 @@
using System.Collections.Immutable;
using System.Text.Json.Serialization;
namespace StellaOps.Scanner.SmartDiff.Output;
/// <summary>
/// SARIF 2.1.0 log model for Smart-Diff output.
/// Per Sprint 3500.4 - Smart-Diff Binary Analysis.
/// </summary>
public sealed record SarifLog(
[property: JsonPropertyName("version")] string Version,
[property: JsonPropertyName("$schema")] string Schema,
[property: JsonPropertyName("runs")] ImmutableArray<SarifRun> Runs);
/// <summary>
/// A single SARIF run representing one analysis execution.
/// </summary>
public sealed record SarifRun(
[property: JsonPropertyName("tool")] SarifTool Tool,
[property: JsonPropertyName("results")] ImmutableArray<SarifResult> Results,
[property: JsonPropertyName("invocations")] ImmutableArray<SarifInvocation>? Invocations = null,
[property: JsonPropertyName("artifacts")] ImmutableArray<SarifArtifact>? Artifacts = null,
[property: JsonPropertyName("versionControlProvenance")] ImmutableArray<SarifVersionControlDetails>? VersionControlProvenance = null);
/// <summary>
/// Tool information for the SARIF run.
/// </summary>
public sealed record SarifTool(
[property: JsonPropertyName("driver")] SarifToolComponent Driver,
[property: JsonPropertyName("extensions")] ImmutableArray<SarifToolComponent>? Extensions = null);
/// <summary>
/// Tool component (driver or extension).
/// </summary>
public sealed record SarifToolComponent(
[property: JsonPropertyName("name")] string Name,
[property: JsonPropertyName("version")] string Version,
[property: JsonPropertyName("informationUri")] string? InformationUri = null,
[property: JsonPropertyName("rules")] ImmutableArray<SarifReportingDescriptor>? Rules = null,
[property: JsonPropertyName("supportedTaxonomies")] ImmutableArray<SarifToolComponentReference>? SupportedTaxonomies = null);
/// <summary>
/// Reference to a tool component.
/// </summary>
public sealed record SarifToolComponentReference(
[property: JsonPropertyName("name")] string Name,
[property: JsonPropertyName("guid")] string? Guid = null);
/// <summary>
/// Rule definition.
/// </summary>
public sealed record SarifReportingDescriptor(
[property: JsonPropertyName("id")] string Id,
[property: JsonPropertyName("name")] string? Name = null,
[property: JsonPropertyName("shortDescription")] SarifMessage? ShortDescription = null,
[property: JsonPropertyName("fullDescription")] SarifMessage? FullDescription = null,
[property: JsonPropertyName("defaultConfiguration")] SarifReportingConfiguration? DefaultConfiguration = null,
[property: JsonPropertyName("helpUri")] string? HelpUri = null);
/// <summary>
/// Rule configuration.
/// </summary>
public sealed record SarifReportingConfiguration(
[property: JsonPropertyName("level")] SarifLevel Level = SarifLevel.Warning,
[property: JsonPropertyName("enabled")] bool Enabled = true);
/// <summary>
/// SARIF message with text.
/// </summary>
public sealed record SarifMessage(
[property: JsonPropertyName("text")] string Text,
[property: JsonPropertyName("markdown")] string? Markdown = null);
/// <summary>
/// SARIF result level.
/// </summary>
[JsonConverter(typeof(JsonStringEnumConverter<SarifLevel>))]
public enum SarifLevel
{
[JsonStringEnumMemberName("none")]
None,
[JsonStringEnumMemberName("note")]
Note,
[JsonStringEnumMemberName("warning")]
Warning,
[JsonStringEnumMemberName("error")]
Error
}
/// <summary>
/// A single result/finding.
/// </summary>
public sealed record SarifResult(
[property: JsonPropertyName("ruleId")] string RuleId,
[property: JsonPropertyName("level")] SarifLevel Level,
[property: JsonPropertyName("message")] SarifMessage Message,
[property: JsonPropertyName("locations")] ImmutableArray<SarifLocation>? Locations = null,
[property: JsonPropertyName("fingerprints")] ImmutableDictionary<string, string>? Fingerprints = null,
[property: JsonPropertyName("partialFingerprints")] ImmutableDictionary<string, string>? PartialFingerprints = null,
[property: JsonPropertyName("properties")] ImmutableDictionary<string, object>? Properties = null);
/// <summary>
/// Location of a result.
/// </summary>
public sealed record SarifLocation(
[property: JsonPropertyName("physicalLocation")] SarifPhysicalLocation? PhysicalLocation = null,
[property: JsonPropertyName("logicalLocations")] ImmutableArray<SarifLogicalLocation>? LogicalLocations = null);
/// <summary>
/// Physical file location.
/// </summary>
public sealed record SarifPhysicalLocation(
[property: JsonPropertyName("artifactLocation")] SarifArtifactLocation ArtifactLocation,
[property: JsonPropertyName("region")] SarifRegion? Region = null);
/// <summary>
/// Artifact location (file path).
/// </summary>
public sealed record SarifArtifactLocation(
[property: JsonPropertyName("uri")] string Uri,
[property: JsonPropertyName("uriBaseId")] string? UriBaseId = null,
[property: JsonPropertyName("index")] int? Index = null);
/// <summary>
/// Region within a file.
/// </summary>
public sealed record SarifRegion(
[property: JsonPropertyName("startLine")] int? StartLine = null,
[property: JsonPropertyName("startColumn")] int? StartColumn = null,
[property: JsonPropertyName("endLine")] int? EndLine = null,
[property: JsonPropertyName("endColumn")] int? EndColumn = null);
/// <summary>
/// Logical location (namespace, class, function).
/// </summary>
public sealed record SarifLogicalLocation(
[property: JsonPropertyName("name")] string Name,
[property: JsonPropertyName("fullyQualifiedName")] string? FullyQualifiedName = null,
[property: JsonPropertyName("kind")] string? Kind = null);
/// <summary>
/// Invocation information.
/// </summary>
public sealed record SarifInvocation(
[property: JsonPropertyName("executionSuccessful")] bool ExecutionSuccessful,
[property: JsonPropertyName("startTimeUtc")] DateTimeOffset? StartTimeUtc = null,
[property: JsonPropertyName("endTimeUtc")] DateTimeOffset? EndTimeUtc = null,
[property: JsonPropertyName("workingDirectory")] SarifArtifactLocation? WorkingDirectory = null,
[property: JsonPropertyName("commandLine")] string? CommandLine = null);
/// <summary>
/// Artifact (file) information.
/// </summary>
public sealed record SarifArtifact(
[property: JsonPropertyName("location")] SarifArtifactLocation Location,
[property: JsonPropertyName("mimeType")] string? MimeType = null,
[property: JsonPropertyName("hashes")] ImmutableDictionary<string, string>? Hashes = null);
/// <summary>
/// Version control information.
/// </summary>
public sealed record SarifVersionControlDetails(
[property: JsonPropertyName("repositoryUri")] string RepositoryUri,
[property: JsonPropertyName("revisionId")] string? RevisionId = null,
[property: JsonPropertyName("branch")] string? Branch = null);

View File

@@ -0,0 +1,393 @@
using System.Collections.Immutable;
using System.Text.Json;
namespace StellaOps.Scanner.SmartDiff.Output;
/// <summary>
/// Options for SARIF output generation.
/// </summary>
public sealed class SarifOutputOptions
{
/// <summary>
/// Default options instance.
/// </summary>
public static readonly SarifOutputOptions Default = new();
/// <summary>
/// Whether to include VEX candidates in output.
/// </summary>
public bool IncludeVexCandidates { get; init; } = true;
/// <summary>
/// Whether to include hardening regressions in output.
/// </summary>
public bool IncludeHardeningRegressions { get; init; } = true;
/// <summary>
/// Whether to include reachability changes in output.
/// </summary>
public bool IncludeReachabilityChanges { get; init; } = true;
/// <summary>
/// Whether to pretty-print JSON output.
/// </summary>
public bool IndentedJson { get; init; } = false;
}
/// <summary>
/// Input for SARIF generation.
/// </summary>
public sealed record SmartDiffSarifInput(
string ScannerVersion,
DateTimeOffset ScanTime,
string? BaseDigest,
string? TargetDigest,
IReadOnlyList<MaterialRiskChange> MaterialChanges,
IReadOnlyList<HardeningRegression> HardeningRegressions,
IReadOnlyList<VexCandidate> VexCandidates,
IReadOnlyList<ReachabilityChange> ReachabilityChanges,
VcsInfo? VcsInfo = null);
/// <summary>
/// VCS information for SARIF provenance.
/// </summary>
public sealed record VcsInfo(
string RepositoryUri,
string? RevisionId,
string? Branch);
/// <summary>
/// A material risk change finding.
/// </summary>
public sealed record MaterialRiskChange(
string VulnId,
string ComponentPurl,
RiskDirection Direction,
string Reason,
string? FilePath = null);
/// <summary>
/// Direction of risk change.
/// </summary>
public enum RiskDirection
{
/// <summary>Risk increased (worse).</summary>
Increased,
/// <summary>Risk decreased (better).</summary>
Decreased,
/// <summary>Risk status changed but severity unclear.</summary>
Changed
}
/// <summary>
/// A hardening regression finding.
/// </summary>
public sealed record HardeningRegression(
string BinaryPath,
string FlagName,
bool WasEnabled,
bool IsEnabled,
double ScoreImpact);
/// <summary>
/// A VEX candidate finding.
/// </summary>
public sealed record VexCandidate(
string VulnId,
string ComponentPurl,
string Justification,
string? ImpactStatement);
/// <summary>
/// A reachability status change.
/// </summary>
public sealed record ReachabilityChange(
string VulnId,
string ComponentPurl,
bool WasReachable,
bool IsReachable,
string? Evidence);
/// <summary>
/// Generates SARIF 2.1.0 output for Smart-Diff findings.
/// Per Sprint 3500.4 - Smart-Diff Binary Analysis.
/// </summary>
public sealed class SarifOutputGenerator
{
private const string SarifVersion = "2.1.0";
private const string SchemaUri = "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json";
private const string ToolName = "StellaOps.Scanner.SmartDiff";
private const string ToolInfoUri = "https://stellaops.dev/docs/scanner/smart-diff";
private static readonly JsonSerializerOptions SarifJsonOptions = new()
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
DefaultIgnoreCondition = System.Text.Json.Serialization.JsonIgnoreCondition.WhenWritingNull,
WriteIndented = false
};
/// <summary>
/// Generate a SARIF log from Smart-Diff input.
/// </summary>
public SarifLog Generate(SmartDiffSarifInput input, SarifOutputOptions? options = null)
{
options ??= SarifOutputOptions.Default;
var tool = CreateTool(input);
var results = CreateResults(input, options);
var invocation = CreateInvocation(input);
var artifacts = CreateArtifacts(input);
var vcsProvenance = CreateVcsProvenance(input);
var run = new SarifRun(
Tool: tool,
Results: results,
Invocations: [invocation],
Artifacts: artifacts.Length > 0 ? artifacts : null,
VersionControlProvenance: vcsProvenance);
return new SarifLog(
Version: SarifVersion,
Schema: SchemaUri,
Runs: [run]);
}
/// <summary>
/// Generate SARIF JSON string.
/// </summary>
public string GenerateJson(SmartDiffSarifInput input, SarifOutputOptions? options = null)
{
var log = Generate(input, options);
var jsonOptions = options?.IndentedJson == true
? new JsonSerializerOptions(SarifJsonOptions) { WriteIndented = true }
: SarifJsonOptions;
return JsonSerializer.Serialize(log, jsonOptions);
}
/// <summary>
/// Write SARIF to a stream.
/// </summary>
public async Task WriteAsync(
SmartDiffSarifInput input,
Stream outputStream,
SarifOutputOptions? options = null,
CancellationToken ct = default)
{
var log = Generate(input, options);
var jsonOptions = options?.IndentedJson == true
? new JsonSerializerOptions(SarifJsonOptions) { WriteIndented = true }
: SarifJsonOptions;
await JsonSerializer.SerializeAsync(outputStream, log, jsonOptions, ct);
}
private static SarifTool CreateTool(SmartDiffSarifInput input)
{
var rules = CreateRules();
return new SarifTool(
Driver: new SarifToolComponent(
Name: ToolName,
Version: input.ScannerVersion,
InformationUri: ToolInfoUri,
Rules: rules,
SupportedTaxonomies: [
new SarifToolComponentReference(
Name: "CWE",
Guid: "25F72D7E-8A92-459D-AD67-64853F788765")
]));
}
private static ImmutableArray<SarifReportingDescriptor> CreateRules()
{
return
[
new SarifReportingDescriptor(
Id: "SDIFF001",
Name: "MaterialRiskChange",
ShortDescription: new SarifMessage("Material risk change detected"),
FullDescription: new SarifMessage("A vulnerability finding has undergone a material risk state change between scans."),
DefaultConfiguration: new SarifReportingConfiguration(Level: SarifLevel.Warning),
HelpUri: $"{ToolInfoUri}/rules/SDIFF001"),
new SarifReportingDescriptor(
Id: "SDIFF002",
Name: "HardeningRegression",
ShortDescription: new SarifMessage("Binary hardening regression detected"),
FullDescription: new SarifMessage("A binary has lost security hardening flags compared to the previous scan."),
DefaultConfiguration: new SarifReportingConfiguration(Level: SarifLevel.Error),
HelpUri: $"{ToolInfoUri}/rules/SDIFF002"),
new SarifReportingDescriptor(
Id: "SDIFF003",
Name: "VexCandidateGenerated",
ShortDescription: new SarifMessage("VEX candidate auto-generated"),
FullDescription: new SarifMessage("A VEX 'not_affected' candidate was generated because vulnerable APIs are no longer present."),
DefaultConfiguration: new SarifReportingConfiguration(Level: SarifLevel.Note),
HelpUri: $"{ToolInfoUri}/rules/SDIFF003"),
new SarifReportingDescriptor(
Id: "SDIFF004",
Name: "ReachabilityFlip",
ShortDescription: new SarifMessage("Reachability status changed"),
FullDescription: new SarifMessage("The reachability of a vulnerability has flipped between scans."),
DefaultConfiguration: new SarifReportingConfiguration(Level: SarifLevel.Warning),
HelpUri: $"{ToolInfoUri}/rules/SDIFF004")
];
}
private static ImmutableArray<SarifResult> CreateResults(SmartDiffSarifInput input, SarifOutputOptions options)
{
var results = new List<SarifResult>();
// Material risk changes
foreach (var change in input.MaterialChanges)
{
results.Add(CreateMaterialChangeResult(change));
}
// Hardening regressions
if (options.IncludeHardeningRegressions)
{
foreach (var regression in input.HardeningRegressions)
{
results.Add(CreateHardeningRegressionResult(regression));
}
}
// VEX candidates
if (options.IncludeVexCandidates)
{
foreach (var candidate in input.VexCandidates)
{
results.Add(CreateVexCandidateResult(candidate));
}
}
// Reachability changes
if (options.IncludeReachabilityChanges)
{
foreach (var change in input.ReachabilityChanges)
{
results.Add(CreateReachabilityChangeResult(change));
}
}
return [.. results];
}
private static SarifResult CreateMaterialChangeResult(MaterialRiskChange change)
{
var level = change.Direction == RiskDirection.Increased ? SarifLevel.Warning : SarifLevel.Note;
var message = $"Material risk change for {change.VulnId} in {change.ComponentPurl}: {change.Reason}";
var locations = change.FilePath is not null
? ImmutableArray.Create(new SarifLocation(
PhysicalLocation: new SarifPhysicalLocation(
ArtifactLocation: new SarifArtifactLocation(Uri: change.FilePath))))
: (ImmutableArray<SarifLocation>?)null;
return new SarifResult(
RuleId: "SDIFF001",
Level: level,
Message: new SarifMessage(message),
Locations: locations,
Fingerprints: ImmutableDictionary.CreateRange(new[]
{
KeyValuePair.Create("vulnId", change.VulnId),
KeyValuePair.Create("purl", change.ComponentPurl)
}));
}
private static SarifResult CreateHardeningRegressionResult(HardeningRegression regression)
{
var message = $"Hardening flag '{regression.FlagName}' was {(regression.WasEnabled ? "enabled" : "disabled")} " +
$"but is now {(regression.IsEnabled ? "enabled" : "disabled")} in {regression.BinaryPath}";
return new SarifResult(
RuleId: "SDIFF002",
Level: SarifLevel.Error,
Message: new SarifMessage(message),
Locations: [new SarifLocation(
PhysicalLocation: new SarifPhysicalLocation(
ArtifactLocation: new SarifArtifactLocation(Uri: regression.BinaryPath)))]);
}
private static SarifResult CreateVexCandidateResult(VexCandidate candidate)
{
var message = $"VEX not_affected candidate for {candidate.VulnId} in {candidate.ComponentPurl}: {candidate.Justification}";
return new SarifResult(
RuleId: "SDIFF003",
Level: SarifLevel.Note,
Message: new SarifMessage(message),
Fingerprints: ImmutableDictionary.CreateRange(new[]
{
KeyValuePair.Create("vulnId", candidate.VulnId),
KeyValuePair.Create("purl", candidate.ComponentPurl)
}));
}
private static SarifResult CreateReachabilityChangeResult(ReachabilityChange change)
{
var direction = change.IsReachable ? "became reachable" : "became unreachable";
var message = $"Vulnerability {change.VulnId} in {change.ComponentPurl} {direction}";
return new SarifResult(
RuleId: "SDIFF004",
Level: SarifLevel.Warning,
Message: new SarifMessage(message),
Fingerprints: ImmutableDictionary.CreateRange(new[]
{
KeyValuePair.Create("vulnId", change.VulnId),
KeyValuePair.Create("purl", change.ComponentPurl)
}));
}
private static SarifInvocation CreateInvocation(SmartDiffSarifInput input)
{
return new SarifInvocation(
ExecutionSuccessful: true,
StartTimeUtc: input.ScanTime,
EndTimeUtc: DateTimeOffset.UtcNow);
}
private static ImmutableArray<SarifArtifact> CreateArtifacts(SmartDiffSarifInput input)
{
var artifacts = new List<SarifArtifact>();
// Collect unique file paths from results
var paths = new HashSet<string>();
foreach (var change in input.MaterialChanges)
{
if (change.FilePath is not null)
paths.Add(change.FilePath);
}
foreach (var regression in input.HardeningRegressions)
{
paths.Add(regression.BinaryPath);
}
foreach (var path in paths)
{
artifacts.Add(new SarifArtifact(
Location: new SarifArtifactLocation(Uri: path)));
}
return [.. artifacts];
}
private static ImmutableArray<SarifVersionControlDetails>? CreateVcsProvenance(SmartDiffSarifInput input)
{
if (input.VcsInfo is null)
return null;
return [new SarifVersionControlDetails(
RepositoryUri: input.VcsInfo.RepositoryUri,
RevisionId: input.VcsInfo.RevisionId,
Branch: input.VcsInfo.Branch)];
}
}