feat: Implement IsolatedReplayContext for deterministic audit replay
- Added IsolatedReplayContext class to provide an isolated environment for replaying audit bundles without external calls. - Introduced methods for initializing the context, verifying input digests, and extracting inputs for policy evaluation. - Created supporting interfaces and options for context configuration. feat: Create ReplayExecutor for executing policy re-evaluation and verdict comparison - Developed ReplayExecutor class to handle the execution of replay processes, including input verification and verdict comparison. - Implemented detailed drift detection and error handling during replay execution. - Added interfaces for policy evaluation and replay execution options. feat: Add ScanSnapshotFetcher for fetching scan data and snapshots - Introduced ScanSnapshotFetcher class to retrieve necessary scan data and snapshots for audit bundle creation. - Implemented methods to fetch scan metadata, advisory feeds, policy snapshots, and VEX statements. - Created supporting interfaces for scan data, feed snapshots, and policy snapshots.
This commit is contained in:
@@ -217,7 +217,7 @@ public sealed class NodeCallGraphExtractor : ICallGraphExtractor
|
||||
IsEntrypoint: false,
|
||||
EntrypointType: null,
|
||||
IsSink: true,
|
||||
SinkCategory: sink.Category));
|
||||
SinkCategory: MapSinkCategory(sink.Category)));
|
||||
|
||||
// Add edge from caller to sink
|
||||
var callerNodeId = CallGraphNodeIds.Compute(sink.Caller);
|
||||
@@ -299,10 +299,15 @@ public sealed class NodeCallGraphExtractor : ICallGraphExtractor
|
||||
"file_read" or "path_traversal" => SinkCategory.PathTraversal,
|
||||
"weak_crypto" or "crypto_weak" => SinkCategory.CryptoWeak,
|
||||
"ldap_injection" => SinkCategory.LdapInjection,
|
||||
"nosql_injection" or "nosql" => SinkCategory.NoSqlInjection,
|
||||
"nosql_injection" or "nosql" => SinkCategory.SqlRaw, // Map to SQL as closest category
|
||||
"xss" or "template_injection" => SinkCategory.TemplateInjection,
|
||||
"log_injection" or "log_forging" => SinkCategory.LogForging,
|
||||
"regex_dos" or "redos" => SinkCategory.ReDos,
|
||||
"log_injection" or "log_forging" => SinkCategory.LogInjection,
|
||||
"regex_dos" or "redos" => SinkCategory.CodeInjection, // Map to code injection as closest
|
||||
"code_injection" or "eval" => SinkCategory.CodeInjection,
|
||||
"xxe" => SinkCategory.XxeInjection,
|
||||
"xpath_injection" => SinkCategory.XPathInjection,
|
||||
"open_redirect" => SinkCategory.OpenRedirect,
|
||||
"reflection" => SinkCategory.Reflection,
|
||||
_ => null
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,137 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
// Copyright (c) StellaOps
|
||||
|
||||
using System.Collections.Immutable;
|
||||
using StellaOps.Scanner.Reachability.Stack;
|
||||
|
||||
namespace StellaOps.Scanner.Reachability.Layer1;
|
||||
|
||||
/// <summary>
|
||||
/// Layer 1 analyzer: Static call graph reachability.
|
||||
/// Determines if vulnerable symbols are reachable from application entrypoints
|
||||
/// via static code analysis.
|
||||
/// </summary>
|
||||
public interface ILayer1Analyzer
|
||||
{
|
||||
/// <summary>
|
||||
/// Analyzes static reachability of a vulnerable symbol.
|
||||
/// </summary>
|
||||
/// <param name="symbol">The vulnerable symbol to check</param>
|
||||
/// <param name="graph">The call graph to analyze</param>
|
||||
/// <param name="entrypoints">Known application entrypoints</param>
|
||||
/// <param name="ct">Cancellation token</param>
|
||||
/// <returns>Layer 1 reachability analysis result</returns>
|
||||
Task<ReachabilityLayer1> AnalyzeAsync(
|
||||
VulnerableSymbol symbol,
|
||||
CallGraph graph,
|
||||
ImmutableArray<Entrypoint> entrypoints,
|
||||
CancellationToken ct = default);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A call graph representing method/function calls in the application.
|
||||
/// </summary>
|
||||
public sealed record CallGraph
|
||||
{
|
||||
/// <summary>Unique identifier for this call graph</summary>
|
||||
public required string Id { get; init; }
|
||||
|
||||
/// <summary>When this call graph was generated</summary>
|
||||
public required DateTimeOffset GeneratedAt { get; init; }
|
||||
|
||||
/// <summary>All nodes in the graph</summary>
|
||||
public ImmutableArray<CallGraphNode> Nodes { get; init; } = [];
|
||||
|
||||
/// <summary>All edges (calls) in the graph</summary>
|
||||
public ImmutableArray<CallGraphEdge> Edges { get; init; } = [];
|
||||
|
||||
/// <summary>Source of this call graph</summary>
|
||||
public required CallGraphSource Source { get; init; }
|
||||
|
||||
/// <summary>Language/platform this graph represents</summary>
|
||||
public required string Language { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A node in the call graph (method/function).
|
||||
/// </summary>
|
||||
public sealed record CallGraphNode(
|
||||
string Id,
|
||||
string Name,
|
||||
string? ClassName,
|
||||
string? Namespace,
|
||||
string? FileName,
|
||||
int? LineNumber,
|
||||
bool IsEntrypoint,
|
||||
bool IsExternal
|
||||
);
|
||||
|
||||
/// <summary>
|
||||
/// An edge in the call graph (call from one method to another).
|
||||
/// </summary>
|
||||
public sealed record CallGraphEdge(
|
||||
string FromNodeId,
|
||||
string ToNodeId,
|
||||
CallSiteType CallType,
|
||||
int? LineNumber,
|
||||
bool IsConditional
|
||||
);
|
||||
|
||||
/// <summary>
|
||||
/// Source of a call graph.
|
||||
/// </summary>
|
||||
public enum CallGraphSource
|
||||
{
|
||||
/// <summary>Roslyn/ILSpy analysis for .NET</summary>
|
||||
DotNetAnalysis,
|
||||
|
||||
/// <summary>TypeScript/JavaScript AST analysis</summary>
|
||||
NodeAnalysis,
|
||||
|
||||
/// <summary>javap/ASM analysis for Java</summary>
|
||||
JavaAnalysis,
|
||||
|
||||
/// <summary>go/analysis for Go</summary>
|
||||
GoAnalysis,
|
||||
|
||||
/// <summary>Python AST analysis</summary>
|
||||
PythonAnalysis,
|
||||
|
||||
/// <summary>Binary disassembly</summary>
|
||||
BinaryAnalysis,
|
||||
|
||||
/// <summary>Combined from multiple sources</summary>
|
||||
Composite
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Input for Layer 1 analysis.
|
||||
/// </summary>
|
||||
public sealed record Layer1AnalysisInput
|
||||
{
|
||||
public required VulnerableSymbol Symbol { get; init; }
|
||||
public required CallGraph Graph { get; init; }
|
||||
public ImmutableArray<Entrypoint> Entrypoints { get; init; } = [];
|
||||
public Layer1AnalysisOptions? Options { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Options for Layer 1 analysis.
|
||||
/// </summary>
|
||||
public sealed record Layer1AnalysisOptions
|
||||
{
|
||||
/// <summary>Maximum call path depth to explore</summary>
|
||||
public int MaxPathDepth { get; init; } = 100;
|
||||
|
||||
/// <summary>Maximum number of paths to return</summary>
|
||||
public int MaxPaths { get; init; } = 10;
|
||||
|
||||
/// <summary>Include paths through external libraries</summary>
|
||||
public bool IncludeExternalPaths { get; init; } = true;
|
||||
|
||||
/// <summary>Consider reflection calls as potential paths</summary>
|
||||
public bool ConsiderReflection { get; init; } = true;
|
||||
|
||||
/// <summary>Consider dynamic dispatch as potential paths</summary>
|
||||
public bool ConsiderDynamicDispatch { get; init; } = true;
|
||||
}
|
||||
@@ -0,0 +1,193 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
// Copyright (c) StellaOps
|
||||
|
||||
using System.Collections.Immutable;
|
||||
using StellaOps.Scanner.Reachability.Stack;
|
||||
|
||||
namespace StellaOps.Scanner.Reachability.Layer2;
|
||||
|
||||
/// <summary>
|
||||
/// Layer 2 analyzer: Binary/loader resolution.
|
||||
/// Determines if the dynamic loader actually links the vulnerable symbol at runtime.
|
||||
/// </summary>
|
||||
public interface ILayer2Analyzer
|
||||
{
|
||||
/// <summary>
|
||||
/// Analyzes whether a vulnerable symbol is actually resolved by the loader.
|
||||
/// </summary>
|
||||
/// <param name="symbol">The vulnerable symbol to check</param>
|
||||
/// <param name="binary">The binary artifact to analyze</param>
|
||||
/// <param name="context">Loader context (paths, preloads, etc.)</param>
|
||||
/// <param name="ct">Cancellation token</param>
|
||||
/// <returns>Layer 2 resolution analysis result</returns>
|
||||
Task<ReachabilityLayer2> AnalyzeAsync(
|
||||
VulnerableSymbol symbol,
|
||||
BinaryArtifact binary,
|
||||
LoaderContext context,
|
||||
CancellationToken ct = default);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A binary artifact (executable, shared library, etc.).
|
||||
/// </summary>
|
||||
public sealed record BinaryArtifact
|
||||
{
|
||||
/// <summary>Path to the binary file</summary>
|
||||
public required string Path { get; init; }
|
||||
|
||||
/// <summary>Binary format</summary>
|
||||
public required BinaryFormat Format { get; init; }
|
||||
|
||||
/// <summary>Architecture (x86_64, arm64, etc.)</summary>
|
||||
public required string Architecture { get; init; }
|
||||
|
||||
/// <summary>Direct library dependencies (NEEDED/imports)</summary>
|
||||
public ImmutableArray<LibraryDependency> Dependencies { get; init; } = [];
|
||||
|
||||
/// <summary>Imported symbols</summary>
|
||||
public ImmutableArray<ImportedSymbol> ImportedSymbols { get; init; } = [];
|
||||
|
||||
/// <summary>Exported symbols</summary>
|
||||
public ImmutableArray<ExportedSymbol> ExportedSymbols { get; init; } = [];
|
||||
|
||||
/// <summary>RPATH entries (ELF)</summary>
|
||||
public ImmutableArray<string> Rpath { get; init; } = [];
|
||||
|
||||
/// <summary>RUNPATH entries (ELF)</summary>
|
||||
public ImmutableArray<string> RunPath { get; init; } = [];
|
||||
|
||||
/// <summary>Whether the binary has ASLR/PIE</summary>
|
||||
public bool HasPie { get; init; }
|
||||
|
||||
/// <summary>Whether the binary is stripped</summary>
|
||||
public bool IsStripped { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Binary format.
|
||||
/// </summary>
|
||||
public enum BinaryFormat
|
||||
{
|
||||
/// <summary>ELF (Linux/Unix)</summary>
|
||||
Elf,
|
||||
|
||||
/// <summary>PE (Windows)</summary>
|
||||
Pe,
|
||||
|
||||
/// <summary>Mach-O (macOS)</summary>
|
||||
MachO,
|
||||
|
||||
/// <summary>.NET assembly</summary>
|
||||
DotNetAssembly,
|
||||
|
||||
/// <summary>Java class/JAR</summary>
|
||||
JavaClass,
|
||||
|
||||
/// <summary>WebAssembly</summary>
|
||||
Wasm
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A library dependency.
|
||||
/// </summary>
|
||||
public sealed record LibraryDependency(
|
||||
string Name,
|
||||
string? Version,
|
||||
bool IsDelayLoad,
|
||||
bool IsOptional
|
||||
);
|
||||
|
||||
/// <summary>
|
||||
/// An imported symbol.
|
||||
/// </summary>
|
||||
public sealed record ImportedSymbol(
|
||||
string Name,
|
||||
string? Library,
|
||||
string? SymbolVersion,
|
||||
bool IsWeak
|
||||
);
|
||||
|
||||
/// <summary>
|
||||
/// An exported symbol.
|
||||
/// </summary>
|
||||
public sealed record ExportedSymbol(
|
||||
string Name,
|
||||
string? SymbolVersion,
|
||||
ulong? Address,
|
||||
bool IsDefault
|
||||
);
|
||||
|
||||
/// <summary>
|
||||
/// Loader context - environment affecting symbol resolution.
|
||||
/// </summary>
|
||||
public sealed record LoaderContext
|
||||
{
|
||||
/// <summary>LD_LIBRARY_PATH or equivalent</summary>
|
||||
public ImmutableArray<string> LibraryPath { get; init; } = [];
|
||||
|
||||
/// <summary>LD_PRELOAD or equivalent</summary>
|
||||
public ImmutableArray<string> Preloads { get; init; } = [];
|
||||
|
||||
/// <summary>System library directories</summary>
|
||||
public ImmutableArray<string> SystemPaths { get; init; } = [];
|
||||
|
||||
/// <summary>Available libraries in the environment</summary>
|
||||
public ImmutableArray<AvailableLibrary> AvailableLibraries { get; init; } = [];
|
||||
|
||||
/// <summary>Whether to consider LD_PRELOAD interposition</summary>
|
||||
public bool ConsiderPreloadInterposition { get; init; } = true;
|
||||
|
||||
/// <summary>Operating system</summary>
|
||||
public required OperatingSystemType OS { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Operating system type.
|
||||
/// </summary>
|
||||
public enum OperatingSystemType
|
||||
{
|
||||
Linux,
|
||||
Windows,
|
||||
MacOS,
|
||||
FreeBSD,
|
||||
Unknown
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A library available in the loader context.
|
||||
/// </summary>
|
||||
public sealed record AvailableLibrary(
|
||||
string Name,
|
||||
string Path,
|
||||
string? Version,
|
||||
ImmutableArray<ExportedSymbol> Exports
|
||||
);
|
||||
|
||||
/// <summary>
|
||||
/// Input for Layer 2 analysis.
|
||||
/// </summary>
|
||||
public sealed record Layer2AnalysisInput
|
||||
{
|
||||
public required VulnerableSymbol Symbol { get; init; }
|
||||
public required BinaryArtifact Binary { get; init; }
|
||||
public required LoaderContext Context { get; init; }
|
||||
public Layer2AnalysisOptions? Options { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Options for Layer 2 analysis.
|
||||
/// </summary>
|
||||
public sealed record Layer2AnalysisOptions
|
||||
{
|
||||
/// <summary>Consider symbol versioning (e.g., GLIBC_2.17)</summary>
|
||||
public bool ConsiderSymbolVersioning { get; init; } = true;
|
||||
|
||||
/// <summary>Consider delay-load DLLs (PE)</summary>
|
||||
public bool ConsiderDelayLoad { get; init; } = true;
|
||||
|
||||
/// <summary>Consider weak symbols</summary>
|
||||
public bool ConsiderWeakSymbols { get; init; } = true;
|
||||
|
||||
/// <summary>Consider side-by-side manifests (Windows)</summary>
|
||||
public bool ConsiderSxsManifests { get; init; } = true;
|
||||
}
|
||||
@@ -0,0 +1,205 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
// Copyright (c) StellaOps
|
||||
|
||||
using System.Collections.Immutable;
|
||||
using StellaOps.Scanner.Reachability.Stack;
|
||||
|
||||
namespace StellaOps.Scanner.Reachability.Layer3;
|
||||
|
||||
/// <summary>
|
||||
/// Layer 3 analyzer: Runtime gating detection.
|
||||
/// Determines if any feature flag, configuration, or environment condition
|
||||
/// blocks execution of the vulnerable code path.
|
||||
/// </summary>
|
||||
public interface ILayer3Analyzer
|
||||
{
|
||||
/// <summary>
|
||||
/// Analyzes whether runtime conditions gate (block) execution of a call path.
|
||||
/// </summary>
|
||||
/// <param name="path">The call path to analyze for gating conditions</param>
|
||||
/// <param name="context">Runtime context (config, env vars, etc.)</param>
|
||||
/// <param name="ct">Cancellation token</param>
|
||||
/// <returns>Layer 3 gating analysis result</returns>
|
||||
Task<ReachabilityLayer3> AnalyzeAsync(
|
||||
CallPath path,
|
||||
RuntimeContext context,
|
||||
CancellationToken ct = default);
|
||||
|
||||
/// <summary>
|
||||
/// Analyzes gating for multiple paths and aggregates results.
|
||||
/// </summary>
|
||||
/// <param name="paths">Call paths to analyze</param>
|
||||
/// <param name="context">Runtime context</param>
|
||||
/// <param name="ct">Cancellation token</param>
|
||||
/// <returns>Aggregated Layer 3 result</returns>
|
||||
Task<ReachabilityLayer3> AnalyzeMultipleAsync(
|
||||
ImmutableArray<CallPath> paths,
|
||||
RuntimeContext context,
|
||||
CancellationToken ct = default);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Runtime context - configuration and environment affecting execution.
|
||||
/// </summary>
|
||||
public sealed record RuntimeContext
|
||||
{
|
||||
/// <summary>Environment variables</summary>
|
||||
public ImmutableDictionary<string, string> EnvironmentVariables { get; init; } =
|
||||
ImmutableDictionary<string, string>.Empty;
|
||||
|
||||
/// <summary>Configuration values from files/services</summary>
|
||||
public ImmutableDictionary<string, ConfigValue> Configuration { get; init; } =
|
||||
ImmutableDictionary<string, ConfigValue>.Empty;
|
||||
|
||||
/// <summary>Feature flags and their states</summary>
|
||||
public ImmutableDictionary<string, FeatureFlag> FeatureFlags { get; init; } =
|
||||
ImmutableDictionary<string, FeatureFlag>.Empty;
|
||||
|
||||
/// <summary>Build/compile-time configuration</summary>
|
||||
public BuildConfiguration? BuildConfig { get; init; }
|
||||
|
||||
/// <summary>Platform information</summary>
|
||||
public PlatformInfo? Platform { get; init; }
|
||||
|
||||
/// <summary>Process capabilities/privileges</summary>
|
||||
public ImmutableArray<string> Capabilities { get; init; } = [];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A configuration value.
|
||||
/// </summary>
|
||||
public sealed record ConfigValue(
|
||||
string Key,
|
||||
string? Value,
|
||||
ConfigValueSource Source,
|
||||
bool IsSecret
|
||||
);
|
||||
|
||||
/// <summary>
|
||||
/// Source of a configuration value.
|
||||
/// </summary>
|
||||
public enum ConfigValueSource
|
||||
{
|
||||
EnvironmentVariable,
|
||||
ConfigFile,
|
||||
CommandLine,
|
||||
RemoteService,
|
||||
Default,
|
||||
Unknown
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A feature flag.
|
||||
/// </summary>
|
||||
public sealed record FeatureFlag(
|
||||
string Name,
|
||||
bool IsEnabled,
|
||||
FeatureFlagSource Source,
|
||||
string? Description
|
||||
);
|
||||
|
||||
/// <summary>
|
||||
/// Source of a feature flag.
|
||||
/// </summary>
|
||||
public enum FeatureFlagSource
|
||||
{
|
||||
CompileTime,
|
||||
ConfigFile,
|
||||
RemoteService,
|
||||
EnvironmentVariable,
|
||||
Default,
|
||||
Unknown
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Build/compile-time configuration.
|
||||
/// </summary>
|
||||
public sealed record BuildConfiguration
|
||||
{
|
||||
/// <summary>Whether this is a debug build</summary>
|
||||
public bool IsDebugBuild { get; init; }
|
||||
|
||||
/// <summary>Defined preprocessor symbols</summary>
|
||||
public ImmutableArray<string> DefineConstants { get; init; } = [];
|
||||
|
||||
/// <summary>Target framework</summary>
|
||||
public string? TargetFramework { get; init; }
|
||||
|
||||
/// <summary>Build mode (Debug, Release, etc.)</summary>
|
||||
public string? BuildMode { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Platform information.
|
||||
/// </summary>
|
||||
public sealed record PlatformInfo
|
||||
{
|
||||
/// <summary>Operating system</summary>
|
||||
public required string OS { get; init; }
|
||||
|
||||
/// <summary>OS version</summary>
|
||||
public string? OSVersion { get; init; }
|
||||
|
||||
/// <summary>Architecture (x64, arm64, etc.)</summary>
|
||||
public required string Architecture { get; init; }
|
||||
|
||||
/// <summary>Whether running in container</summary>
|
||||
public bool IsContainer { get; init; }
|
||||
|
||||
/// <summary>Container runtime if applicable</summary>
|
||||
public string? ContainerRuntime { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Input for Layer 3 analysis.
|
||||
/// </summary>
|
||||
public sealed record Layer3AnalysisInput
|
||||
{
|
||||
public required CallPath Path { get; init; }
|
||||
public required RuntimeContext Context { get; init; }
|
||||
public Layer3AnalysisOptions? Options { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Options for Layer 3 analysis.
|
||||
/// </summary>
|
||||
public sealed record Layer3AnalysisOptions
|
||||
{
|
||||
/// <summary>Detect feature flag patterns in code</summary>
|
||||
public bool DetectFeatureFlags { get; init; } = true;
|
||||
|
||||
/// <summary>Detect environment variable checks</summary>
|
||||
public bool DetectEnvVarChecks { get; init; } = true;
|
||||
|
||||
/// <summary>Detect configuration value checks</summary>
|
||||
public bool DetectConfigChecks { get; init; } = true;
|
||||
|
||||
/// <summary>Detect platform checks</summary>
|
||||
public bool DetectPlatformChecks { get; init; } = true;
|
||||
|
||||
/// <summary>Detect capability/privilege checks</summary>
|
||||
public bool DetectCapabilityChecks { get; init; } = true;
|
||||
|
||||
/// <summary>Feature flag patterns to detect (regex)</summary>
|
||||
public ImmutableArray<string> FeatureFlagPatterns { get; init; } = [
|
||||
@"FeatureFlags?\.",
|
||||
@"IsFeatureEnabled",
|
||||
@"Feature\.IsEnabled",
|
||||
@"LaunchDarkly",
|
||||
@"Unleash",
|
||||
@"ConfigCat"
|
||||
];
|
||||
|
||||
/// <summary>Known blocking conditions</summary>
|
||||
public ImmutableArray<KnownGatingPattern> KnownPatterns { get; init; } = [];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A known gating pattern to detect.
|
||||
/// </summary>
|
||||
public sealed record KnownGatingPattern(
|
||||
string Pattern,
|
||||
GatingType Type,
|
||||
string Description,
|
||||
bool IsBlockingByDefault
|
||||
);
|
||||
@@ -0,0 +1,364 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
// Copyright (c) StellaOps
|
||||
|
||||
using System.Collections.Immutable;
|
||||
using StellaOps.Scanner.Explainability.Assumptions;
|
||||
|
||||
namespace StellaOps.Scanner.Reachability.Stack;
|
||||
|
||||
/// <summary>
|
||||
/// Composite three-layer reachability model.
|
||||
/// Exploitability is proven only when ALL THREE layers align.
|
||||
/// </summary>
|
||||
public sealed record ReachabilityStack
|
||||
{
|
||||
/// <summary>Unique identifier for this reachability assessment</summary>
|
||||
public required string Id { get; init; }
|
||||
|
||||
/// <summary>The finding this reachability assessment applies to</summary>
|
||||
public required string FindingId { get; init; }
|
||||
|
||||
/// <summary>The vulnerable symbol being analyzed</summary>
|
||||
public required VulnerableSymbol Symbol { get; init; }
|
||||
|
||||
/// <summary>Layer 1: Static call graph analysis</summary>
|
||||
public required ReachabilityLayer1 StaticCallGraph { get; init; }
|
||||
|
||||
/// <summary>Layer 2: Binary/loader resolution</summary>
|
||||
public required ReachabilityLayer2 BinaryResolution { get; init; }
|
||||
|
||||
/// <summary>Layer 3: Runtime gating analysis</summary>
|
||||
public required ReachabilityLayer3 RuntimeGating { get; init; }
|
||||
|
||||
/// <summary>Final verdict derived from all three layers</summary>
|
||||
public required ReachabilityVerdict Verdict { get; init; }
|
||||
|
||||
/// <summary>When this assessment was performed</summary>
|
||||
public required DateTimeOffset AnalyzedAt { get; init; }
|
||||
|
||||
/// <summary>Human-readable explanation of the verdict</summary>
|
||||
public string? Explanation { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A symbol that may be vulnerable in the target.
|
||||
/// </summary>
|
||||
public sealed record VulnerableSymbol(
|
||||
string Name,
|
||||
string? Library,
|
||||
string? Version,
|
||||
string VulnerabilityId,
|
||||
SymbolType Type
|
||||
);
|
||||
|
||||
/// <summary>
|
||||
/// Type of symbol being analyzed.
|
||||
/// </summary>
|
||||
public enum SymbolType
|
||||
{
|
||||
/// <summary>Native function (C/C++)</summary>
|
||||
Function,
|
||||
|
||||
/// <summary>.NET method</summary>
|
||||
Method,
|
||||
|
||||
/// <summary>Java method</summary>
|
||||
JavaMethod,
|
||||
|
||||
/// <summary>JavaScript/Node function</summary>
|
||||
JsFunction,
|
||||
|
||||
/// <summary>Python function</summary>
|
||||
PyFunction,
|
||||
|
||||
/// <summary>Go function</summary>
|
||||
GoFunction,
|
||||
|
||||
/// <summary>Rust function</summary>
|
||||
RustFunction
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Layer 1: Static call graph reachability.
|
||||
/// Determines if the vulnerable symbol is reachable from any entrypoint via static analysis.
|
||||
/// </summary>
|
||||
public sealed record ReachabilityLayer1
|
||||
{
|
||||
/// <summary>Whether the symbol is reachable from any entrypoint</summary>
|
||||
public required bool IsReachable { get; init; }
|
||||
|
||||
/// <summary>Call paths from entrypoints to the vulnerable symbol</summary>
|
||||
public ImmutableArray<CallPath> Paths { get; init; } = [];
|
||||
|
||||
/// <summary>Entrypoints that can reach the vulnerable symbol</summary>
|
||||
public ImmutableArray<Entrypoint> ReachingEntrypoints { get; init; } = [];
|
||||
|
||||
/// <summary>Confidence level of this layer's analysis</summary>
|
||||
public required ConfidenceLevel Confidence { get; init; }
|
||||
|
||||
/// <summary>Analysis method used</summary>
|
||||
public string? AnalysisMethod { get; init; }
|
||||
|
||||
/// <summary>Any limitations or caveats</summary>
|
||||
public ImmutableArray<string> Limitations { get; init; } = [];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A call path from entrypoint to vulnerable symbol.
|
||||
/// </summary>
|
||||
public sealed record CallPath
|
||||
{
|
||||
/// <summary>Sequence of method/function calls</summary>
|
||||
public required ImmutableArray<CallSite> Sites { get; init; }
|
||||
|
||||
/// <summary>The entrypoint this path starts from</summary>
|
||||
public required Entrypoint Entrypoint { get; init; }
|
||||
|
||||
/// <summary>Path confidence score</summary>
|
||||
public double Confidence { get; init; } = 1.0;
|
||||
|
||||
/// <summary>Whether this path has any conditional branches</summary>
|
||||
public bool HasConditionals { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A single call site in a call path.
|
||||
/// </summary>
|
||||
public sealed record CallSite(
|
||||
string MethodName,
|
||||
string? ClassName,
|
||||
string? FileName,
|
||||
int? LineNumber,
|
||||
CallSiteType Type
|
||||
);
|
||||
|
||||
/// <summary>
|
||||
/// Type of call site.
|
||||
/// </summary>
|
||||
public enum CallSiteType
|
||||
{
|
||||
Direct,
|
||||
Virtual,
|
||||
Interface,
|
||||
Delegate,
|
||||
Reflection,
|
||||
Dynamic
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// An application entrypoint.
|
||||
/// </summary>
|
||||
public sealed record Entrypoint(
|
||||
string Name,
|
||||
EntrypointType Type,
|
||||
string? Location,
|
||||
string? Description
|
||||
);
|
||||
|
||||
/// <summary>
|
||||
/// Type of entrypoint.
|
||||
/// </summary>
|
||||
public enum EntrypointType
|
||||
{
|
||||
Main,
|
||||
HttpEndpoint,
|
||||
MessageHandler,
|
||||
Timer,
|
||||
EventHandler,
|
||||
Constructor,
|
||||
StaticInitializer,
|
||||
TestMethod
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Layer 2: Binary/loader resolution.
|
||||
/// Determines if the dynamic loader actually links the vulnerable symbol.
|
||||
/// </summary>
|
||||
public sealed record ReachabilityLayer2
|
||||
{
|
||||
/// <summary>Whether the symbol is actually resolved/linked at runtime</summary>
|
||||
public required bool IsResolved { get; init; }
|
||||
|
||||
/// <summary>Resolution details if resolved</summary>
|
||||
public SymbolResolution? Resolution { get; init; }
|
||||
|
||||
/// <summary>The loader rule that determined resolution</summary>
|
||||
public LoaderRule? AppliedRule { get; init; }
|
||||
|
||||
/// <summary>Confidence level of this layer's analysis</summary>
|
||||
public required ConfidenceLevel Confidence { get; init; }
|
||||
|
||||
/// <summary>Why the symbol is/isn't resolved</summary>
|
||||
public string? Reason { get; init; }
|
||||
|
||||
/// <summary>Alternative symbols that could be loaded instead</summary>
|
||||
public ImmutableArray<string> Alternatives { get; init; } = [];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Details of how a symbol was resolved.
|
||||
/// </summary>
|
||||
public sealed record SymbolResolution(
|
||||
string SymbolName,
|
||||
string ResolvedLibrary,
|
||||
string? ResolvedVersion,
|
||||
string? SymbolVersion,
|
||||
ResolutionMethod Method
|
||||
);
|
||||
|
||||
/// <summary>
|
||||
/// How the symbol was resolved.
|
||||
/// </summary>
|
||||
public enum ResolutionMethod
|
||||
{
|
||||
DirectLink,
|
||||
DynamicLoad,
|
||||
DelayLoad,
|
||||
WeakSymbol,
|
||||
Interposition
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A loader rule that affected resolution.
|
||||
/// </summary>
|
||||
public sealed record LoaderRule(
|
||||
LoaderRuleType Type,
|
||||
string Value,
|
||||
string? Source
|
||||
);
|
||||
|
||||
/// <summary>
|
||||
/// Type of loader rule.
|
||||
/// </summary>
|
||||
public enum LoaderRuleType
|
||||
{
|
||||
Rpath,
|
||||
RunPath,
|
||||
LdLibraryPath,
|
||||
LdPreload,
|
||||
SymbolVersion,
|
||||
ImportTable,
|
||||
DelayLoadTable,
|
||||
SxsManifest
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Layer 3: Runtime gating analysis.
|
||||
/// Determines if any feature flag, config, or environment blocks execution.
|
||||
/// </summary>
|
||||
public sealed record ReachabilityLayer3
|
||||
{
|
||||
/// <summary>Whether execution is gated (blocked) by runtime conditions</summary>
|
||||
public required bool IsGated { get; init; }
|
||||
|
||||
/// <summary>Gating conditions found</summary>
|
||||
public ImmutableArray<GatingCondition> Conditions { get; init; } = [];
|
||||
|
||||
/// <summary>Overall gating outcome</summary>
|
||||
public required GatingOutcome Outcome { get; init; }
|
||||
|
||||
/// <summary>Confidence level of this layer's analysis</summary>
|
||||
public required ConfidenceLevel Confidence { get; init; }
|
||||
|
||||
/// <summary>Description of gating analysis</summary>
|
||||
public string? Description { get; init; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A condition that gates (potentially blocks) execution.
|
||||
/// </summary>
|
||||
public sealed record GatingCondition(
|
||||
GatingType Type,
|
||||
string Description,
|
||||
string? ConfigKey,
|
||||
string? EnvVar,
|
||||
bool IsBlocking,
|
||||
GatingStatus Status
|
||||
);
|
||||
|
||||
/// <summary>
|
||||
/// Type of gating condition.
|
||||
/// </summary>
|
||||
public enum GatingType
|
||||
{
|
||||
/// <summary>Feature flag check (e.g., if (FeatureFlags.UseNewAuth))</summary>
|
||||
FeatureFlag,
|
||||
|
||||
/// <summary>Environment variable check</summary>
|
||||
EnvironmentVariable,
|
||||
|
||||
/// <summary>Configuration value check</summary>
|
||||
ConfigurationValue,
|
||||
|
||||
/// <summary>Compile-time conditional (#if DEBUG)</summary>
|
||||
CompileTimeConditional,
|
||||
|
||||
/// <summary>Platform check (RuntimeInformation.IsOSPlatform)</summary>
|
||||
PlatformCheck,
|
||||
|
||||
/// <summary>Capability/privilege check</summary>
|
||||
CapabilityCheck,
|
||||
|
||||
/// <summary>License/subscription check</summary>
|
||||
LicenseCheck,
|
||||
|
||||
/// <summary>A/B test or experiment flag</summary>
|
||||
ExperimentFlag
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Status of a gating condition.
|
||||
/// </summary>
|
||||
public enum GatingStatus
|
||||
{
|
||||
/// <summary>Condition is enabled, code path is accessible</summary>
|
||||
Enabled,
|
||||
|
||||
/// <summary>Condition is disabled, code path is blocked</summary>
|
||||
Disabled,
|
||||
|
||||
/// <summary>Condition status is unknown</summary>
|
||||
Unknown,
|
||||
|
||||
/// <summary>Condition is configurable at runtime</summary>
|
||||
RuntimeConfigurable
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Overall outcome of gating analysis.
|
||||
/// </summary>
|
||||
public enum GatingOutcome
|
||||
{
|
||||
/// <summary>No gating detected, path is open</summary>
|
||||
NotGated,
|
||||
|
||||
/// <summary>Gating detected and path is blocked</summary>
|
||||
Blocked,
|
||||
|
||||
/// <summary>Gating detected but path is conditionally open</summary>
|
||||
Conditional,
|
||||
|
||||
/// <summary>Unable to determine gating status</summary>
|
||||
Unknown
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Final reachability verdict derived from all three layers.
|
||||
/// </summary>
|
||||
public enum ReachabilityVerdict
|
||||
{
|
||||
/// <summary>All 3 layers confirm reachable - definitely exploitable</summary>
|
||||
Exploitable,
|
||||
|
||||
/// <summary>L1+L2 confirm, L3 unknown - likely exploitable</summary>
|
||||
LikelyExploitable,
|
||||
|
||||
/// <summary>L1 confirms, L2+L3 unknown - possibly exploitable</summary>
|
||||
PossiblyExploitable,
|
||||
|
||||
/// <summary>Any layer definitively blocks - not exploitable</summary>
|
||||
Unreachable,
|
||||
|
||||
/// <summary>Insufficient data to determine</summary>
|
||||
Unknown
|
||||
}
|
||||
@@ -0,0 +1,210 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
// Copyright (c) StellaOps
|
||||
|
||||
using System.Text;
|
||||
using StellaOps.Scanner.Explainability.Assumptions;
|
||||
|
||||
namespace StellaOps.Scanner.Reachability.Stack;
|
||||
|
||||
/// <summary>
|
||||
/// Evaluates three-layer reachability to produce a final verdict.
|
||||
/// </summary>
|
||||
public interface IReachabilityStackEvaluator
|
||||
{
|
||||
/// <summary>
|
||||
/// Evaluates the three layers and produces a complete ReachabilityStack with verdict.
|
||||
/// </summary>
|
||||
ReachabilityStack Evaluate(
|
||||
string findingId,
|
||||
VulnerableSymbol symbol,
|
||||
ReachabilityLayer1 layer1,
|
||||
ReachabilityLayer2 layer2,
|
||||
ReachabilityLayer3 layer3);
|
||||
|
||||
/// <summary>
|
||||
/// Derives the verdict from three layers.
|
||||
/// </summary>
|
||||
ReachabilityVerdict DeriveVerdict(
|
||||
ReachabilityLayer1 layer1,
|
||||
ReachabilityLayer2 layer2,
|
||||
ReachabilityLayer3 layer3);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Default implementation of <see cref="IReachabilityStackEvaluator"/>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Verdict Truth Table:
|
||||
/// | L1 Reachable | L2 Resolved | L3 Gated | Verdict |
|
||||
/// |--------------|-------------|----------|---------|
|
||||
/// | Yes | Yes | No | Exploitable |
|
||||
/// | Yes | Yes | Unknown | LikelyExploitable |
|
||||
/// | Yes | Yes | Yes | Unreachable |
|
||||
/// | Yes | Unknown | Unknown | PossiblyExploitable |
|
||||
/// | Yes | No | * | Unreachable |
|
||||
/// | No | * | * | Unreachable |
|
||||
/// | Unknown | * | * | Unknown |
|
||||
/// </remarks>
|
||||
public sealed class ReachabilityStackEvaluator : IReachabilityStackEvaluator
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public ReachabilityStack Evaluate(
|
||||
string findingId,
|
||||
VulnerableSymbol symbol,
|
||||
ReachabilityLayer1 layer1,
|
||||
ReachabilityLayer2 layer2,
|
||||
ReachabilityLayer3 layer3)
|
||||
{
|
||||
var verdict = DeriveVerdict(layer1, layer2, layer3);
|
||||
var explanation = GenerateExplanation(layer1, layer2, layer3, verdict);
|
||||
|
||||
return new ReachabilityStack
|
||||
{
|
||||
Id = Guid.NewGuid().ToString("N"),
|
||||
FindingId = findingId,
|
||||
Symbol = symbol,
|
||||
StaticCallGraph = layer1,
|
||||
BinaryResolution = layer2,
|
||||
RuntimeGating = layer3,
|
||||
Verdict = verdict,
|
||||
AnalyzedAt = DateTimeOffset.UtcNow,
|
||||
Explanation = explanation
|
||||
};
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public ReachabilityVerdict DeriveVerdict(
|
||||
ReachabilityLayer1 layer1,
|
||||
ReachabilityLayer2 layer2,
|
||||
ReachabilityLayer3 layer3)
|
||||
{
|
||||
// Check for unknown L1 - can't determine anything
|
||||
if (layer1.Confidence == ConfidenceLevel.Low && !layer1.IsReachable && layer1.Paths.Length == 0)
|
||||
{
|
||||
return ReachabilityVerdict.Unknown;
|
||||
}
|
||||
|
||||
// L1 definitively blocks (not reachable via static analysis)
|
||||
if (!layer1.IsReachable && layer1.Confidence >= ConfidenceLevel.Medium)
|
||||
{
|
||||
return ReachabilityVerdict.Unreachable;
|
||||
}
|
||||
|
||||
// L2 definitively blocks (symbol not linked)
|
||||
if (!layer2.IsResolved && layer2.Confidence >= ConfidenceLevel.Medium)
|
||||
{
|
||||
return ReachabilityVerdict.Unreachable;
|
||||
}
|
||||
|
||||
// L3 definitively blocks (gating prevents execution)
|
||||
if (layer3.IsGated && layer3.Outcome == GatingOutcome.Blocked && layer3.Confidence >= ConfidenceLevel.Medium)
|
||||
{
|
||||
return ReachabilityVerdict.Unreachable;
|
||||
}
|
||||
|
||||
// All three confirm reachable
|
||||
if (layer1.IsReachable &&
|
||||
layer2.IsResolved &&
|
||||
!layer3.IsGated &&
|
||||
layer3.Outcome == GatingOutcome.NotGated)
|
||||
{
|
||||
return ReachabilityVerdict.Exploitable;
|
||||
}
|
||||
|
||||
// L1 + L2 confirm, but L3 blocked with low confidence (can't trust the block)
|
||||
// Treat as if L3 analysis is inconclusive - still exploitable since we can't rely on the gate
|
||||
if (layer1.IsReachable &&
|
||||
layer2.IsResolved &&
|
||||
layer3.Outcome == GatingOutcome.Blocked &&
|
||||
layer3.Confidence < ConfidenceLevel.Medium)
|
||||
{
|
||||
return ReachabilityVerdict.Exploitable;
|
||||
}
|
||||
|
||||
// L1 + L2 confirm, L3 unknown/conditional
|
||||
if (layer1.IsReachable &&
|
||||
layer2.IsResolved &&
|
||||
(layer3.Outcome == GatingOutcome.Unknown || layer3.Outcome == GatingOutcome.Conditional))
|
||||
{
|
||||
return ReachabilityVerdict.LikelyExploitable;
|
||||
}
|
||||
|
||||
// L1 confirms, L2/L3 unknown
|
||||
if (layer1.IsReachable &&
|
||||
(layer2.Confidence == ConfidenceLevel.Low || !layer2.IsResolved))
|
||||
{
|
||||
return ReachabilityVerdict.PossiblyExploitable;
|
||||
}
|
||||
|
||||
// Default to unknown if we can't determine
|
||||
return ReachabilityVerdict.Unknown;
|
||||
}
|
||||
|
||||
private static string GenerateExplanation(
|
||||
ReachabilityLayer1 layer1,
|
||||
ReachabilityLayer2 layer2,
|
||||
ReachabilityLayer3 layer3,
|
||||
ReachabilityVerdict verdict)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
|
||||
// Verdict summary
|
||||
sb.AppendLine(verdict switch
|
||||
{
|
||||
ReachabilityVerdict.Exploitable =>
|
||||
"All three reachability layers confirm the vulnerability is exploitable.",
|
||||
ReachabilityVerdict.LikelyExploitable =>
|
||||
"Static and binary analysis confirm reachability. Runtime gating status is unclear.",
|
||||
ReachabilityVerdict.PossiblyExploitable =>
|
||||
"Static analysis shows reachability, but binary resolution or runtime gating is uncertain.",
|
||||
ReachabilityVerdict.Unreachable =>
|
||||
"At least one reachability layer definitively blocks exploitation.",
|
||||
ReachabilityVerdict.Unknown =>
|
||||
"Insufficient evidence to determine reachability.",
|
||||
_ => "Verdict determination failed."
|
||||
});
|
||||
|
||||
sb.AppendLine();
|
||||
|
||||
// Layer 1 details
|
||||
sb.AppendLine($"**Layer 1 (Static Call Graph)**: {(layer1.IsReachable ? "Reachable" : "Not reachable")} [{layer1.Confidence}]");
|
||||
if (layer1.Paths.Length > 0)
|
||||
{
|
||||
sb.AppendLine($" - {layer1.Paths.Length} call path(s) found");
|
||||
sb.AppendLine($" - {layer1.ReachingEntrypoints.Length} entrypoint(s) can reach vulnerable code");
|
||||
}
|
||||
if (layer1.AnalysisMethod is not null)
|
||||
{
|
||||
sb.AppendLine($" - Analysis method: {layer1.AnalysisMethod}");
|
||||
}
|
||||
|
||||
// Layer 2 details
|
||||
sb.AppendLine($"**Layer 2 (Binary Resolution)**: {(layer2.IsResolved ? "Resolved" : "Not resolved")} [{layer2.Confidence}]");
|
||||
if (layer2.Resolution is not null)
|
||||
{
|
||||
sb.AppendLine($" - Symbol: {layer2.Resolution.SymbolName}");
|
||||
sb.AppendLine($" - Library: {layer2.Resolution.ResolvedLibrary}");
|
||||
if (layer2.Resolution.SymbolVersion is not null)
|
||||
{
|
||||
sb.AppendLine($" - Version: {layer2.Resolution.SymbolVersion}");
|
||||
}
|
||||
}
|
||||
if (layer2.Reason is not null)
|
||||
{
|
||||
sb.AppendLine($" - Reason: {layer2.Reason}");
|
||||
}
|
||||
|
||||
// Layer 3 details
|
||||
sb.AppendLine($"**Layer 3 (Runtime Gating)**: {(layer3.IsGated ? "Gated" : "Not gated")} - {layer3.Outcome} [{layer3.Confidence}]");
|
||||
if (layer3.Conditions.Length > 0)
|
||||
{
|
||||
foreach (var condition in layer3.Conditions)
|
||||
{
|
||||
var status = condition.IsBlocking ? "BLOCKING" : "non-blocking";
|
||||
sb.AppendLine($" - [{status}] {condition.Type}: {condition.Description}");
|
||||
}
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
}
|
||||
@@ -1 +1,26 @@
|
||||
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="10.0.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.0" />
|
||||
<PackageReference Include="Npgsql" Version="9.0.3" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\StellaOps.Scanner.Core\StellaOps.Scanner.Core.csproj" />
|
||||
<ProjectReference Include="..\StellaOps.Scanner.Explainability\StellaOps.Scanner.Explainability.csproj" />
|
||||
<ProjectReference Include="..\StellaOps.Scanner.Cache\StellaOps.Scanner.Cache.csproj" />
|
||||
<ProjectReference Include="..\StellaOps.Scanner.ProofSpine\StellaOps.Scanner.ProofSpine.csproj" />
|
||||
<ProjectReference Include="..\StellaOps.Scanner.Surface.Env\StellaOps.Scanner.Surface.Env.csproj" />
|
||||
<ProjectReference Include="..\StellaOps.Scanner.SmartDiff\StellaOps.Scanner.SmartDiff.csproj" />
|
||||
<ProjectReference Include="..\..\StellaOps.Scanner.Analyzers.Native\StellaOps.Scanner.Analyzers.Native.csproj" />
|
||||
<ProjectReference Include="..\..\..\Attestor\StellaOps.Attestor\StellaOps.Attestor.Core\StellaOps.Attestor.Core.csproj" />
|
||||
<ProjectReference Include="..\..\..\Attestor\StellaOps.Attestor.Envelope\StellaOps.Attestor.Envelope.csproj" />
|
||||
<ProjectReference Include="..\..\..\Attestor\__Libraries\StellaOps.Attestor.ProofChain\StellaOps.Attestor.ProofChain.csproj" />
|
||||
<ProjectReference Include="..\..\..\__Libraries\StellaOps.Replay.Core\StellaOps.Replay.Core.csproj" />
|
||||
<ProjectReference Include="..\..\..\__Libraries\StellaOps.Cryptography\StellaOps.Cryptography.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -127,7 +127,7 @@ public sealed class OciArtifactPusher
|
||||
|
||||
return new OciArtifactManifest
|
||||
{
|
||||
MediaType = OciMediaTypes.ArtifactManifest,
|
||||
MediaType = OciMediaTypes.ImageManifest,
|
||||
ArtifactType = request.ArtifactType,
|
||||
Config = new OciDescriptor
|
||||
{
|
||||
@@ -140,7 +140,7 @@ public sealed class OciArtifactPusher
|
||||
? null
|
||||
: new OciDescriptor
|
||||
{
|
||||
MediaType = OciMediaTypes.ArtifactManifest,
|
||||
MediaType = OciMediaTypes.ImageManifest,
|
||||
Digest = request.SubjectDigest!,
|
||||
Size = 0
|
||||
},
|
||||
@@ -220,7 +220,7 @@ public sealed class OciArtifactPusher
|
||||
Content = new ByteArrayContent(manifestBytes)
|
||||
};
|
||||
|
||||
request.Content.Headers.ContentType = new MediaTypeHeaderValue(OciMediaTypes.ArtifactManifest);
|
||||
request.Content.Headers.ContentType = new MediaTypeHeaderValue(OciMediaTypes.ImageManifest);
|
||||
auth.ApplyTo(request);
|
||||
|
||||
using var response = await _httpClient.SendAsync(request, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
@@ -2,7 +2,16 @@
|
||||
|
||||
public static class OciMediaTypes
|
||||
{
|
||||
/// <summary>
|
||||
/// OCI 1.1 image manifest (used for all manifests including artifacts).
|
||||
/// </summary>
|
||||
public const string ImageManifest = "application/vnd.oci.image.manifest.v1+json";
|
||||
|
||||
/// <summary>
|
||||
/// Deprecated artifact manifest type (kept for compatibility, prefer ImageManifest).
|
||||
/// </summary>
|
||||
public const string ArtifactManifest = "application/vnd.oci.artifact.manifest.v1+json";
|
||||
|
||||
public const string EmptyConfig = "application/vnd.oci.empty.v1+json";
|
||||
public const string OctetStream = "application/octet-stream";
|
||||
|
||||
@@ -26,4 +35,30 @@ public static class OciMediaTypes
|
||||
/// Config media type for verdict attestation artifacts.
|
||||
/// </summary>
|
||||
public const string VerdictConfig = "application/vnd.stellaops.verdict.config.v1+json";
|
||||
|
||||
// Sprint: SPRINT_5200_0001_0001 - Policy Pack Distribution
|
||||
/// <summary>
|
||||
/// Media type for policy pack artifacts.
|
||||
/// </summary>
|
||||
public const string PolicyPack = "application/vnd.stellaops.policy-pack.v1+json";
|
||||
|
||||
/// <summary>
|
||||
/// Config media type for policy pack artifacts.
|
||||
/// </summary>
|
||||
public const string PolicyPackConfig = "application/vnd.stellaops.policy-pack.config.v1+json";
|
||||
|
||||
/// <summary>
|
||||
/// Media type for policy pack attestation (DSSE envelope).
|
||||
/// </summary>
|
||||
public const string PolicyPackAttestation = "application/vnd.stellaops.policy-pack.attestation.v1+json";
|
||||
|
||||
/// <summary>
|
||||
/// Media type for policy pack YAML layer.
|
||||
/// </summary>
|
||||
public const string PolicyPackYaml = "application/vnd.stellaops.policy-pack.yaml.v1";
|
||||
|
||||
/// <summary>
|
||||
/// Media type for policy pack override layer.
|
||||
/// </summary>
|
||||
public const string PolicyPackOverride = "application/vnd.stellaops.policy-pack.override.v1+json";
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ public sealed record OciArtifactManifest
|
||||
public int SchemaVersion { get; init; } = 2;
|
||||
|
||||
[JsonPropertyName("mediaType")]
|
||||
public string MediaType { get; init; } = OciMediaTypes.ArtifactManifest;
|
||||
public string MediaType { get; init; } = OciMediaTypes.ImageManifest;
|
||||
|
||||
[JsonPropertyName("artifactType")]
|
||||
public string? ArtifactType { get; init; }
|
||||
|
||||
Reference in New Issue
Block a user