using System; using System.CommandLine; using System.CommandLine.Invocation; using System.IO; using System.Net; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using StellaOps.Auth.Client; using StellaOps.Cli.Commands; using StellaOps.Cli.Commands.Scan; using StellaOps.Cli.Configuration; using StellaOps.Cli.Services; using StellaOps.Cli.Telemetry; using StellaOps.AirGap.Policy; using StellaOps.Configuration; using StellaOps.Attestor.StandardPredicates.BinaryDiff; using StellaOps.Policy.Scoring.Engine; using StellaOps.ExportCenter.Client; using StellaOps.ExportCenter.Core.EvidenceCache; using StellaOps.Verdict; using StellaOps.Excititor.Core.Evidence; using StellaOps.Scanner.Storage.Oci; using StellaOps.Scanner.PatchVerification.DependencyInjection; using StellaOps.Scanner.Analyzers.Native; using StellaOps.Doctor.DependencyInjection; using StellaOps.Doctor.Plugins.Core.DependencyInjection; using StellaOps.Doctor.Plugins.Database.DependencyInjection; #if DEBUG || STELLAOPS_ENABLE_SIMULATOR using StellaOps.Cryptography.Plugin.SimRemote.DependencyInjection; #endif namespace StellaOps.Cli; internal static class Program { internal static async Task Main(string[] args) { var (options, configuration) = CliBootstrapper.Build(args); var services = new ServiceCollection(); services.AddSingleton(configuration); services.AddSingleton(options); services.AddOptions(); var verbosityState = new VerbosityState(); services.AddSingleton(verbosityState); services.AddAirGapEgressPolicy(configuration); services.AddStellaOpsCrypto(options.Crypto); // Conditionally register regional crypto plugins based on distribution build #if STELLAOPS_ENABLE_GOST services.AddGostCryptoProviders(configuration); #endif #if STELLAOPS_ENABLE_EIDAS services.AddEidasCryptoProviders(configuration); #endif #if STELLAOPS_ENABLE_SM services.AddSmSoftCryptoProvider(configuration); services.AddSmRemoteCryptoProvider(configuration); #endif #if DEBUG || STELLAOPS_ENABLE_SIMULATOR services.AddSimRemoteCryptoProvider(configuration); #endif // CLI-AIRGAP-56-002: Add sealed mode telemetry for air-gapped operation services.AddSealedModeTelemetryIfOffline( options.IsOffline, options.IsOffline ? Path.Combine(options.Offline.KitsDirectory, "telemetry") : null); services.AddLogging(builder => { builder.ClearProviders(); builder.AddSimpleConsole(logOptions => { logOptions.TimestampFormat = "HH:mm:ss "; logOptions.SingleLine = true; }); builder.AddFilter((category, level) => level >= verbosityState.MinimumLevel); }); if (!string.IsNullOrWhiteSpace(options.Authority.Url)) { services.AddStellaOpsAuthClient(clientOptions => { clientOptions.Authority = options.Authority.Url; clientOptions.ClientId = options.Authority.ClientId ?? string.Empty; clientOptions.ClientSecret = options.Authority.ClientSecret; clientOptions.DefaultScopes.Clear(); clientOptions.DefaultScopes.Add(string.IsNullOrWhiteSpace(options.Authority.Scope) ? StellaOps.Auth.Abstractions.StellaOpsScopes.ConcelierJobsTrigger : options.Authority.Scope); var resilience = options.Authority.Resilience ?? new StellaOpsCliAuthorityResilienceOptions(); clientOptions.EnableRetries = resilience.EnableRetries ?? true; if (resilience.RetryDelays is { Count: > 0 }) { clientOptions.RetryDelays.Clear(); foreach (var delay in resilience.RetryDelays) { if (delay > TimeSpan.Zero) { clientOptions.RetryDelays.Add(delay); } } } if (resilience.AllowOfflineCacheFallback.HasValue) { clientOptions.AllowOfflineCacheFallback = resilience.AllowOfflineCacheFallback.Value; } if (resilience.OfflineCacheTolerance.HasValue && resilience.OfflineCacheTolerance.Value >= TimeSpan.Zero) { clientOptions.OfflineCacheTolerance = resilience.OfflineCacheTolerance.Value; } }); var cacheDirectory = options.Authority.TokenCacheDirectory; if (!string.IsNullOrWhiteSpace(cacheDirectory)) { Directory.CreateDirectory(cacheDirectory); services.AddStellaOpsFileTokenCache(cacheDirectory); } services.AddHttpClient(client => { client.Timeout = TimeSpan.FromMinutes(2); if (Uri.TryCreate(options.Authority.Url, UriKind.Absolute, out var authorityUri)) { client.BaseAddress = authorityUri; } }).AddEgressPolicyGuard("stellaops-cli", "authority-revocation"); services.AddHttpClient(client => { client.Timeout = TimeSpan.FromSeconds(30); if (Uri.TryCreate(options.Authority.Url, UriKind.Absolute, out var authorityUri)) { client.BaseAddress = authorityUri; } }).AddEgressPolicyGuard("stellaops-cli", "authority-console"); } services.AddHttpClient(client => { client.Timeout = TimeSpan.FromMinutes(5); if (!string.IsNullOrWhiteSpace(options.BackendUrl) && Uri.TryCreate(options.BackendUrl, UriKind.Absolute, out var backendUri)) { client.BaseAddress = backendUri; } }).AddEgressPolicyGuard("stellaops-cli", "backend-api"); services.AddHttpClient(client => { client.Timeout = TimeSpan.FromMinutes(10); if (!string.IsNullOrWhiteSpace(options.BackendUrl) && Uri.TryCreate(options.BackendUrl, UriKind.Absolute, out var exportCenterUri)) { client.BaseAddress = exportCenterUri; } }).AddEgressPolicyGuard("stellaops-cli", "export-center-api"); services.AddHttpClient(client => { client.Timeout = TimeSpan.FromSeconds(30); if (!string.IsNullOrWhiteSpace(options.ConcelierUrl) && Uri.TryCreate(options.ConcelierUrl, UriKind.Absolute, out var concelierUri)) { client.BaseAddress = concelierUri; } }).AddEgressPolicyGuard("stellaops-cli", "concelier-api"); services.AddHttpClient("stellaops-cli.ingest-download") .ConfigurePrimaryHttpMessageHandler(() => new HttpClientHandler { AutomaticDecompression = DecompressionMethods.All }) .AddEgressPolicyGuard("stellaops-cli", "sources-ingest"); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(TimeProvider.System); services.AddSingleton(); services.AddVexEvidenceLinking(configuration); // Doctor diagnostics engine services.AddDoctorEngine(); services.AddDoctorCorePlugin(); services.AddDoctorDatabasePlugin(); // CLI-FORENSICS-53-001: Forensic snapshot client services.AddHttpClient(client => { client.Timeout = TimeSpan.FromMinutes(5); if (!string.IsNullOrWhiteSpace(options.BackendUrl) && Uri.TryCreate(options.BackendUrl, UriKind.Absolute, out var backendUri)) { client.BaseAddress = backendUri; } }).AddEgressPolicyGuard("stellaops-cli", "forensic-api"); // CLI-FORENSICS-54-001: Forensic verifier (local only, no HTTP) services.AddSingleton(); // CLI-FORENSICS-54-002: Attestation reader (local only, no HTTP) services.AddSingleton(); // CLI-LNM-22-002: VEX observations client services.AddHttpClient(client => { client.Timeout = TimeSpan.FromMinutes(2); if (!string.IsNullOrWhiteSpace(options.BackendUrl) && Uri.TryCreate(options.BackendUrl, UriKind.Absolute, out var backendUri)) { client.BaseAddress = backendUri; } }).AddEgressPolicyGuard("stellaops-cli", "vex-api"); // CLI-PROMO-70-001: Promotion assembler (local, may call crane/cosign) services.AddHttpClient(client => { client.Timeout = TimeSpan.FromMinutes(5); }); // CLI-DETER-70-003: Determinism harness (local only, executes docker) services.AddSingleton(); // CLI-OBS-51-001: Observability client for health metrics services.AddHttpClient(client => { client.Timeout = TimeSpan.FromSeconds(30); }).AddEgressPolicyGuard("stellaops-cli", "observability-api"); // CLI-PACKS-42-001: Pack client for Task Pack operations services.AddHttpClient(client => { client.Timeout = TimeSpan.FromMinutes(10); // Pack operations may take longer }).AddEgressPolicyGuard("stellaops-cli", "packs-api"); // CLI-EXC-25-001: Exception client for exception governance operations services.AddHttpClient(client => { client.Timeout = TimeSpan.FromSeconds(60); }).AddEgressPolicyGuard("stellaops-cli", "exceptions-api"); // CLI-ORCH-32-001: Orchestrator client for source/job management services.AddHttpClient(client => { client.Timeout = TimeSpan.FromSeconds(60); }).AddEgressPolicyGuard("stellaops-cli", "orchestrator-api"); // CLI-PARITY-41-001: SBOM client for SBOM explorer services.AddHttpClient(client => { client.Timeout = TimeSpan.FromSeconds(60); }).AddEgressPolicyGuard("stellaops-cli", "sbom-api"); // VRR-021: Rationale client for verdict rationale services.AddHttpClient(client => { client.Timeout = TimeSpan.FromSeconds(30); }).AddEgressPolicyGuard("stellaops-cli", "triage-api"); // CLI-VERIFY-43-001: OCI registry client for verify image services.AddHttpClient(client => { client.Timeout = TimeSpan.FromMinutes(2); client.DefaultRequestHeaders.UserAgent.ParseAdd("StellaOps.Cli/verify-image"); }).AddEgressPolicyGuard("stellaops-cli", "oci-registry"); services.AddOciImageInspector(configuration.GetSection("OciRegistry")); // CLI-DIFF-0001: Binary diff predicates and native analyzer support services.AddBinaryDiffPredicates(); services.AddNativeAnalyzer(configuration); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); // CLI-PARITY-41-002: Notify client for notification management services.AddHttpClient(client => { client.Timeout = TimeSpan.FromSeconds(60); }).AddEgressPolicyGuard("stellaops-cli", "notify-api"); // CLI-SBOM-60-001: Sbomer client for layer/compose operations services.AddHttpClient(client => { client.Timeout = TimeSpan.FromMinutes(5); // Composition may take longer }).AddEgressPolicyGuard("stellaops-cli", "sbomer-api"); // CLI-CVSS-190-010: CVSS receipt client (talks to Policy Gateway /api/cvss) services.AddHttpClient(client => { client.Timeout = TimeSpan.FromSeconds(60); }).AddEgressPolicyGuard("stellaops-cli", "cvss-api"); services.AddSingleton(); // RPL-003: VerdictBuilder for replay infrastructure (SPRINT_20260105_002_001_REPLAY) services.AddVerdictBuilderAirGap(); // RPL-016/017: Timeline and bundle store adapters for stella prove command services.AddHttpClient(client => { client.Timeout = TimeSpan.FromSeconds(30); if (!string.IsNullOrWhiteSpace(options.BackendUrl) && Uri.TryCreate(options.BackendUrl, UriKind.Absolute, out var backendUri)) { client.BaseAddress = backendUri; } }).AddEgressPolicyGuard("stellaops-cli", "timeline-api"); services.AddHttpClient(client => { client.Timeout = TimeSpan.FromMinutes(5); // Bundle downloads may take longer if (!string.IsNullOrWhiteSpace(options.BackendUrl) && Uri.TryCreate(options.BackendUrl, UriKind.Absolute, out var backendUri)) { client.BaseAddress = backendUri; } }).AddEgressPolicyGuard("stellaops-cli", "replay-bundle-api"); // CLI-AIRGAP-56-001: Mirror bundle import service for air-gap operations services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); // CLI-CRYPTO-4100-001: Crypto profile validator services.AddSingleton(); // CLI-PATCHVERIFY-001-004: Patch verification services (SPRINT_20260111_001_004) services.AddPatchVerification(); await using var serviceProvider = services.BuildServiceProvider(); var loggerFactory = serviceProvider.GetRequiredService(); var startupLogger = loggerFactory.CreateLogger("StellaOps.Cli.Startup"); AuthorityDiagnosticsReporter.Emit(configuration, startupLogger); // CLI-CRYPTO-4100-001: Validate crypto configuration on startup var cryptoValidator = serviceProvider.GetRequiredService(); var cryptoValidation = cryptoValidator.Validate(serviceProvider); if (cryptoValidation.HasWarnings) { foreach (var warning in cryptoValidation.Warnings) { startupLogger.LogWarning("Crypto: {Warning}", warning); } } if (cryptoValidation.HasErrors) { foreach (var error in cryptoValidation.Errors) { startupLogger.LogError("Crypto: {Error}", error); } } using var cts = new CancellationTokenSource(); Console.CancelKeyPress += (_, eventArgs) => { eventArgs.Cancel = true; cts.Cancel(); }; var rootCommand = CommandFactory.Create(serviceProvider, options, cts.Token, loggerFactory); int commandExit; try { var parseResult = rootCommand.Parse(args); commandExit = await parseResult.InvokeAsync().ConfigureAwait(false); } catch (AirGapEgressBlockedException ex) { var guardLogger = loggerFactory.CreateLogger("StellaOps.Cli.AirGap"); guardLogger.LogError("{ErrorCode}: {Reason} Remediation: {Remediation}", AirGapEgressBlockedException.ErrorCode, ex.Reason, ex.Remediation); if (!string.IsNullOrWhiteSpace(ex.DocumentationUrl)) { guardLogger.LogInformation("Documentation: {DocumentationUrl}", ex.DocumentationUrl); } if (!string.IsNullOrWhiteSpace(ex.SupportContact)) { guardLogger.LogInformation("Support contact: {SupportContact}", ex.SupportContact); } Console.Error.WriteLine(ex.Message); return 1; } var finalExit = Environment.ExitCode != 0 ? Environment.ExitCode : commandExit; if (cts.IsCancellationRequested && finalExit == 0) { finalExit = 130; // Typical POSIX cancellation exit code } return finalExit; } }