up
Some checks failed
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Docs CI / lint-and-preview (push) Has been cancelled
Notify Smoke Test / Notify Unit Tests (push) Has been cancelled
Notify Smoke Test / Notifier Service Tests (push) Has been cancelled
Notify Smoke Test / Notification Smoke Test (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled
Scanner Analyzers / Discover Analyzers (push) Has been cancelled
Scanner Analyzers / Build Analyzers (push) Has been cancelled
Scanner Analyzers / Test Language Analyzers (push) Has been cancelled
Scanner Analyzers / Validate Test Fixtures (push) Has been cancelled
Scanner Analyzers / Verify Deterministic Output (push) Has been cancelled
Signals CI & Image / signals-ci (push) Has been cancelled
Signals Reachability Scoring & Events / reachability-smoke (push) Has been cancelled
Signals Reachability Scoring & Events / sign-and-upload (push) Has been cancelled
Manifest Integrity / Validate Schema Integrity (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
devportal-offline / build-offline (push) Has been cancelled
Mirror Thin Bundle Sign & Verify / mirror-sign (push) Has been cancelled
Some checks failed
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Docs CI / lint-and-preview (push) Has been cancelled
Notify Smoke Test / Notify Unit Tests (push) Has been cancelled
Notify Smoke Test / Notifier Service Tests (push) Has been cancelled
Notify Smoke Test / Notification Smoke Test (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled
Scanner Analyzers / Discover Analyzers (push) Has been cancelled
Scanner Analyzers / Build Analyzers (push) Has been cancelled
Scanner Analyzers / Test Language Analyzers (push) Has been cancelled
Scanner Analyzers / Validate Test Fixtures (push) Has been cancelled
Scanner Analyzers / Verify Deterministic Output (push) Has been cancelled
Signals CI & Image / signals-ci (push) Has been cancelled
Signals Reachability Scoring & Events / reachability-smoke (push) Has been cancelled
Signals Reachability Scoring & Events / sign-and-upload (push) Has been cancelled
Manifest Integrity / Validate Schema Integrity (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
devportal-offline / build-offline (push) Has been cancelled
Mirror Thin Bundle Sign & Verify / mirror-sign (push) Has been cancelled
This commit is contained in:
@@ -1,9 +1,17 @@
|
||||
namespace StellaOps.Scanner.Analyzers.Lang;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a language component discovered during analysis.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Updated in Sprint 0411 - Semantic Entrypoint Engine (Task 18) to include semantic fields.
|
||||
/// </remarks>
|
||||
public sealed class LanguageComponentRecord
|
||||
{
|
||||
private readonly SortedDictionary<string, string?> _metadata;
|
||||
private readonly SortedDictionary<string, LanguageComponentEvidence> _evidence;
|
||||
private readonly List<string> _capabilities;
|
||||
private readonly List<ComponentThreatVector> _threatVectors;
|
||||
|
||||
private LanguageComponentRecord(
|
||||
string analyzerId,
|
||||
@@ -14,7 +22,10 @@ public sealed class LanguageComponentRecord
|
||||
string type,
|
||||
IEnumerable<KeyValuePair<string, string?>> metadata,
|
||||
IEnumerable<LanguageComponentEvidence> evidence,
|
||||
bool usedByEntrypoint)
|
||||
bool usedByEntrypoint,
|
||||
string? intent = null,
|
||||
IEnumerable<string>? capabilities = null,
|
||||
IEnumerable<ComponentThreatVector>? threatVectors = null)
|
||||
{
|
||||
AnalyzerId = analyzerId ?? throw new ArgumentNullException(nameof(analyzerId));
|
||||
ComponentKey = componentKey ?? throw new ArgumentNullException(nameof(componentKey));
|
||||
@@ -23,6 +34,7 @@ public sealed class LanguageComponentRecord
|
||||
Version = string.IsNullOrWhiteSpace(version) ? null : version.Trim();
|
||||
Type = string.IsNullOrWhiteSpace(type) ? throw new ArgumentException("Type is required", nameof(type)) : type.Trim();
|
||||
UsedByEntrypoint = usedByEntrypoint;
|
||||
Intent = string.IsNullOrWhiteSpace(intent) ? null : intent.Trim();
|
||||
|
||||
_metadata = new SortedDictionary<string, string?>(StringComparer.Ordinal);
|
||||
foreach (var entry in metadata ?? Array.Empty<KeyValuePair<string, string?>>())
|
||||
@@ -45,6 +57,26 @@ public sealed class LanguageComponentRecord
|
||||
|
||||
_evidence[evidenceItem.ComparisonKey] = evidenceItem;
|
||||
}
|
||||
|
||||
_capabilities = new List<string>();
|
||||
foreach (var cap in capabilities ?? Array.Empty<string>())
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(cap))
|
||||
{
|
||||
_capabilities.Add(cap.Trim());
|
||||
}
|
||||
}
|
||||
_capabilities.Sort(StringComparer.Ordinal);
|
||||
|
||||
_threatVectors = new List<ComponentThreatVector>();
|
||||
foreach (var threat in threatVectors ?? Array.Empty<ComponentThreatVector>())
|
||||
{
|
||||
if (threat is not null)
|
||||
{
|
||||
_threatVectors.Add(threat);
|
||||
}
|
||||
}
|
||||
_threatVectors.Sort((a, b) => StringComparer.Ordinal.Compare(a.VectorType, b.VectorType));
|
||||
}
|
||||
|
||||
public string AnalyzerId { get; }
|
||||
@@ -61,6 +93,24 @@ public sealed class LanguageComponentRecord
|
||||
|
||||
public bool UsedByEntrypoint { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Inferred application intent (e.g., "WebServer", "CliTool", "Worker").
|
||||
/// </summary>
|
||||
/// <remarks>Part of Sprint 0411 - Semantic Entrypoint Engine (Task 18).</remarks>
|
||||
public string? Intent { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Inferred capabilities (e.g., "NetworkListen", "FileWrite", "DatabaseAccess").
|
||||
/// </summary>
|
||||
/// <remarks>Part of Sprint 0411 - Semantic Entrypoint Engine (Task 18).</remarks>
|
||||
public IReadOnlyList<string> Capabilities => _capabilities;
|
||||
|
||||
/// <summary>
|
||||
/// Identified threat vectors with confidence scores.
|
||||
/// </summary>
|
||||
/// <remarks>Part of Sprint 0411 - Semantic Entrypoint Engine (Task 18).</remarks>
|
||||
public IReadOnlyList<ComponentThreatVector> ThreatVectors => _threatVectors;
|
||||
|
||||
public IReadOnlyDictionary<string, string?> Metadata => _metadata;
|
||||
|
||||
public IReadOnlyCollection<LanguageComponentEvidence> Evidence => _evidence.Values;
|
||||
@@ -73,7 +123,10 @@ public sealed class LanguageComponentRecord
|
||||
string type,
|
||||
IEnumerable<KeyValuePair<string, string?>>? metadata = null,
|
||||
IEnumerable<LanguageComponentEvidence>? evidence = null,
|
||||
bool usedByEntrypoint = false)
|
||||
bool usedByEntrypoint = false,
|
||||
string? intent = null,
|
||||
IEnumerable<string>? capabilities = null,
|
||||
IEnumerable<ComponentThreatVector>? threatVectors = null)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(purl))
|
||||
{
|
||||
@@ -90,7 +143,10 @@ public sealed class LanguageComponentRecord
|
||||
type,
|
||||
metadata ?? Array.Empty<KeyValuePair<string, string?>>(),
|
||||
evidence ?? Array.Empty<LanguageComponentEvidence>(),
|
||||
usedByEntrypoint);
|
||||
usedByEntrypoint,
|
||||
intent,
|
||||
capabilities,
|
||||
threatVectors);
|
||||
}
|
||||
|
||||
public static LanguageComponentRecord FromExplicitKey(
|
||||
@@ -102,7 +158,10 @@ public sealed class LanguageComponentRecord
|
||||
string type,
|
||||
IEnumerable<KeyValuePair<string, string?>>? metadata = null,
|
||||
IEnumerable<LanguageComponentEvidence>? evidence = null,
|
||||
bool usedByEntrypoint = false)
|
||||
bool usedByEntrypoint = false,
|
||||
string? intent = null,
|
||||
IEnumerable<string>? capabilities = null,
|
||||
IEnumerable<ComponentThreatVector>? threatVectors = null)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(componentKey))
|
||||
{
|
||||
@@ -118,7 +177,10 @@ public sealed class LanguageComponentRecord
|
||||
type,
|
||||
metadata ?? Array.Empty<KeyValuePair<string, string?>>(),
|
||||
evidence ?? Array.Empty<LanguageComponentEvidence>(),
|
||||
usedByEntrypoint);
|
||||
usedByEntrypoint,
|
||||
intent,
|
||||
capabilities,
|
||||
threatVectors);
|
||||
}
|
||||
|
||||
internal static LanguageComponentRecord FromSnapshot(LanguageComponentSnapshot snapshot)
|
||||
@@ -144,6 +206,17 @@ public sealed class LanguageComponentRecord
|
||||
item.Sha256))
|
||||
.ToArray();
|
||||
|
||||
var threatVectors = snapshot.ThreatVectors is null or { Count: 0 }
|
||||
? Array.Empty<ComponentThreatVector>()
|
||||
: snapshot.ThreatVectors
|
||||
.Where(static item => item is not null)
|
||||
.Select(static item => new ComponentThreatVector(
|
||||
item.VectorType ?? string.Empty,
|
||||
item.Confidence,
|
||||
item.Evidence,
|
||||
item.EntryPath))
|
||||
.ToArray();
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(snapshot.Purl))
|
||||
{
|
||||
return FromPurl(
|
||||
@@ -154,7 +227,10 @@ public sealed class LanguageComponentRecord
|
||||
snapshot.Type,
|
||||
metadata,
|
||||
evidence,
|
||||
snapshot.UsedByEntrypoint);
|
||||
snapshot.UsedByEntrypoint,
|
||||
snapshot.Intent,
|
||||
snapshot.Capabilities,
|
||||
threatVectors);
|
||||
}
|
||||
|
||||
return FromExplicitKey(
|
||||
@@ -166,7 +242,10 @@ public sealed class LanguageComponentRecord
|
||||
snapshot.Type,
|
||||
metadata,
|
||||
evidence,
|
||||
snapshot.UsedByEntrypoint);
|
||||
snapshot.UsedByEntrypoint,
|
||||
snapshot.Intent,
|
||||
snapshot.Capabilities,
|
||||
threatVectors);
|
||||
}
|
||||
|
||||
internal void Merge(LanguageComponentRecord other)
|
||||
@@ -180,6 +259,34 @@ public sealed class LanguageComponentRecord
|
||||
|
||||
UsedByEntrypoint |= other.UsedByEntrypoint;
|
||||
|
||||
// Merge intent - prefer non-null
|
||||
if (string.IsNullOrEmpty(Intent) && !string.IsNullOrEmpty(other.Intent))
|
||||
{
|
||||
Intent = other.Intent;
|
||||
}
|
||||
|
||||
// Merge capabilities - union
|
||||
foreach (var cap in other._capabilities)
|
||||
{
|
||||
if (!_capabilities.Contains(cap, StringComparer.Ordinal))
|
||||
{
|
||||
_capabilities.Add(cap);
|
||||
}
|
||||
}
|
||||
_capabilities.Sort(StringComparer.Ordinal);
|
||||
|
||||
// Merge threat vectors - union by type
|
||||
var existingTypes = new HashSet<string>(_threatVectors.Select(t => t.VectorType), StringComparer.Ordinal);
|
||||
foreach (var threat in other._threatVectors)
|
||||
{
|
||||
if (!existingTypes.Contains(threat.VectorType))
|
||||
{
|
||||
_threatVectors.Add(threat);
|
||||
existingTypes.Add(threat.VectorType);
|
||||
}
|
||||
}
|
||||
_threatVectors.Sort((a, b) => StringComparer.Ordinal.Compare(a.VectorType, b.VectorType));
|
||||
|
||||
foreach (var entry in other._metadata)
|
||||
{
|
||||
if (!_metadata.TryGetValue(entry.Key, out var existing) || string.IsNullOrEmpty(existing))
|
||||
@@ -194,6 +301,44 @@ public sealed class LanguageComponentRecord
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets semantic analysis data on this component.
|
||||
/// </summary>
|
||||
/// <remarks>Part of Sprint 0411 - Semantic Entrypoint Engine (Task 18).</remarks>
|
||||
public void SetSemantics(string? intent, IEnumerable<string>? capabilities, IEnumerable<ComponentThreatVector>? threatVectors)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(intent))
|
||||
{
|
||||
Intent = intent.Trim();
|
||||
}
|
||||
|
||||
if (capabilities is not null)
|
||||
{
|
||||
foreach (var cap in capabilities)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(cap) && !_capabilities.Contains(cap.Trim(), StringComparer.Ordinal))
|
||||
{
|
||||
_capabilities.Add(cap.Trim());
|
||||
}
|
||||
}
|
||||
_capabilities.Sort(StringComparer.Ordinal);
|
||||
}
|
||||
|
||||
if (threatVectors is not null)
|
||||
{
|
||||
var existingTypes = new HashSet<string>(_threatVectors.Select(t => t.VectorType), StringComparer.Ordinal);
|
||||
foreach (var threat in threatVectors)
|
||||
{
|
||||
if (threat is not null && !existingTypes.Contains(threat.VectorType))
|
||||
{
|
||||
_threatVectors.Add(threat);
|
||||
existingTypes.Add(threat.VectorType);
|
||||
}
|
||||
}
|
||||
_threatVectors.Sort((a, b) => StringComparer.Ordinal.Compare(a.VectorType, b.VectorType));
|
||||
}
|
||||
}
|
||||
|
||||
public LanguageComponentSnapshot ToSnapshot()
|
||||
{
|
||||
return new LanguageComponentSnapshot
|
||||
@@ -205,6 +350,15 @@ public sealed class LanguageComponentRecord
|
||||
Version = Version,
|
||||
Type = Type,
|
||||
UsedByEntrypoint = UsedByEntrypoint,
|
||||
Intent = Intent,
|
||||
Capabilities = _capabilities.ToArray(),
|
||||
ThreatVectors = _threatVectors.Select(static item => new ComponentThreatVectorSnapshot
|
||||
{
|
||||
VectorType = item.VectorType,
|
||||
Confidence = item.Confidence,
|
||||
Evidence = item.Evidence,
|
||||
EntryPath = item.EntryPath,
|
||||
}).ToArray(),
|
||||
Metadata = _metadata.ToDictionary(static pair => pair.Key, static pair => pair.Value, StringComparer.Ordinal),
|
||||
Evidence = _evidence.Values.Select(static item => new LanguageComponentEvidenceSnapshot
|
||||
{
|
||||
@@ -218,6 +372,16 @@ public sealed class LanguageComponentRecord
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Represents an identified threat vector for a component.
|
||||
/// </summary>
|
||||
/// <remarks>Part of Sprint 0411 - Semantic Entrypoint Engine (Task 18).</remarks>
|
||||
public sealed record ComponentThreatVector(
|
||||
string VectorType,
|
||||
double Confidence,
|
||||
string? Evidence,
|
||||
string? EntryPath);
|
||||
|
||||
public sealed class LanguageComponentSnapshot
|
||||
{
|
||||
[JsonPropertyName("analyzerId")]
|
||||
@@ -241,6 +405,27 @@ public sealed class LanguageComponentSnapshot
|
||||
[JsonPropertyName("usedByEntrypoint")]
|
||||
public bool UsedByEntrypoint { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Inferred application intent.
|
||||
/// </summary>
|
||||
/// <remarks>Part of Sprint 0411 - Semantic Entrypoint Engine (Task 18).</remarks>
|
||||
[JsonPropertyName("intent")]
|
||||
public string? Intent { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Inferred capabilities.
|
||||
/// </summary>
|
||||
/// <remarks>Part of Sprint 0411 - Semantic Entrypoint Engine (Task 18).</remarks>
|
||||
[JsonPropertyName("capabilities")]
|
||||
public IReadOnlyList<string> Capabilities { get; set; } = Array.Empty<string>();
|
||||
|
||||
/// <summary>
|
||||
/// Identified threat vectors.
|
||||
/// </summary>
|
||||
/// <remarks>Part of Sprint 0411 - Semantic Entrypoint Engine (Task 18).</remarks>
|
||||
[JsonPropertyName("threatVectors")]
|
||||
public IReadOnlyList<ComponentThreatVectorSnapshot> ThreatVectors { get; set; } = Array.Empty<ComponentThreatVectorSnapshot>();
|
||||
|
||||
[JsonPropertyName("metadata")]
|
||||
public IDictionary<string, string?> Metadata { get; set; } = new Dictionary<string, string?>(StringComparer.Ordinal);
|
||||
|
||||
@@ -248,6 +433,25 @@ public sealed class LanguageComponentSnapshot
|
||||
public IReadOnlyList<LanguageComponentEvidenceSnapshot> Evidence { get; set; } = Array.Empty<LanguageComponentEvidenceSnapshot>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Snapshot representation of a threat vector.
|
||||
/// </summary>
|
||||
/// <remarks>Part of Sprint 0411 - Semantic Entrypoint Engine (Task 18).</remarks>
|
||||
public sealed class ComponentThreatVectorSnapshot
|
||||
{
|
||||
[JsonPropertyName("vectorType")]
|
||||
public string VectorType { get; set; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("confidence")]
|
||||
public double Confidence { get; set; }
|
||||
|
||||
[JsonPropertyName("evidence")]
|
||||
public string? Evidence { get; set; }
|
||||
|
||||
[JsonPropertyName("entryPath")]
|
||||
public string? EntryPath { get; set; }
|
||||
}
|
||||
|
||||
public sealed class LanguageComponentEvidenceSnapshot
|
||||
{
|
||||
[JsonPropertyName("kind")]
|
||||
|
||||
@@ -0,0 +1,261 @@
|
||||
using System.Collections.Immutable;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace StellaOps.Scanner.Analyzers.Lang;
|
||||
|
||||
/// <summary>
|
||||
/// Semantic metadata field names for LanguageComponentRecord.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Part of Sprint 0411 - Semantic Entrypoint Engine (Task 18).
|
||||
/// </remarks>
|
||||
public static class SemanticMetadataFields
|
||||
{
|
||||
/// <summary>Application intent (WebServer, Worker, CliTool, etc.).</summary>
|
||||
public const string Intent = "semantic:intent";
|
||||
|
||||
/// <summary>Comma-separated capability flags.</summary>
|
||||
public const string Capabilities = "semantic:capabilities";
|
||||
|
||||
/// <summary>JSON array of threat vectors.</summary>
|
||||
public const string ThreatVectors = "semantic:threatVectors";
|
||||
|
||||
/// <summary>Confidence score (0.0-1.0).</summary>
|
||||
public const string Confidence = "semantic:confidence";
|
||||
|
||||
/// <summary>Confidence tier (Unknown, Low, Medium, High, Definitive).</summary>
|
||||
public const string ConfidenceTier = "semantic:confidenceTier";
|
||||
|
||||
/// <summary>Framework name.</summary>
|
||||
public const string Framework = "semantic:framework";
|
||||
|
||||
/// <summary>Framework version.</summary>
|
||||
public const string FrameworkVersion = "semantic:frameworkVersion";
|
||||
|
||||
/// <summary>Whether this component is security-relevant.</summary>
|
||||
public const string SecurityRelevant = "semantic:securityRelevant";
|
||||
|
||||
/// <summary>Risk score (0.0-1.0).</summary>
|
||||
public const string RiskScore = "semantic:riskScore";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Extension methods for accessing semantic data on LanguageComponentRecord.
|
||||
/// </summary>
|
||||
public static class LanguageComponentSemanticExtensions
|
||||
{
|
||||
private static readonly JsonSerializerOptions JsonOptions = new()
|
||||
{
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
||||
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
|
||||
};
|
||||
|
||||
/// <summary>Gets the inferred application intent.</summary>
|
||||
public static string? GetIntent(this LanguageComponentRecord record)
|
||||
{
|
||||
return record.Metadata.TryGetValue(SemanticMetadataFields.Intent, out var value) ? value : null;
|
||||
}
|
||||
|
||||
/// <summary>Gets the inferred capabilities as a list.</summary>
|
||||
public static IReadOnlyList<string> GetCapabilities(this LanguageComponentRecord record)
|
||||
{
|
||||
if (!record.Metadata.TryGetValue(SemanticMetadataFields.Capabilities, out var value) ||
|
||||
string.IsNullOrWhiteSpace(value))
|
||||
{
|
||||
return Array.Empty<string>();
|
||||
}
|
||||
|
||||
return value.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
|
||||
}
|
||||
|
||||
/// <summary>Gets the threat vectors as deserialized objects.</summary>
|
||||
public static IReadOnlyList<ThreatVectorInfo> GetThreatVectors(this LanguageComponentRecord record)
|
||||
{
|
||||
if (!record.Metadata.TryGetValue(SemanticMetadataFields.ThreatVectors, out var value) ||
|
||||
string.IsNullOrWhiteSpace(value))
|
||||
{
|
||||
return Array.Empty<ThreatVectorInfo>();
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
return JsonSerializer.Deserialize<ThreatVectorInfo[]>(value, JsonOptions)
|
||||
?? Array.Empty<ThreatVectorInfo>();
|
||||
}
|
||||
catch
|
||||
{
|
||||
return Array.Empty<ThreatVectorInfo>();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Gets the confidence score.</summary>
|
||||
public static double? GetConfidenceScore(this LanguageComponentRecord record)
|
||||
{
|
||||
if (!record.Metadata.TryGetValue(SemanticMetadataFields.Confidence, out var value) ||
|
||||
string.IsNullOrWhiteSpace(value))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return double.TryParse(value, out var score) ? score : null;
|
||||
}
|
||||
|
||||
/// <summary>Gets the confidence tier.</summary>
|
||||
public static string? GetConfidenceTier(this LanguageComponentRecord record)
|
||||
{
|
||||
return record.Metadata.TryGetValue(SemanticMetadataFields.ConfidenceTier, out var value) ? value : null;
|
||||
}
|
||||
|
||||
/// <summary>Gets the framework name.</summary>
|
||||
public static string? GetFramework(this LanguageComponentRecord record)
|
||||
{
|
||||
return record.Metadata.TryGetValue(SemanticMetadataFields.Framework, out var value) ? value : null;
|
||||
}
|
||||
|
||||
/// <summary>Gets whether this component is security-relevant.</summary>
|
||||
public static bool IsSecurityRelevant(this LanguageComponentRecord record)
|
||||
{
|
||||
if (!record.Metadata.TryGetValue(SemanticMetadataFields.SecurityRelevant, out var value) ||
|
||||
string.IsNullOrWhiteSpace(value))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return bool.TryParse(value, out var result) && result;
|
||||
}
|
||||
|
||||
/// <summary>Gets the risk score.</summary>
|
||||
public static double? GetRiskScore(this LanguageComponentRecord record)
|
||||
{
|
||||
if (!record.Metadata.TryGetValue(SemanticMetadataFields.RiskScore, out var value) ||
|
||||
string.IsNullOrWhiteSpace(value))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return double.TryParse(value, out var score) ? score : null;
|
||||
}
|
||||
|
||||
/// <summary>Checks if semantic data is present.</summary>
|
||||
public static bool HasSemanticData(this LanguageComponentRecord record)
|
||||
{
|
||||
return record.Metadata.ContainsKey(SemanticMetadataFields.Intent) ||
|
||||
record.Metadata.ContainsKey(SemanticMetadataFields.Capabilities);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Builder for adding semantic metadata to LanguageComponentRecord.
|
||||
/// </summary>
|
||||
public sealed class SemanticMetadataBuilder
|
||||
{
|
||||
private static readonly JsonSerializerOptions JsonOptions = new()
|
||||
{
|
||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
||||
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
|
||||
};
|
||||
|
||||
private readonly Dictionary<string, string?> _metadata = new(StringComparer.Ordinal);
|
||||
|
||||
public SemanticMetadataBuilder WithIntent(string intent)
|
||||
{
|
||||
_metadata[SemanticMetadataFields.Intent] = intent;
|
||||
return this;
|
||||
}
|
||||
|
||||
public SemanticMetadataBuilder WithCapabilities(IEnumerable<string> capabilities)
|
||||
{
|
||||
_metadata[SemanticMetadataFields.Capabilities] = string.Join(",", capabilities);
|
||||
return this;
|
||||
}
|
||||
|
||||
public SemanticMetadataBuilder WithCapabilities(long capabilityFlags, Func<long, IEnumerable<string>> flagsToNames)
|
||||
{
|
||||
_metadata[SemanticMetadataFields.Capabilities] = string.Join(",", flagsToNames(capabilityFlags));
|
||||
return this;
|
||||
}
|
||||
|
||||
public SemanticMetadataBuilder WithThreatVectors(IEnumerable<ThreatVectorInfo> threats)
|
||||
{
|
||||
var list = threats.ToList();
|
||||
if (list.Count > 0)
|
||||
{
|
||||
_metadata[SemanticMetadataFields.ThreatVectors] = JsonSerializer.Serialize(list, JsonOptions);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public SemanticMetadataBuilder WithConfidence(double score, string tier)
|
||||
{
|
||||
_metadata[SemanticMetadataFields.Confidence] = score.ToString("F3");
|
||||
_metadata[SemanticMetadataFields.ConfidenceTier] = tier;
|
||||
return this;
|
||||
}
|
||||
|
||||
public SemanticMetadataBuilder WithFramework(string framework, string? version = null)
|
||||
{
|
||||
_metadata[SemanticMetadataFields.Framework] = framework;
|
||||
if (version is not null)
|
||||
{
|
||||
_metadata[SemanticMetadataFields.FrameworkVersion] = version;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public SemanticMetadataBuilder WithSecurityRelevant(bool relevant)
|
||||
{
|
||||
_metadata[SemanticMetadataFields.SecurityRelevant] = relevant.ToString().ToLowerInvariant();
|
||||
return this;
|
||||
}
|
||||
|
||||
public SemanticMetadataBuilder WithRiskScore(double score)
|
||||
{
|
||||
_metadata[SemanticMetadataFields.RiskScore] = score.ToString("F3");
|
||||
return this;
|
||||
}
|
||||
|
||||
public IEnumerable<KeyValuePair<string, string?>> Build()
|
||||
{
|
||||
return _metadata;
|
||||
}
|
||||
|
||||
/// <summary>Merges semantic metadata with existing component metadata.</summary>
|
||||
public IEnumerable<KeyValuePair<string, string?>> MergeWith(IEnumerable<KeyValuePair<string, string?>> existing)
|
||||
{
|
||||
var merged = new Dictionary<string, string?>(StringComparer.Ordinal);
|
||||
|
||||
foreach (var pair in existing)
|
||||
{
|
||||
merged[pair.Key] = pair.Value;
|
||||
}
|
||||
|
||||
foreach (var pair in _metadata)
|
||||
{
|
||||
merged[pair.Key] = pair.Value;
|
||||
}
|
||||
|
||||
return merged;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Serializable threat vector information.
|
||||
/// </summary>
|
||||
public sealed class ThreatVectorInfo
|
||||
{
|
||||
[JsonPropertyName("type")]
|
||||
public string Type { get; set; } = string.Empty;
|
||||
|
||||
[JsonPropertyName("confidence")]
|
||||
public double Confidence { get; set; }
|
||||
|
||||
[JsonPropertyName("cweid")]
|
||||
public int? CweId { get; set; }
|
||||
|
||||
[JsonPropertyName("owasp")]
|
||||
public string? OwaspCategory { get; set; }
|
||||
|
||||
[JsonPropertyName("evidence")]
|
||||
public IReadOnlyList<string>? Evidence { get; set; }
|
||||
}
|
||||
Reference in New Issue
Block a user