Here’s a crisp idea I think you’ll like: **attested, offline‑verifiable call graphs** for binaries. ![abstract graph with signed edges concept](https://images.unsplash.com/photo-1558494949-ef010cbdcc31?q=80\&w=1200\&auto=format\&fit=crop) ### The gist * **Goal:** Make binary reachability (who calls whom) something an auditor can replay **deterministically**, even air‑gapped. * **How:** 1. Build the call graph for ELF/PE/Mach‑O. 2. **Seal each edge (caller → callee) as its own artifact** and sign it in a **DSSE** (in‑toto envelope). 3. Bundle a **reachability graph manifest** listing all edge‑artifacts + hashes of the inputs (binary, debug info, decompiler version, lattice/policy config). 4. Upload edge‑attestations to a **transparency log** (e.g., Rekor v2). 5. Anyone can later fetch/verifiy the envelopes and **replay the analysis identically** (same inputs ⇒ same graph). ### Why this matters * **Deterministic audits:** “Prove this edge existed at analysis time.” No hand‑wavy “our tool said so last week.” * **Granular trust:** You can quarantine or dispute **just one edge** without invalidating the whole graph. * **Supply‑chain fit:** Edge‑artifacts compose nicely with SBOM/VEX; you can say “CVE‑123 is reachable via these signed edges.” ### Minimal vocabulary * **DSSE:** A standard envelope that signs the *statement* (here: an edge) and its *subject* (binary, build‑ID, PURLs). * **Rekor (v2):** An append‑only public log for attestations. Inclusion proofs = tamper‑evidence. * **Reachability graph:** Nodes are functions/symbols; edges are possible calls; roots are entrypoints (exports, handlers, ctors, etc.). ### What “best‑in‑class” looks like in Stella Ops * **Edge schema (per envelope):** * `subject`: binary digest + **build‑id**, container image digest (if relevant) * `caller`: {binary‑offset | symbol | demangled | PURL, version} * `callee`: same structure * `reason`: static pattern (PLT/JMP, thunk), **init_array/ctors**, EH frames, import table, or **dynamic witness** (trace sample ID) * `provenance`: tool name + version, pipeline run ID, OS, container digest * `policy-hash`: hash of lattice/policy/rules used * `evidence`: (optional) byte slice, CFG snippet hash, or trace excerpt hash * **Graph manifest (DSSE too):** * list of edge envelope digests, **roots set**, toolchain hashes, input feeds, **PURL map** (component/function ↔ PURL). * **Verification flow:** * Verify envelopes → verify Rekor inclusion → recompute edges from inputs (or check cached proofs) → compare manifest hash. * **Roots you must include:** exports, syscalls, signal handlers, **.init_array / .ctors**, TLS callbacks, exception trampolines, plugin entrypoints, registered callbacks. ### Quick implementation plan (C#/.NET 10, fits your stack) 1. **Parsers**: ELF/PE/Mach‑O loaders (SymbolTable, DynSym, Reloc/Relr, Import/Export, Sections, Build‑ID), plus DWARF/PDB stubs when present. 2. **Normalizer**: stable symbol IDs (image base + RVA) and **PURL resolver** (package → function namespace). 3. **Edge extractors** (pluggable): * Static: import thunks, PLT/JMP, reloc‑targets, vtable patterns, .init_array, EH tables, jump tables. * Dynamic (optional): eBPF/ETW/Perf trace ingester → produce **witness edges**. 4. **Edge attestation**: one DSSE per edge + signer (FIPS/SM/GOST/EIDAS as needed). 5. **Manifest builder**: emit graph manifest + policy/lattice hash; store in your **Ledger**. 6. **Transparency client**: Rekor v2 submit/query; cache inclusion proofs for offline bundles. 7. **Verifier**: deterministic replay runner; diff engine (edge‑set, roots, policy changes). 8. **UI**: “Edge provenance” panel; click an edge → see DSSE, Rekor proof, extraction reason. ### Practical guardrails * **Idempotence:** Edge IDs = `hash(callerID, calleeID, reason, tool-version)`. Re‑runs don’t duplicate. * **Explainability:** Every edge must say *why it exists* (pattern or witness). * **Stripped binaries:** fall back to pattern heuristics + patch oracles; mark edges **probabilistic** with separate attestation type. * **Hybrid truth:** Keep static and dynamic edges distinct; policies can require both for “reachable”. ### How this helps your day‑to‑day * **Compliance**: Ship an SBOM/VEX plus a **proof pack**; auditors can verify offline. * **Triage**: For a CVE, show **the exact signed path** from entrypoint → vulnerable function; suppresses noisy “maybe‑reachable” claims. * **Vendor claims**: Accept third‑party edges only if they come with DSSE + Rekor inclusion. If you want, I can draft the **DSSE edge schema (JSON)**, the **manifest format**, and the **.NET 10 interfaces** (`IEdgeExtractor`, `IAttestor`, `IReplayer`, `ITransparencyClient`) so your mid‑level dev can start coding today. Here’s a concrete, “give this to a mid‑level .NET dev” implementation plan for the attested, offline‑verifiable call graph. I’ll assume: * Recent .NET (your “.NET 10”) * C# * You can add NuGet packages * You already have (or will have) an “Authority Signer” for DSSE signatures (file key, KMS, etc.) --- ## 0. Solution layout (what projects to create) Create a new solution, e.g. `StellaOps.CallGraph.sln` with: 1. **`StellaOps.CallGraph.Core`** (Class Library) * Domain models (functions, edges, manifests) * Interfaces (`IBinaryParser`, `IEdgeExtractor`, `IAttestor`, `IRekorClient`, etc.) * DSSE envelope and helpers 2. **`StellaOps.CallGraph.BinaryParsers`** (Class Library) * Implementations of `IBinaryParser` for: * **PE/.NET assemblies** using `System.Reflection.Metadata` / `PEReader`([NuGet][1]) * Optionally native PE / ELF using `Microsoft.Binary.Parsers`([NuGet][2]) or `ELFSharp`([NuGet][3]) 3. **`StellaOps.CallGraph.EdgeExtraction`** (Class Library) * Call‑graph builder / edge extractors (import table, IL call instructions, .ctors, etc.) 4. **`StellaOps.CallGraph.Attestation`** (Class Library) * DSSE helpers * Attestation logic for edges + graph manifest * Transparency log (Rekor) client 5. **`StellaOps.CallGraph.Cli`** (Console app) * Developer entrypoint: `callgraph analyze ` * Outputs: * Edge DSSE envelopes (one per edge, or batched) * Graph manifest DSSE * Human‑readable summary 6. **`StellaOps.CallGraph.Tests`** (xUnit / NUnit) * Unit tests per layer --- ## 1. Define the core domain (Core project) ### 1.1 Records and enums Create these in `StellaOps.CallGraph.Core`: ```csharp public sealed record BinaryIdentity( string LogicalId, // e.g. build-id or image digest string Path, // local path used during analysis string? BuildId, string? ImageDigest, // e.g. OCI digest IReadOnlyDictionary Digests // sha256, sha512, etc. ); public sealed record FunctionRef( string BinaryLogicalId, // link to BinaryIdentity.LogicalId ulong Rva, // Relative virtual address (for native) or metadata token for managed string? SymbolName, // raw symbol if available string? DisplayName, // demangled, user-facing string? Purl // optional: pkg/function mapping ); public enum EdgeReasonKind { ImportTable, StaticCall, // direct call instruction VirtualDispatch, // via vtable / callvirt InitArrayOrCtor, ExceptionHandler, DynamicWitness // from traces } public sealed record EdgeReason( EdgeReasonKind Kind, string Detail // e.g. ".text: call 0x401234", "import: kernel32!CreateFileW" ); public sealed record ReachabilityEdge( FunctionRef Caller, FunctionRef Callee, EdgeReason Reason, string ToolVersion, string PolicyHash, // hash of lattice/policy string EvidenceHash // hash of raw evidence blob (CFG snippet, trace, etc.) ); ``` Graph manifest: ```csharp public sealed record CallGraphManifest( string SchemaVersion, BinaryIdentity Binary, IReadOnlyList Roots, IReadOnlyList EdgeEnvelopeDigests, // sha256 of DSSE envelopes string PolicyHash, IReadOnlyDictionary ToolMetadata ); ``` ### 1.2 Core interfaces ```csharp public interface IBinaryParser { BinaryIdentity Identify(string path); IReadOnlyList GetFunctions(BinaryIdentity binary); IReadOnlyList GetRoots(BinaryIdentity binary); // exports, entrypoint, handlers, etc. BinaryCodeRegion GetCodeRegion(BinaryIdentity binary); // raw bytes + mappings, see below } public sealed record BinaryCodeRegion( byte[] Bytes, ulong ImageBase, IReadOnlyList Sections ); public sealed record SectionInfo( string Name, ulong Rva, uint Size ); public interface IEdgeExtractor { IReadOnlyList Extract( BinaryIdentity binary, IReadOnlyList functions, BinaryCodeRegion code); } public interface IAttestor { Task SignEdgeAsync( ReachabilityEdge edge, BinaryIdentity binary, CancellationToken ct = default); Task SignManifestAsync( CallGraphManifest manifest, CancellationToken ct = default); } public interface IRekorClient { Task UploadAsync(DsseEnvelope envelope, CancellationToken ct = default); } public sealed record RekorEntryRef(string LogId, long Index, string Uuid); ``` (We’ll define `DsseEnvelope` in section 3.) --- ## 2. Implement minimal PE parser (BinaryParsers project) Start with **PE/.NET** only; expand later. ### 2.1 Add NuGet packages * `System.Reflection.Metadata` (if you’re not already on a shared framework that has it)([NuGet][1]) * Optionally `Microsoft.Binary.Parsers` for native PE & ELF; it already knows how to parse PE headers and ELF.([NuGet][2]) ### 2.2 Implement `PeBinaryParser` (managed assemblies) In `StellaOps.CallGraph.BinaryParsers`: * `BinaryIdentity Identify(string path)` * Open file, compute SHA‑256 (streaming). * Use `PEReader` and `MetadataReader` to pull: * MVID (`ModuleDefinition`). * Assembly name, version. * Derive `LogicalId`, e.g. `"dotnet:/"`. * `IReadOnlyList GetFunctions(...)` * Use `PEReader` → `GetMetadataReader()` to enumerate methods: * `reader.TypeDefinitions` → methods in each type. * For each `MethodDefinition`, compute: * `BinaryLogicalId = binary.LogicalId` * `Rva = methodDef.RelativeVirtualAddress` * `SymbolName = reader.GetString(methodDef.Name)` * `DisplayName = typeFullName + "::" + methodName + signature` * `Purl` optional mapping (you can fill later from SBOM). * `IReadOnlyList GetRoots(...)` * Roots for .NET: * `Main` methods in entry assembly. * Public exported API if you want (public methods in public types). * Static constructors (.cctor) for public types (init roots). * Keep it simple for v1: treat `Main` as only root. * `BinaryCodeRegion GetCodeRegion(...)` * For managed assemblies, you only need IL for now: * Use `PEReader.GetMethodBody(rva)` to get `MethodBodyBlock`.([Microsoft Learn][4]) * For v1, you can assemble per‑method IL as you go in the extractor instead of pre‑building a whole region. Implementation trick: have `PeBinaryParser` expose a helper: ```csharp public MethodBodyBlock? TryGetMethodBody(BinaryIdentity binary, uint rva); ``` You’ll pass this down to the edge extractor. ### 2.3 (Optional) native PE/ELF Once managed assemblies work: * Add `Microsoft.Binary.Parsers` for PE + ELF.([NuGet][2]) * Or `ELFSharp` if you prefer.([NuGet][3]) You can then: * Parse import table → edges from “import stub” → imported function. * Parse export table → roots (exports). * Parse `.pdata`, `.xdata` → exception handlers. * Parse `.init_array` (ELF) / TLS callbacks, C runtime init functions. For an “average dev” first iteration, you can **skip native** and get a lot of value from .NET assemblies only. --- ## 3. DSSE attestation primitives (Attestation project) You already use DSSE elsewhere, but here’s a self‑contained minimal version. ### 3.1 Envelope models ```csharp public sealed record DsseSignature( string KeyId, string Sig // base64 signature ); public sealed record DsseEnvelope( string PayloadType, // e.g. "application/vnd.stella.call-edge+json" string Payload, // base64-encoded JSON statement IReadOnlyList Signatures ); ``` Statement for a **single edge**: ```csharp public sealed record EdgeStatement( string _type, // e.g. "https://stella.ops/Statement/CallEdge/v1" object subject, // Binary info + maybe PURLs ReachabilityEdge edge ); ``` You can loosely follow the DSSE / in‑toto style: Google’s Grafeas `Envelope` type also matches DSSE’s `envelope.proto`.([Google Cloud][5]) ### 3.2 Pre‑authentication encoding (PAE) Implement DSSE PAE once: ```csharp public static class Dsse { public static byte[] PreAuthEncode(string payloadType, byte[] payload) { static byte[] Cat(params byte[][] parts) { var total = parts.Sum(p => p.Length); var buf = new byte[total]; var offset = 0; foreach (var part in parts) { Buffer.BlockCopy(part, 0, buf, offset, part.Length); offset += part.Length; } return buf; } static byte[] Utf8(string s) => Encoding.UTF8.GetBytes(s); var header = Utf8("DSSEv1"); var pt = Utf8(payloadType); var lenPt = Utf8(pt.Length.ToString(CultureInfo.InvariantCulture)); var lenPayload = Utf8(payload.Length.ToString(CultureInfo.InvariantCulture)); var space = Utf8(" "); return Cat(header, space, lenPt, space, pt, space, lenPayload, space, payload); } } ``` ### 3.3 Implement `IAttestor` Assume you already have some `IAuthoritySigner` that can sign arbitrary byte arrays (Ed25519, RSA, etc.). ```csharp public sealed class DsseAttestor : IAttestor { private readonly IAuthoritySigner _signer; private readonly JsonSerializerOptions _jsonOptions = new() { DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull }; public DsseAttestor(IAuthoritySigner signer) => _signer = signer; public async Task SignEdgeAsync( ReachabilityEdge edge, BinaryIdentity binary, CancellationToken ct = default) { var stmt = new EdgeStatement( _type: "https://stella.ops/Statement/CallEdge/v1", subject: new { type = "file", name = binary.Path, digest = binary.Digests }, edge: edge ); return await SignStatementAsync( stmt, payloadType: "application/vnd.stella.call-edge+json", ct); } public async Task SignManifestAsync( CallGraphManifest manifest, CancellationToken ct = default) { var stmt = new { _type = "https://stella.ops/Statement/CallGraphManifest/v1", subject = new { type = "file", name = manifest.Binary.Path, digest = manifest.Binary.Digests }, manifest }; return await SignStatementAsync( stmt, payloadType: "application/vnd.stella.call-manifest+json", ct); } private async Task SignStatementAsync( object statement, string payloadType, CancellationToken ct) { var payloadBytes = JsonSerializer.SerializeToUtf8Bytes(statement, _jsonOptions); var pae = Dsse.PreAuthEncode(payloadType, payloadBytes); var signatureBytes = await _signer.SignAsync(pae, ct).ConfigureAwait(false); var keyId = await _signer.GetKeyIdAsync(ct).ConfigureAwait(false); return new DsseEnvelope( PayloadType: payloadType, Payload: Convert.ToBase64String(payloadBytes), Signatures: new[] { new DsseSignature(keyId, Convert.ToBase64String(signatureBytes)) }); } } ``` You can plug in: * `IAuthoritySigner` using `System.Security.Cryptography.Ed25519` on .NET (or BouncyCastle) for signatures.([Stack Overflow][6]) --- ## 4. Edge extraction (EdgeExtraction project) ### 4.1 Choose strategy per binary type For **managed .NET assemblies** the easiest route is to use `Mono.Cecil` to read IL opcodes.([NuGet][7]) Add package: `Mono.Cecil`. ```csharp public sealed class ManagedIlEdgeExtractor : IEdgeExtractor { public IReadOnlyList Extract( BinaryIdentity binary, IReadOnlyList functions, BinaryCodeRegion code) { // For managed we won't use BinaryCodeRegion; we’ll re-open file with Cecil. var result = new List(); var filePath = binary.Path; var module = ModuleDefinition.ReadModule(filePath, new ReaderParameters { ReadSymbols = false }); foreach (var type in module.Types) foreach (var method in type.Methods.Where(m => m.HasBody)) { var callerRef = ToFunctionRef(binary, method); foreach (var instr in method.Body.Instructions) { if (instr.OpCode.FlowControl != FlowControl.Call) continue; if (instr.Operand is not MethodReference calleeMethod) continue; var calleeRef = ToFunctionRef(binary, calleeMethod); var edge = new ReachabilityEdge( Caller: callerRef, Callee: calleeRef, Reason: new EdgeReason( EdgeReasonKind.StaticCall, Detail: $"IL {instr.OpCode} {calleeMethod.FullName}" ), ToolVersion: "stella-callgraph/0.1.0", PolicyHash: "TODO", EvidenceHash: "TODO" // later: hash of snippet ); result.Add(edge); } } return result; } private static FunctionRef ToFunctionRef(BinaryIdentity binary, MethodReference method) { var displayName = $"{method.DeclaringType.FullName}::{method.Name}"; return new FunctionRef( BinaryLogicalId: binary.LogicalId, Rva: (ulong)method.MetadataToken.ToInt32(), SymbolName: method.FullName, DisplayName: displayName, Purl: null ); } } ``` Later, you can add: * Import table edges (`EdgeReasonKind.ImportTable`). * Virtual dispatch edges, heuristics, etc. * Dynamic edges from trace logs (`EdgeReasonKind.DynamicWitness`). ### 4.2 Call‑graph builder Add a thin orchestration service: ```csharp public sealed class CallGraphBuilder { private readonly IBinaryParser _parser; private readonly IReadOnlyList _extractors; public CallGraphBuilder( IBinaryParser parser, IEnumerable extractors) { _parser = parser; _extractors = extractors.ToList(); } public (BinaryIdentity binary, IReadOnlyList functions, IReadOnlyList roots, IReadOnlyList edges) Build(string path) { var binary = _parser.Identify(path); var functions = _parser.GetFunctions(binary); var roots = _parser.GetRoots(binary); // Optionally, pack code region if needed var code = new BinaryCodeRegion(Array.Empty(), 0, Array.Empty()); var edges = _extractors .SelectMany(e => e.Extract(binary, functions, code)) .ToList(); return (binary, functions, roots, edges); } } ``` --- ## 5. Edge→DSSE and manifest→DSSE wiring In `StellaOps.CallGraph.Attestation`, create a coordinator: ```csharp public sealed class CallGraphAttestationService { private readonly CallGraphBuilder _builder; private readonly IAttestor _attestor; private readonly IRekorClient _rekor; public CallGraphAttestationService( CallGraphBuilder builder, IAttestor attestor, IRekorClient rekor) { _builder = builder; _attestor = attestor; _rekor = rekor; } public async Task AnalyzeAndAttestAsync( string path, CancellationToken ct = default) { var (binary, functions, roots, edges) = _builder.Build(path); // 1) Sign each edge var edgeEnvelopes = new List(); foreach (var edge in edges) { var env = await _attestor.SignEdgeAsync(edge, binary, ct); edgeEnvelopes.Add(env); } // 2) Compute digests for manifest var edgeEnvelopeDigests = edgeEnvelopes .Select(e => Crypto.HashSha256(JsonSerializer.SerializeToUtf8Bytes(e))) .ToList(); var manifest = new CallGraphManifest( SchemaVersion: "1.0", Binary: binary, Roots: roots, EdgeEnvelopeDigests: edgeEnvelopeDigests, PolicyHash: edges.FirstOrDefault()?.PolicyHash ?? "", ToolMetadata: new Dictionary { ["builder"] = "stella-callgraph/0.1.0", ["created-at"] = DateTimeOffset.UtcNow.ToString("O") }); var manifestEnvelope = await _attestor.SignManifestAsync(manifest, ct); // 3) Publish DSSE envelopes to Rekor (if configured) var rekorRefs = new List(); foreach (var env in edgeEnvelopes.Append(manifestEnvelope)) { var entry = await _rekor.UploadAsync(env, ct); rekorRefs.Add(entry); } return new CallGraphAttestationResult( Manifest: manifest, ManifestEnvelope: manifestEnvelope, EdgeEnvelopes: edgeEnvelopes, RekorEntries: rekorRefs); } } public sealed record CallGraphAttestationResult( CallGraphManifest Manifest, DsseEnvelope ManifestEnvelope, IReadOnlyList EdgeEnvelopes, IReadOnlyList RekorEntries); ``` --- ## 6. Rekor v2 client (transparency log) Rekor is a REST‑based transparency log (part of Sigstore).([Sigstore][8]) For an average dev, keep it **simple**: 1. Add `HttpClient`‑based `RekorClient`: * `UploadAsync(DsseEnvelope)`: * POST to your Rekor server’s `/api/v1/log/entries` (v1 today; v2 is under active development, but the pattern is similar). * Store returned `logID`, `logIndex`, `uuid` in `RekorEntryRef`. 2. For offline replay you’ll want to store: * The DSSE envelopes. * Rekor entry references (and ideally inclusion proofs, but that can come later). You don’t need to fully implement Merkle tree verification in v1; you can add that when you harden the verifier. --- ## 7. CLI for developers (Cli project) A simple console app gives you fast feedback: ```bash stella-callgraph analyze myapp.dll \ --output-dir artifacts/callgraph ``` Implementation sketch: ```csharp static async Task Main(string[] args) { var input = args[1]; // TODO: proper parser var services = Bootstrap(); // DI container var svc = services.GetRequiredService(); var result = await svc.AnalyzeAndAttestAsync(input); // Write DSSE envelopes & manifest as JSON files var outDir = Path.Combine("artifacts", "callgraph"); Directory.CreateDirectory(outDir); await File.WriteAllTextAsync( Path.Combine(outDir, "manifest.dsse.json"), JsonSerializer.Serialize(result.ManifestEnvelope, new JsonSerializerOptions { WriteIndented = true })); for (var i = 0; i < result.EdgeEnvelopes.Count; i++) { var path = Path.Combine(outDir, $"edge-{i:D6}.dsse.json"); await File.WriteAllTextAsync(path, JsonSerializer.Serialize(result.EdgeEnvelopes[i], new JsonSerializerOptions { WriteIndented = true })); } return 0; } ``` --- ## 8. Verifier (same libraries, different flow) Later (or in parallel), add a **verification** mode: 1. Inputs: * Binary file. * Manifest DSSE file. * Edge DSSE files. * (Optionally) Rekor log inclusion proof bundle. 2. Steps (same dev can implement): * Verify DSSE signatures for manifest and edges (using `IAuthoritySigner.VerifyAsync`). * Check: * Manifest’s binary digest matches the current file. * Manifest’s edge‑envelope digests match hashes of the provided DSSE edge files. * Rebuild call graph using the same tool & policy version and diff against attested edges: * For deterministic replay, their differences should be zero. * Optionally: * Ask Rekor for current log info and verify inclusion proof (advanced). --- ## 9. Order of work for a mid‑level .NET dev If you hand this as a sequence of tasks: 1. **Core models & interfaces** * Add domain records (`BinaryIdentity`, `FunctionRef`, `ReachabilityEdge`, `CallGraphManifest`). * Add `IBinaryParser`, `IEdgeExtractor`, `IAttestor`, `IRekorClient`. 2. **Managed PE parser** * Implement `PeBinaryParser` using `System.Reflection.Metadata` (`PEReader`, `MetadataReader`).([NuGet][1]) * Return `BinaryIdentity`, a list of methods as `FunctionRef`, and roots (`Main`). 3. **IL edge extractor** * Add `Mono.Cecil`. * Implement `ManagedIlEdgeExtractor` that: * Iterates methods and IL instructions. * Emits edges for `call` and `callvirt`. 4. **CallGraphBuilder** * Wire `.Build(path)` to use `PeBinaryParser` + `ManagedIlEdgeExtractor`. 5. **DSSE library** * Add `DsseEnvelope`, `DsseSignature`, `Dsse.PreAuthEncode`. * Implement `DsseAttestor` that wraps `ReachabilityEdge` and `CallGraphManifest` into DSSE envelopes using an `IAuthoritySigner`. 6. **Rekor client (stub, then real)** * First: `DummyRekorClient` that just returns fake IDs. * Then: `HttpRekorClient` that POSTs to your Rekor server. 7. **CallGraphAttestationService + CLI** * Implement `CallGraphAttestationService`. * CLI command to: * Run analysis. * Write DSSE files + a human readable summary. 8. **Verifier** * Implement basic “offline verify” command: * Verify DSSE signatures on manifest + edges. * Verify manifest ↔ edge digest linkage. * (Later) compare re‑analyzed graph with attested one. --- If you want, I can next: * Propose the **exact JSON schema** for `EdgeStatement` and `CallGraphManifest` (with sample instances). * Or help turn this into a **Jira/Linear ticket breakdown** ready for your team. [1]: https://www.nuget.org/packages/system.reflection.metadata/?utm_source=chatgpt.com "System.Reflection.Metadata 10.0.0" [2]: https://www.nuget.org/packages/Microsoft.Binary.Parsers?utm_source=chatgpt.com "Microsoft.Binary.Parsers 4.4.8" [3]: https://www.nuget.org/packages/ELFSharp?utm_source=chatgpt.com "ELFSharp 2.17.3" [4]: https://learn.microsoft.com/en-us/dotnet/api/system.reflection.portableexecutable.pereader?view=net-10.0&utm_source=chatgpt.com "PEReader Class (System.Reflection.PortableExecutable)" [5]: https://cloud.google.com/dotnet/docs/reference/Grafeas.V1/latest/Grafeas.V1.Envelope?utm_source=chatgpt.com "Grafeas v1 API - Class Envelope (3.10.0) | .NET client library" [6]: https://stackoverflow.com/questions/72152837/get-public-and-private-key-from-pem-ed25519-in-c-sharp?utm_source=chatgpt.com "Get public and private key from PEM ed25519 in C#" [7]: https://www.nuget.org/packages/mono.cecil/?utm_source=chatgpt.com "Mono.Cecil 0.11.6" [8]: https://docs.sigstore.dev/logging/overview/?utm_source=chatgpt.com "Rekor"