File: Telemetry\InternalMicrosoftDetector.cs
Web Access
Project: src\src\Aspire.Cli\Aspire.Cli.csproj (aspire)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Net;
using System.Net.Http.Headers;
using System.Runtime.Versioning;
using System.Text;
using System.Text.Json;
using System.Text.Json.Nodes;
using System.Text.RegularExpressions;
using Aspire.Cli.DotNet;
using Aspire.Cli.Utils;
using Microsoft.Extensions.Logging;
 
namespace Aspire.Cli.Telemetry;
 
/// <summary>
/// Detects whether the current user or machine appears to be Microsoft internal.
/// </summary>
internal interface IInternalMicrosoftDetector
{
    /// <summary>
    /// Gets whether the current user or machine appears to be Microsoft internal.
    /// </summary>
    Task<InternalMicrosoftDetectionResult> IsInternalMicrosoftMachineAsync(CancellationToken cancellationToken = default);
}
 
/// <summary>
/// Caches and runs staged Microsoft-internal probes.
/// </summary>
internal sealed partial class InternalMicrosoftDetector : IInternalMicrosoftDetector
{
    private const string MicrosoftGitHubOrg = "microsoft";
    private const string MicrosoftTenantId = "72f988bf-86f1-41af-91ab-2d7cd011db47";
    private const string CorpMicrosoftDomainSuffix = ".corp.microsoft.com";
    private const string CacheSubdirectoryName = "internal-microsoft";
    private const string CacheFileName = "detector.json";
    private const int MaxGitHubTokenCandidates = 5;
 
    private static readonly TimeSpan s_cacheRefreshInterval = TimeSpan.FromHours(6);
    private static readonly TimeSpan s_processProbeTimeout = TimeSpan.FromSeconds(3);
    private static readonly TimeSpan s_cancelledProbeDrainTimeout = TimeSpan.FromSeconds(1);
    private static readonly TimeSpan s_gitHubHttpTimeout = TimeSpan.FromSeconds(3);
    private static readonly TimeSpan s_gitHubCandidateTimeout = TimeSpan.FromSeconds(5);
 
    private readonly string _cacheFilePath;
    private readonly CliExecutionContext _executionContext;
    private readonly IEnvironment _environment;
    private readonly IProcessExecutionFactory _processExecutionFactory;
    private readonly HttpMessageHandler? _gitHubHttpMessageHandler;
    private readonly TimeSpan _gitHubCandidateTimeout;
    private readonly TimeSpan _gitHubHttpTimeout;
    private readonly TimeProvider _timeProvider;
    private readonly ILogger<InternalMicrosoftDetector> _logger;
    private readonly IReadOnlyList<IReadOnlyList<InternalMicrosoftProbe>> _probeStages;
 
    public InternalMicrosoftDetector(CliExecutionContext executionContext, IEnvironment environment, TimeProvider timeProvider, ILogger<InternalMicrosoftDetector> logger, IProcessExecutionFactory processExecutionFactory)
        : this(
            executionContext,
            environment,
            Path.Combine(executionContext.CacheDirectory.FullName, CacheSubdirectoryName, CacheFileName),
            timeProvider,
            logger,
            processExecutionFactory,
            probeStages: null)
    {
    }
 
    internal InternalMicrosoftDetector(
        CliExecutionContext executionContext,
        IEnvironment environment,
        string cacheFilePath,
        TimeProvider timeProvider,
        ILogger<InternalMicrosoftDetector> logger,
        IProcessExecutionFactory processExecutionFactory,
        IReadOnlyList<IReadOnlyList<InternalMicrosoftProbe>>? probeStages,
        HttpMessageHandler? gitHubHttpMessageHandler = null,
        TimeSpan? gitHubCandidateTimeout = null,
        TimeSpan? gitHubHttpTimeout = null)
    {
        _cacheFilePath = cacheFilePath;
        _executionContext = executionContext;
        _environment = environment;
        _processExecutionFactory = processExecutionFactory;
        _gitHubHttpMessageHandler = gitHubHttpMessageHandler;
        _gitHubCandidateTimeout = gitHubCandidateTimeout ?? s_gitHubCandidateTimeout;
        _gitHubHttpTimeout = gitHubHttpTimeout ?? s_gitHubHttpTimeout;
        _timeProvider = timeProvider;
        _logger = logger;
        _probeStages = probeStages ?? CreateDefaultProbeStages();
    }
 
    public async Task<InternalMicrosoftDetectionResult> IsInternalMicrosoftMachineAsync(CancellationToken cancellationToken = default)
    {
        try
        {
            var cached = await TryReadFreshCacheAsync(cancellationToken).ConfigureAwait(false);
            if (cached is not null)
            {
                return new InternalMicrosoftDetectionResult(cached.IsInternalMicrosoft, cached.Source, cached.Alias, cached.Domain);
            }
 
            var result = await RunProbeStagesAsync(cancellationToken).ConfigureAwait(false) ??
                new InternalMicrosoftDetectionResult(IsInternalMicrosoft: false, Source: null, Alias: null, Domain: null);
            await TryWriteCacheAsync(result, cancellationToken).ConfigureAwait(false);
 
            return result;
        }
        catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
        {
            throw;
        }
        catch (Exception ex)
        {
            if (_logger.IsEnabled(LogLevel.Debug))
            {
                _logger.LogDebug(ex, "Internal Microsoft detection failed.");
            }
 
            return new InternalMicrosoftDetectionResult(IsInternalMicrosoft: false, Source: null, Alias: null, Domain: null);
        }
    }
 
    private IReadOnlyList<IReadOnlyList<InternalMicrosoftProbe>> CreateDefaultProbeStages()
    {
        // Probes are ordered by cost and signal quality. Local account stores and OS enrollment
        // state come from standard developer-machine tooling: Windows dsregcmd, Visual Studio
        // IdentityService, VS Code global state, macOS Platform SSO, gh/Copilot CLI auth, and
        // GitHub's organization membership API.
        // See:
        // - https://learn.microsoft.com/entra/identity/devices/troubleshoot-device-dsregcmd
        // - https://learn.microsoft.com/entra/identity/devices/macos-platform-single-sign-on
        // - https://docs.github.com/rest/orgs/members
 
        // Fastest/strongest signal probes
        var stage1 = new List<InternalMicrosoftProbe>();
        if (_environment.IsMacOS())
        {
            // Use the platform SSO service on MacOS as the strongest signal (indicates machine is enrolled in
            // Microsoft Intune and user has a Microsoft account in the Microsoft tenant configured in their keychain)
            stage1.Add(new("Mac Platform SSO", CheckMacPlatformSsoAsync));
        }
 
        // Probes that may require file I/O or process execution, but can still complete relatively quickly
        var stage2 = new List<InternalMicrosoftProbe>
        {
            // Is the user signed into VS Code with a Microsoft account that belongs to the Microsoft tenant?
            new("VS Code Microsoft tenant", CheckVsCodeMicrosoftTenantAsync)
        };
 
        // Probes that may involve more extensive process execution/network calls or are a weaker signal, run last
        // to avoid delaying detection when faster/better quality signals are available
        var stage3 = new List<InternalMicrosoftProbe>
        {
            // Is there a GitHub token in the environment that has an active membership in the Microsoft GitHub org?
            new("Environment GitHub token membership", CheckEnvironmentGitHubTokenAsync),
 
            // Is there a GitHub token from the gh CLI that has an active membership in the Microsoft GitHub org?
            new("gh CLI GitHub org membership", CheckGhCliAsync),
 
            // Is there a GitHub token from the Copilot CLI that has an active membership in the Microsoft GitHub org?
            new("Copilot CLI GitHub org membership", CheckCopilotCliAsync)
        };
 
        if (_environment.IsWindows())
        {
            // Stage 1
 
            // Check USERDNSDOMAIN for a corp.microsoft.com domain, which is a strong signal of being on a Microsoft corporate machine or VPN.
            // This is much faster than checking workplace join status and doesn't require admin privileges, so we check it in stage 1.
            stage1.Add(new("Windows USERDNSDOMAIN", CheckWindowsUserDnsDomainAsync));
 
            // Check for a Microsoft tenant in the Visual Studio account store, which is a strong signal of being a Microsoft employee.
            // This is also relatively fast and doesn't require admin privileges, so we check it in stage 1.
            stage1.Add(new("Visual Studio Microsoft tenant", CheckVisualStudioMicrosoftTenantAsync));
 
            // Stage 3
 
            // Check if the machine is workplace joined to the Microsoft tenant, which is a strong signal of being on a Microsoft corporate machine,
            // but can be slower to evaluate so we check it in stage 3.
            stage3.Add(new("Windows workplace join", CheckWindowsWorkplaceJoinAsync));
        }
        else if (IsWsl())
        {
            // Stage 1
 
            // Check USERDNSDOMAIN for a corp.microsoft.com domain on the Windows host, which is a strong signal of being on a Microsoft corporate machine or VPN.
            // This is much faster than checking workplace join status and doesn't require admin privileges, so we check it in stage 1.
            stage1.Add(new("WSL Windows USERDNSDOMAIN", CheckWslWindowsUserDnsDomainAsync));
 
            // Check for a Microsoft tenant in the Visual Studio account store on the Windows host, which is a strong signal of being a Microsoft employee.
            // This is also relatively fast and doesn't require admin privileges, so we check it in stage 1.
            stage1.Add(new("WSL Visual Studio Microsoft tenant", CheckWslVisualStudioMicrosoftTenantAsync));
 
            // Stage 3
 
            // Check if the Windows host machine is workplace joined to the Microsoft tenant, which is a strong signal of being on a Microsoft corporate machine,
            // but can be slower to evaluate so we check it in stage 3.
            stage3.Add(new("WSL Windows workplace join", CheckWslWindowsWorkplaceJoinAsync));
            stage3.Add(new("WSL Windows gh.exe GitHub org membership", CheckWslWindowsGhCliAsync));
        }
 
        return [stage1, stage2, stage3];
    }
 
    private async Task<InternalMicrosoftDetectionResult?> RunProbeStagesAsync(CancellationToken cancellationToken)
    {
        foreach (var stage in _probeStages)
        {
            cancellationToken.ThrowIfCancellationRequested();
 
            if (stage.Count == 0)
            {
                continue;
            }
 
            var result = await RunProbeStageAsync(stage, cancellationToken).ConfigureAwait(false);
            if (result is not null)
            {
                return result;
            }
        }
 
        return null;
    }
 
    private async Task<InternalMicrosoftDetectionResult?> RunProbeStageAsync(IReadOnlyList<InternalMicrosoftProbe> probes, CancellationToken cancellationToken)
    {
        using var stageCancellation = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
        var probeTasks = probes.Select(probe => RunProbeAsync(probe, stageCancellation.Token)).ToList();
        var pendingTasks = probeTasks.ToList();
 
        while (pendingTasks.Count > 0)
        {
            var completedTask = await Task.WhenAny(pendingTasks).ConfigureAwait(false);
            pendingTasks.Remove(completedTask);
 
            var result = await completedTask.ConfigureAwait(false);
            if (result is not null)
            {
                await stageCancellation.CancelAsync().ConfigureAwait(false);
                await DrainCancelledProbesAsync(probeTasks).ConfigureAwait(false);
                return result;
            }
        }
 
        return null;
    }
 
    private Task<InternalMicrosoftDetectionResult?> RunProbeAsync(InternalMicrosoftProbe probe, CancellationToken cancellationToken)
    {
        return Task.Run(async () =>
        {
            try
            {
                cancellationToken.ThrowIfCancellationRequested();
                var result = await probe.DetectAsync(cancellationToken).ConfigureAwait(false);
                return result.IsInternalMicrosoft
                    ? new InternalMicrosoftDetectionResult(IsInternalMicrosoft: true, Source: probe.Name, Alias: result.Alias, Domain: result.Domain)
                    : null;
            }
            catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
            {
                return null;
            }
            catch (Exception ex)
            {
                if (_logger.IsEnabled(LogLevel.Debug))
                {
                    _logger.LogDebug(ex, "Microsoft internal probe '{ProbeName}' failed.", probe.Name);
                }
                return null;
            }
        }, CancellationToken.None);
    }
 
    private async Task DrainCancelledProbesAsync(IReadOnlyList<Task<InternalMicrosoftDetectionResult?>> probeTasks)
    {
        try
        {
            await Task.WhenAll(probeTasks).WaitAsync(s_cancelledProbeDrainTimeout).ConfigureAwait(false);
        }
        catch (TimeoutException ex)
        {
            if (_logger.IsEnabled(LogLevel.Debug))
            {
                _logger.LogDebug(ex, "Timed out waiting for cancelled Microsoft internal probes to drain.");
            }
        }
        catch (Exception ex)
        {
            if (_logger.IsEnabled(LogLevel.Debug))
            {
                _logger.LogDebug(ex, "A cancelled Microsoft internal probe failed while draining.");
            }
        }
    }
 
    private async Task<InternalMicrosoftDetectorCacheEntry?> TryReadFreshCacheAsync(CancellationToken cancellationToken)
    {
        if (!File.Exists(_cacheFilePath))
        {
            return null;
        }
 
        try
        {
            var json = await File.ReadAllTextAsync(_cacheFilePath, cancellationToken).ConfigureAwait(false);
            var entry = JsonSerializer.Deserialize(json, JsonSourceGenerationContext.Default.InternalMicrosoftDetectorCacheEntry);
            if (entry is null)
            {
                return null;
            }
 
            var hasRequiredSource = !entry.IsInternalMicrosoft || !string.IsNullOrEmpty(entry.Source);
            return hasRequiredSource && _timeProvider.GetUtcNow() - entry.LastRunUtc < s_cacheRefreshInterval
                ? entry
                : null;
        }
        catch (Exception ex) when (ex is IOException or UnauthorizedAccessException or JsonException)
        {
            if (_logger.IsEnabled(LogLevel.Debug))
            {
                _logger.LogDebug(ex, "Failed to read Microsoft internal detector cache from {CacheFilePath}.", _cacheFilePath);
            }
            return null;
        }
    }
 
    private async Task TryWriteCacheAsync(InternalMicrosoftDetectionResult result, CancellationToken cancellationToken)
    {
        var directory = Path.GetDirectoryName(_cacheFilePath);
        if (string.IsNullOrEmpty(directory))
        {
            return;
        }
 
        var tempPath = Path.Combine(directory, $"{Path.GetRandomFileName()}.tmp");
        try
        {
            Directory.CreateDirectory(directory);
 
            var entry = new InternalMicrosoftDetectorCacheEntry
            {
                IsInternalMicrosoft = result.IsInternalMicrosoft,
                Source = result.Source,
                Alias = result.Alias,
                Domain = result.Domain,
                LastRunUtc = _timeProvider.GetUtcNow()
            };
            var json = JsonSerializer.Serialize(entry, JsonSourceGenerationContext.Default.InternalMicrosoftDetectorCacheEntry);
            await File.WriteAllTextAsync(tempPath, json, cancellationToken).ConfigureAwait(false);
            File.Move(tempPath, _cacheFilePath, overwrite: true);
        }
        catch (Exception ex) when (ex is IOException or UnauthorizedAccessException)
        {
            if (_logger.IsEnabled(LogLevel.Debug))
            {
                _logger.LogDebug(ex, "Failed to write Microsoft internal detector cache to {CacheFilePath}.", _cacheFilePath);
            }
        }
        finally
        {
            FileDeleteHelper.TryDeleteFile(tempPath);
        }
    }
 
    internal Task<InternalMicrosoftProbeResult> CheckWindowsUserDnsDomainAsync(CancellationToken cancellationToken)
    {
        cancellationToken.ThrowIfCancellationRequested();
        var userDnsDomain = _environment.GetEnvironmentVariable("USERDNSDOMAIN");
        var domain = ExtractAdDomainNameFromCorpDnsName(userDnsDomain);
        return Task.FromResult(domain is not null
            ? Detected(_environment.GetEnvironmentVariable("USERNAME"), domain)
            : InternalMicrosoftProbeResult.NotDetected);
    }
 
    private async Task<InternalMicrosoftProbeResult> CheckWslWindowsUserDnsDomainAsync(CancellationToken cancellationToken)
    {
        if (!CommandExists("cmd.exe"))
        {
            return InternalMicrosoftProbeResult.NotDetected;
        }
 
        var result = await RunProcessAsync("cmd.exe", ["/c", "echo %USERDNSDOMAIN%&echo %USERNAME%"], cancellationToken).ConfigureAwait(false);
        var outputLines = result.Stdout.Split('\n', StringSplitOptions.TrimEntries);
        var userDnsDomain = outputLines.FirstOrDefault() ?? string.Empty;
        var userName = outputLines.Skip(1).FirstOrDefault() ?? string.Empty;
        var domain = ExtractAdDomainNameFromCorpDnsName(userDnsDomain);
        return result.ExitCode == 0 && domain is not null
            ? Detected(userName, domain)
            : InternalMicrosoftProbeResult.NotDetected;
    }
 
    [SupportedOSPlatform("windows")]
    private async Task<InternalMicrosoftProbeResult> CheckVisualStudioMicrosoftTenantAsync(CancellationToken cancellationToken)
    {
        var localAppData = GetSpecialFolderPath(Environment.SpecialFolder.LocalApplicationData, "LOCALAPPDATA");
        if (string.IsNullOrWhiteSpace(localAppData))
        {
            return InternalMicrosoftProbeResult.NotDetected;
        }
 
        var accountStore = Path.Combine(localAppData, ".IdentityService", "V3AccountStore.json");
        if (!File.Exists(accountStore))
        {
            return InternalMicrosoftProbeResult.NotDetected;
        }
 
        var text = await FileSystemHelper.TryReadAllTextAsync(accountStore, cancellationToken).ConfigureAwait(false);
        return DetectMicrosoftTenant(text, cancellationToken);
    }
 
    private async Task<InternalMicrosoftProbeResult> CheckWslVisualStudioMicrosoftTenantAsync(CancellationToken cancellationToken)
    {
        if (!CommandExists("cmd.exe"))
        {
            return InternalMicrosoftProbeResult.NotDetected;
        }
 
        var result = await RunProcessAsync(
            "cmd.exe",
            ["/c", "if exist \"%LOCALAPPDATA%\\.IdentityService\\V3AccountStore.json\" type \"%LOCALAPPDATA%\\.IdentityService\\V3AccountStore.json\""],
            cancellationToken).ConfigureAwait(false);
 
        return result.ExitCode == 0
            ? DetectMicrosoftTenant(result.Stdout, cancellationToken)
            : InternalMicrosoftProbeResult.NotDetected;
    }
 
    [SupportedOSPlatform("macos")]
    private async Task<InternalMicrosoftProbeResult> CheckMacPlatformSsoAsync(CancellationToken cancellationToken)
    {
        if (!CommandExists("app-sso"))
        {
            return InternalMicrosoftProbeResult.NotDetected;
        }
 
        var result = await RunProcessAsync("app-sso", ["platform", "-s"], cancellationToken).ConfigureAwait(false);
        if (result.ExitCode != 0)
        {
            return InternalMicrosoftProbeResult.NotDetected;
        }
 
        // app-sso emits a JSON document similar to:
        //   {"realm":"REDMOND.CORP.MICROSOFT.COM","upn":"alias@REDMOND.CORP.MICROSOFT.COM",
        //    "issuer":"https://login.microsoftonline.com/<tenant>/v2.0", ...}
        // Use JSON APIs for the fixed fields so formatting changes don't affect detection.
        var json = TryParseJsonObject($"{result.Stdout}{Environment.NewLine}{result.Stderr}");
        if (json is null)
        {
            return InternalMicrosoftProbeResult.NotDetected;
        }
 
        var expectedIssuer = $"https://login.microsoftonline.com/{MicrosoftTenantId}/v2.0";
        var expectedKeyEndpoint = $"https://login.microsoftonline.com/{MicrosoftTenantId}/getkeydata";
        var expectedTokenEndpoint = $"https://login.microsoftonline.com/{MicrosoftTenantId}/oauth2/v2.0/token";
        var upn = TryGetString(json, "upn");
        var realmDomain = ExtractAdDomainNameFromCorpDnsName(TryGetString(json, "realm"));
        var upnDomain = ExtractAdDomainNameFromAccountIdentifier(upn);
        var domain = realmDomain ?? upnDomain;
 
        return HasJsonStringProperty(json, "issuer", expectedIssuer) &&
            HasJsonStringProperty(json, "keyEndpointURL", expectedKeyEndpoint) &&
            HasJsonStringProperty(json, "tokenEndpointURL", expectedTokenEndpoint) &&
            domain is not null &&
            ExtractAliasFromAccountIdentifier(upn) is { } alias
                ? Detected(alias, domain)
                : InternalMicrosoftProbeResult.NotDetected;
    }
 
    private async Task<InternalMicrosoftProbeResult> CheckVsCodeMicrosoftTenantAsync(CancellationToken cancellationToken)
    {
        foreach (var stateDatabasePath in GetVsCodeStateDatabasePaths())
        {
            cancellationToken.ThrowIfCancellationRequested();
 
            if (!File.Exists(stateDatabasePath))
            {
                continue;
            }
 
            var bytes = await FileSystemHelper.TryReadAllBytesAsync(stateDatabasePath, cancellationToken).ConfigureAwait(false);
            if (bytes.Length == 0)
            {
                continue;
            }
 
            var text = Encoding.UTF8.GetString(bytes);
            var result = DetectMicrosoftTenant(text, cancellationToken);
            if (result.IsInternalMicrosoft)
            {
                return result;
            }
        }
 
        return InternalMicrosoftProbeResult.NotDetected;
    }
 
    internal async Task<InternalMicrosoftProbeResult> CheckWindowsWorkplaceJoinAsync(CancellationToken cancellationToken)
    {
        if (!CommandExists("dsregcmd"))
        {
            return InternalMicrosoftProbeResult.NotDetected;
        }
 
        var result = await RunProcessAsync("dsregcmd", ["/status"], cancellationToken).ConfigureAwait(false);
        return result.ExitCode == 0
            ? EvaluateWindowsWorkplaceJoin(
                result.Stdout,
                _environment.GetEnvironmentVariable("USERNAME"),
                _environment.GetEnvironmentVariable("USERDNSDOMAIN"))
            : InternalMicrosoftProbeResult.NotDetected;
    }
 
    private async Task<InternalMicrosoftProbeResult> CheckWslWindowsWorkplaceJoinAsync(CancellationToken cancellationToken)
    {
        if (!CommandExists("cmd.exe"))
        {
            return InternalMicrosoftProbeResult.NotDetected;
        }
 
        var result = await RunProcessAsync("cmd.exe", ["/c", "dsregcmd /status"], cancellationToken).ConfigureAwait(false);
        return result.ExitCode == 0
            ? EvaluateWindowsWorkplaceJoin(result.Stdout, fallbackAlias: null, fallbackDomain: null)
            : InternalMicrosoftProbeResult.NotDetected;
    }
 
    private async Task<InternalMicrosoftProbeResult> CheckGhCliAsync(CancellationToken cancellationToken)
    {
        if (!CommandExists("gh"))
        {
            return InternalMicrosoftProbeResult.NotDetected;
        }
 
        var tokenResult = await RunProcessAsync("gh", ["auth", "token", "--hostname", "github.com"], cancellationToken).ConfigureAwait(false);
        if (tokenResult.ExitCode != 0 || string.IsNullOrWhiteSpace(tokenResult.Stdout))
        {
            return InternalMicrosoftProbeResult.NotDetected;
        }
 
        using var http = CreateGitHubHttpClient();
        return await CheckGitHubMembershipWithTokenAsync(http, tokenResult.Stdout.Trim(), cancellationToken).ConfigureAwait(false)
            ? Detected(alias: null)
            : InternalMicrosoftProbeResult.NotDetected;
    }
 
    private async Task<InternalMicrosoftProbeResult> CheckWslWindowsGhCliAsync(CancellationToken cancellationToken)
    {
        if (!CommandExists("gh.exe"))
        {
            return InternalMicrosoftProbeResult.NotDetected;
        }
 
        var tokenResult = await RunProcessAsync("gh.exe", ["auth", "token", "--hostname", "github.com"], cancellationToken).ConfigureAwait(false);
        if (tokenResult.ExitCode != 0 || string.IsNullOrWhiteSpace(tokenResult.Stdout))
        {
            return InternalMicrosoftProbeResult.NotDetected;
        }
 
        using var http = CreateGitHubHttpClient();
        return await CheckGitHubMembershipWithTokenAsync(http, tokenResult.Stdout.Trim(), cancellationToken).ConfigureAwait(false)
            ? Detected(alias: null)
            : InternalMicrosoftProbeResult.NotDetected;
    }
 
    private async Task<InternalMicrosoftProbeResult> CheckEnvironmentGitHubTokenAsync(CancellationToken cancellationToken)
    {
        var tokenCandidates = DeduplicateTokenCandidates(GetGitHubTokenEnvironmentCandidates(cancellationToken));
        if (tokenCandidates.Count == 0)
        {
            return InternalMicrosoftProbeResult.NotDetected;
        }
 
        return await CheckAnyGitHubMembershipCandidateAsync(tokenCandidates, cancellationToken).ConfigureAwait(false)
            ? Detected(alias: null)
            : InternalMicrosoftProbeResult.NotDetected;
    }
 
    internal async Task<InternalMicrosoftProbeResult> CheckCopilotCliAsync(CancellationToken cancellationToken)
    {
        var tokenCandidates = new List<TokenCandidate>();
        foreach (var (name, value) in GetEnvironmentVariables())
        {
            cancellationToken.ThrowIfCancellationRequested();
 
            if (name.StartsWith("COPILOT_GH_ACCOUNT_", StringComparison.OrdinalIgnoreCase) && value is not null && LooksLikeGitHubToken(value))
            {
                tokenCandidates.Add(new TokenCandidate(value));
            }
        }
 
        var copilotHome = Path.Combine(_executionContext.HomeDirectory.FullName, ".copilot");
        foreach (var path in EnumerateExistingFiles(copilotHome, cancellationToken, "config.json", "settings.json"))
        {
            tokenCandidates.AddRange(ExtractGitHubTokenCandidates(path, cancellationToken));
        }
 
        tokenCandidates = DeduplicateTokenCandidates(tokenCandidates);
        if (tokenCandidates.Count == 0)
        {
            return InternalMicrosoftProbeResult.NotDetected;
        }
 
        return await CheckAnyGitHubMembershipCandidateAsync(tokenCandidates, cancellationToken).ConfigureAwait(false)
            ? Detected(alias: null)
            : InternalMicrosoftProbeResult.NotDetected;
    }
 
    private async Task<bool> CheckAnyGitHubMembershipCandidateAsync(IReadOnlyList<TokenCandidate> candidates, CancellationToken cancellationToken)
    {
        var candidatesToCheck = candidates.Take(MaxGitHubTokenCandidates).ToArray();
        if (candidatesToCheck.Length == 0)
        {
            return false;
        }
 
        using var timeoutSource = new CancellationTokenSource(_gitHubCandidateTimeout);
        using var linkedSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, timeoutSource.Token);
        var candidateTasks = candidatesToCheck
            .Select(candidate => CheckGitHubMembershipCandidateAsync(candidate, linkedSource.Token))
            .ToList();
 
        try
        {
            while (candidateTasks.Count > 0)
            {
                var completedTask = await Task.WhenAny(candidateTasks).WaitAsync(linkedSource.Token).ConfigureAwait(false);
                candidateTasks.Remove(completedTask);
 
                if (await completedTask.ConfigureAwait(false))
                {
                    await linkedSource.CancelAsync().ConfigureAwait(false);
                    await DrainGitHubCandidateTasksAsync(candidateTasks).ConfigureAwait(false);
                    return true;
                }
            }
 
            return false;
        }
        catch (OperationCanceledException) when (!cancellationToken.IsCancellationRequested && timeoutSource.IsCancellationRequested)
        {
            return false;
        }
        finally
        {
            await linkedSource.CancelAsync().ConfigureAwait(false);
            await DrainGitHubCandidateTasksAsync(candidateTasks).ConfigureAwait(false);
        }
    }
 
    private async Task<bool> CheckGitHubMembershipCandidateAsync(TokenCandidate candidate, CancellationToken cancellationToken)
    {
        try
        {
            using var http = CreateGitHubHttpClient();
            return await CheckGitHubMembershipWithTokenAsync(http, candidate.Token, cancellationToken).ConfigureAwait(false);
        }
        catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
        {
            throw;
        }
        catch (Exception ex) when (ex is HttpRequestException or JsonException or InvalidOperationException or TaskCanceledException)
        {
            if (_logger.IsEnabled(LogLevel.Debug))
            {
                _logger.LogDebug(ex, "GitHub token membership probe failed.");
            }
 
            return false;
        }
    }
 
    private async Task DrainGitHubCandidateTasksAsync(IReadOnlyList<Task<bool>> candidateTasks)
    {
        if (candidateTasks.Count == 0)
        {
            return;
        }
 
        try
        {
            await Task.WhenAll(candidateTasks).WaitAsync(s_cancelledProbeDrainTimeout).ConfigureAwait(false);
        }
        catch (Exception ex) when (ex is OperationCanceledException or TimeoutException or HttpRequestException or JsonException or InvalidOperationException or TaskCanceledException)
        {
            if (_logger.IsEnabled(LogLevel.Debug))
            {
                _logger.LogDebug(ex, "A cancelled GitHub token membership probe failed while draining.");
            }
        }
    }
 
    internal async Task<bool> CheckGitHubMembershipWithTokenAsync(string token, CancellationToken cancellationToken)
    {
        using var http = CreateGitHubHttpClient();
        return await CheckGitHubMembershipWithTokenAsync(http, token, cancellationToken).ConfigureAwait(false);
    }
 
    private static async Task<bool> CheckGitHubMembershipWithTokenAsync(HttpClient http, string token, CancellationToken cancellationToken)
    {
        using var userRequest = NewGitHubRequest(HttpMethod.Get, "https://api.github.com/user", token);
        using var userResponse = await http.SendAsync(userRequest, cancellationToken).ConfigureAwait(false);
        if (!userResponse.IsSuccessStatusCode)
        {
            return false;
        }
 
        var login = await ReadJsonPropertyAsync(userResponse, "login", cancellationToken).ConfigureAwait(false);
        if (string.IsNullOrWhiteSpace(login))
        {
            return false;
        }
 
        using var membershipRequest = NewGitHubRequest(HttpMethod.Get, $"https://api.github.com/user/memberships/orgs/{MicrosoftGitHubOrg}", token);
        using var membershipResponse = await http.SendAsync(membershipRequest, cancellationToken).ConfigureAwait(false);
        if (membershipResponse.IsSuccessStatusCode)
        {
            using var doc = JsonDocument.Parse(await membershipResponse.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false));
            var state = TryGetString(doc.RootElement, "state");
            return state?.Equals("active", StringComparison.OrdinalIgnoreCase) == true;
        }
 
        using var publicMemberRequest = NewGitHubRequest(HttpMethod.Get, $"https://api.github.com/orgs/{MicrosoftGitHubOrg}/public_members/{login}", token);
        using var publicMemberResponse = await http.SendAsync(publicMemberRequest, cancellationToken).ConfigureAwait(false);
        return publicMemberResponse.StatusCode == HttpStatusCode.NoContent;
    }
 
    private HttpClient CreateGitHubHttpClient()
    {
        var http = _gitHubHttpMessageHandler is null
            ? new HttpClient()
            : new HttpClient(_gitHubHttpMessageHandler, disposeHandler: false);
        http.Timeout = _gitHubHttpTimeout;
 
        http.DefaultRequestHeaders.UserAgent.ParseAdd("aspire-cli-internal-microsoft-detector/1.0");
        return http;
    }
 
    private static HttpRequestMessage NewGitHubRequest(HttpMethod method, string url, string token)
    {
        var request = new HttpRequestMessage(method, url);
        request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token);
        request.Headers.Accept.ParseAdd("application/vnd.github+json");
        request.Headers.Add("X-GitHub-Api-Version", "2022-11-28");
        return request;
    }
 
    private static async Task<string?> ReadJsonPropertyAsync(HttpResponseMessage response, string propertyName, CancellationToken cancellationToken)
    {
        using var doc = JsonDocument.Parse(await response.Content.ReadAsStringAsync(cancellationToken).ConfigureAwait(false));
        return TryGetString(doc.RootElement, propertyName);
    }
 
    private async Task<ProcessResult> RunProcessAsync(string fileName, string[] arguments, CancellationToken cancellationToken)
    {
        var stdout = new StringBuilder();
        var stderr = new StringBuilder();
        var options = new ProcessInvocationOptions
        {
            SuppressLogging = true,
            StandardOutputCallback = line => stdout.AppendLine(line),
            StandardErrorCallback = line => stderr.AppendLine(line)
        };
 
        using var timeoutCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
        timeoutCts.CancelAfter(s_processProbeTimeout);
        await using var execution = _processExecutionFactory.CreateExecution(
            fileName,
            arguments,
            env: null,
            _executionContext.WorkingDirectory,
            options);
 
        try
        {
            if (!await execution.StartAsync(timeoutCts.Token).ConfigureAwait(false))
            {
                return new ProcessResult(ExitCode: -1, stdout.ToString(), stderr.ToString());
            }
 
            var exitCode = await execution.WaitForExitAsync(timeoutCts.Token).ConfigureAwait(false);
            return new ProcessResult(exitCode, stdout.ToString(), stderr.ToString());
        }
        catch (OperationCanceledException) when (!cancellationToken.IsCancellationRequested)
        {
            return new ProcessResult(ExitCode: -1, stdout.ToString(), stderr.ToString());
        }
    }
 
    private static InternalMicrosoftProbeResult EvaluateWindowsWorkplaceJoin(string output, string? fallbackAlias, string? fallbackDomain)
    {
        if (string.IsNullOrWhiteSpace(output))
        {
            return InternalMicrosoftProbeResult.NotDetected;
        }
 
        var values = ParseColonSeparatedFields(output);
        var tenantId = values.GetValueOrDefault("TenantId");
        var azureAdJoined = IsYes(values.GetValueOrDefault("AzureAdJoined"));
        var workplaceJoined = IsYes(values.GetValueOrDefault("WorkplaceJoined"));
        var alias = ExtractAliasFromAccountIdentifier(GetFirstValue(values, "UserEmail", "User Email", "UserPrincipalName", "User Principal Name", "UPN")) ??
            NormalizeAlias(fallbackAlias);
        var domain = ExtractAdDomainNameFromDsReg(values) ?? ExtractAdDomainNameFromCorpDnsName(fallbackDomain);
 
        return (azureAdJoined || workplaceJoined) && tenantId?.Equals(MicrosoftTenantId, StringComparison.OrdinalIgnoreCase) == true
            ? Detected(alias, domain)
            : InternalMicrosoftProbeResult.NotDetected;
 
        static bool IsYes(string? value)
        {
            return value?.Equals("YES", StringComparison.OrdinalIgnoreCase) == true;
        }
    }
 
    private static Dictionary<string, string> ParseColonSeparatedFields(string text)
    {
        // dsregcmd /status writes colon-separated sections, e.g.:
        //   AzureAdJoined : YES
        //   TenantId : 72f988bf-86f1-41af-91ab-2d7cd011db47
        //   User Email : alias@microsoft.com
        // Values can contain additional ':' characters, so split only on the first delimiter.
        var values = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
        foreach (var line in text.Split('\n'))
        {
            var index = line.IndexOf(':', StringComparison.Ordinal);
            if (index <= 0)
            {
                continue;
            }
 
            var key = line[..index].Trim();
            var value = line[(index + 1)..].Trim();
            if (!string.IsNullOrWhiteSpace(key))
            {
                values[key] = value;
            }
        }
 
        return values;
    }
 
    private static InternalMicrosoftProbeResult DetectMicrosoftTenant(string? text, CancellationToken cancellationToken)
    {
        if (string.IsNullOrWhiteSpace(text))
        {
            return InternalMicrosoftProbeResult.NotDetected;
        }
 
        var alias = ExtractMicrosoftAccountAliasFromText(text, cancellationToken);
        if (text.Contains(MicrosoftTenantId, StringComparison.OrdinalIgnoreCase))
        {
            return Detected(alias);
        }
 
        foreach (var evidence in ExtractTenantAliasEvidenceFromJwtPayloads(text, cancellationToken))
        {
            if (evidence.TenantId.Equals(MicrosoftTenantId, StringComparison.OrdinalIgnoreCase))
            {
                return Detected(evidence.Alias);
            }
        }
 
        return InternalMicrosoftProbeResult.NotDetected;
    }
 
    private static IEnumerable<TenantAliasEvidence> ExtractTenantAliasEvidenceFromJwtPayloads(string text, CancellationToken cancellationToken)
    {
        // Account stores often embed JWTs. Decode only the payload segment:
        //   base64url(header).base64url(payload).base64url(signature)
        // and look for tenant/user claims such as tid, tenantId, preferred_username, and upn.
        var evidence = new List<TenantAliasEvidence>();
        foreach (Match match in JwtRegex().Matches(text))
        {
            cancellationToken.ThrowIfCancellationRequested();
 
            var parts = match.Value.Split('.');
            if (parts.Length < 2)
            {
                continue;
            }
 
            var payload = DecodeBase64Url(parts[1]);
            if (payload is null)
            {
                continue;
            }
 
            try
            {
                using var doc = JsonDocument.Parse(payload);
                var tid = TryGetString(doc.RootElement, "tid") ?? TryGetString(doc.RootElement, "tenantId");
                if (!string.IsNullOrWhiteSpace(tid))
                {
                    evidence.Add(new TenantAliasEvidence(tid, ExtractAliasFromTokenPayload(doc.RootElement)));
                }
            }
            catch (JsonException)
            {
                continue;
            }
        }
 
        return evidence;
    }
 
    private static string? ExtractAliasFromTokenPayload(JsonElement payload)
    {
        foreach (var claimName in new[] { "preferred_username", "upn", "email", "unique_name" })
        {
            var alias = ExtractAliasFromAccountIdentifier(TryGetString(payload, claimName));
            if (!string.IsNullOrWhiteSpace(alias))
            {
                return alias;
            }
        }
 
        return null;
    }
 
    private static string? ExtractMicrosoftAccountAliasFromText(string text, CancellationToken cancellationToken)
    {
        foreach (var evidence in ExtractTenantAliasEvidenceFromJwtPayloads(text, cancellationToken))
        {
            if (evidence.TenantId.Equals(MicrosoftTenantId, StringComparison.OrdinalIgnoreCase) &&
                !string.IsNullOrWhiteSpace(evidence.Alias))
            {
                return evidence.Alias;
            }
        }
 
        foreach (Match match in MicrosoftAccountRegex().Matches(text))
        {
            cancellationToken.ThrowIfCancellationRequested();
            var alias = NormalizeAlias(match.Groups["alias"].Value);
            if (!string.IsNullOrWhiteSpace(alias))
            {
                return alias;
            }
        }
 
        return null;
    }
 
    private static string? DecodeBase64Url(string value)
    {
        try
        {
            var padded = value.Replace('-', '+').Replace('_', '/');
            padded = padded.PadRight(padded.Length + ((4 - padded.Length % 4) % 4), '=');
            return Encoding.UTF8.GetString(Convert.FromBase64String(padded));
        }
        catch (FormatException)
        {
            return null;
        }
    }
 
    private static string? TryGetString(JsonElement element, string propertyName)
    {
        return element.TryGetProperty(propertyName, out var property) && property.ValueKind == JsonValueKind.String
            ? property.GetString()
            : null;
    }
 
    private IEnumerable<TokenCandidate> GetGitHubTokenEnvironmentCandidates(CancellationToken cancellationToken)
    {
        var exactNames = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
        {
            "GH_TOKEN",
            "GITHUB_TOKEN",
            "GITHUB_PAT",
            "GITHUB_OAUTH_TOKEN",
            "GITHUB_ACCESS_TOKEN"
        };
 
        foreach (var (name, value) in GetEnvironmentVariables())
        {
            cancellationToken.ThrowIfCancellationRequested();
 
            if (exactNames.Contains(name) && value is not null && LooksLikeGitHubToken(value))
            {
                yield return new TokenCandidate(value);
            }
        }
    }
 
    private static List<TokenCandidate> DeduplicateTokenCandidates(IEnumerable<TokenCandidate> candidates)
    {
        var seen = new HashSet<string>(StringComparer.Ordinal);
        var result = new List<TokenCandidate>();
 
        foreach (var candidate in candidates)
        {
            if (seen.Add(candidate.Token))
            {
                result.Add(candidate);
            }
        }
 
        return result;
    }
 
    private static IEnumerable<TokenCandidate> ExtractGitHubTokenCandidates(string filePath, CancellationToken cancellationToken)
    {
        cancellationToken.ThrowIfCancellationRequested();
 
        if (!File.Exists(filePath))
        {
            yield break;
        }
 
        string text;
        try
        {
            text = File.ReadAllText(filePath);
        }
        catch (Exception ex) when (ex is IOException or UnauthorizedAccessException)
        {
            yield break;
        }
 
        if (string.IsNullOrWhiteSpace(text))
        {
            yield break;
        }
 
        foreach (Match match in GitHubTokenRegex().Matches(text))
        {
            cancellationToken.ThrowIfCancellationRequested();
 
            var token = match.Value;
            if (LooksLikeGitHubToken(token))
            {
                yield return new TokenCandidate(token);
            }
        }
    }
 
    private static IEnumerable<string> EnumerateExistingFiles(string directory, CancellationToken cancellationToken, params string[] fileNames)
    {
        cancellationToken.ThrowIfCancellationRequested();
 
        if (!Directory.Exists(directory))
        {
            yield break;
        }
 
        foreach (var fileName in fileNames)
        {
            cancellationToken.ThrowIfCancellationRequested();
 
            var path = Path.Combine(directory, fileName);
            if (File.Exists(path))
            {
                yield return path;
            }
        }
    }
 
    private IEnumerable<string> GetVsCodeStateDatabasePaths()
    {
        var home = _executionContext.HomeDirectory.FullName;
 
        if (_environment.IsWindows())
        {
            var appData = GetSpecialFolderPath(Environment.SpecialFolder.ApplicationData, "APPDATA");
            if (string.IsNullOrWhiteSpace(appData))
            {
                yield break;
            }
 
            foreach (var product in GetVsCodeProductNames())
            {
                yield return Path.Combine(appData, product, "User", "globalStorage", "state.vscdb");
            }
 
            yield break;
        }
 
        if (_environment.IsMacOS())
        {
            if (string.IsNullOrWhiteSpace(home))
            {
                yield break;
            }
 
            foreach (var product in GetVsCodeProductNames())
            {
                yield return Path.Combine(home, "Library", "Application Support", product, "User", "globalStorage", "state.vscdb");
            }
 
            yield break;
        }
 
        var xdgConfigHome = _environment.GetEnvironmentVariable("XDG_CONFIG_HOME");
        var configHome = string.IsNullOrWhiteSpace(xdgConfigHome) ? Path.Combine(home, ".config") : xdgConfigHome;
        foreach (var product in GetVsCodeProductNames())
        {
            yield return Path.Combine(configHome, product, "User", "globalStorage", "state.vscdb");
        }
 
        if (IsWsl())
        {
            yield return Path.Combine(home, ".vscode-server", "data", "User", "globalStorage", "state.vscdb");
            yield return Path.Combine(home, ".vscode-server-insiders", "data", "User", "globalStorage", "state.vscdb");
        }
    }
 
    private static string[] GetVsCodeProductNames()
    {
        return ["Code", "Code - Insiders", "VSCodium"];
    }
 
    private bool IsWsl()
    {
        if (!_environment.IsLinux())
        {
            return false;
        }
 
        if (!string.IsNullOrWhiteSpace(_environment.GetEnvironmentVariable("WSL_DISTRO_NAME")) ||
            !string.IsNullOrWhiteSpace(_environment.GetEnvironmentVariable("WSL_INTEROP")))
        {
            return true;
        }
 
        try
        {
            if (!File.Exists("/proc/sys/kernel/osrelease"))
            {
                return false;
            }
 
            var osRelease = File.ReadAllText("/proc/sys/kernel/osrelease");
            return osRelease.Contains("microsoft", StringComparison.OrdinalIgnoreCase) ||
                osRelease.Contains("wsl", StringComparison.OrdinalIgnoreCase);
        }
        catch (Exception ex) when (ex is IOException or UnauthorizedAccessException)
        {
            return false;
        }
    }
 
    private bool CommandExists(string command)
    {
        var path = _environment.GetEnvironmentVariable("PATH");
        if (string.IsNullOrWhiteSpace(path))
        {
            return false;
        }
 
        var extensions = _environment.IsWindows() && string.IsNullOrEmpty(Path.GetExtension(command))
            ? (_environment.GetEnvironmentVariable("PATHEXT") ?? ".EXE;.CMD;.BAT;.COM").Split(';', StringSplitOptions.RemoveEmptyEntries)
            : [string.Empty];
 
        foreach (var directory in path.Split(Path.PathSeparator, StringSplitOptions.RemoveEmptyEntries))
        {
            foreach (var extension in extensions)
            {
                var candidate = Path.Combine(directory, command + extension);
                if (File.Exists(candidate))
                {
                    return true;
                }
            }
        }
 
        return false;
    }
 
    private string GetSpecialFolderPath(Environment.SpecialFolder folder, string environmentVariableName)
    {
        return _environment.GetEnvironmentVariable(environmentVariableName) ??
            Environment.GetFolderPath(folder);
    }
 
    private IEnumerable<(string Name, string? Value)> GetEnvironmentVariables()
    {
        return _environment.GetEnvironmentVariables();
    }
 
    private static JsonObject? TryParseJsonObject(string text)
    {
        try
        {
            var start = text.IndexOf('{');
            var end = text.LastIndexOf('}');
            return start >= 0 && end > start
                ? JsonNode.Parse(text[start..(end + 1)], documentOptions: new JsonDocumentOptions { AllowTrailingCommas = true }) as JsonObject
                : null;
        }
        catch (JsonException)
        {
            return null;
        }
    }
 
    private static InternalMicrosoftProbeResult Detected(string? alias, string? domain = null)
    {
        return new InternalMicrosoftProbeResult(IsInternalMicrosoft: true, Alias: NormalizeAlias(alias), Domain: NormalizeAdDomainName(domain));
    }
 
    private static string? GetFirstValue(IReadOnlyDictionary<string, string> values, params string[] keys)
    {
        foreach (var key in keys)
        {
            if (values.TryGetValue(key, out var value) && !string.IsNullOrWhiteSpace(value))
            {
                return value;
            }
        }
 
        return null;
    }
 
    private static string? ExtractAdDomainNameFromDsReg(IReadOnlyDictionary<string, string> values)
    {
        var domain = GetFirstValue(
            values,
            "DomainName",
            "Domain Name",
            "OnPremisesDomainName",
            "On Premises Domain Name",
            "OnPremDomainName",
            "UserDnsDomain",
            "User DNS Domain");
 
        return ExtractAdDomainNameFromCorpDnsName(domain) ?? NormalizeAdDomainName(domain);
    }
 
    private static string? ExtractAdDomainNameFromCorpDnsName(string? dnsDomain)
    {
        if (string.IsNullOrWhiteSpace(dnsDomain))
        {
            return null;
        }
 
        var trimmed = dnsDomain.Trim().TrimEnd('.');
        if (!trimmed.EndsWith(CorpMicrosoftDomainSuffix, StringComparison.OrdinalIgnoreCase))
        {
            return null;
        }
 
        return NormalizeAdDomainName(trimmed[..^CorpMicrosoftDomainSuffix.Length]);
    }
 
    private static string? NormalizeAdDomainName(string? domain)
    {
        if (string.IsNullOrWhiteSpace(domain))
        {
            return null;
        }
 
        var normalized = domain.Trim().TrimEnd('.');
        if (normalized.EndsWith(CorpMicrosoftDomainSuffix, StringComparison.OrdinalIgnoreCase))
        {
            return ExtractAdDomainNameFromCorpDnsName(normalized);
        }
 
        return normalized.All(c => char.IsLetterOrDigit(c) || c is '.' or '_' or '-')
            ? normalized.ToUpperInvariant()
            : null;
    }
 
    private static string? ExtractAliasFromAccountIdentifier(string? value)
    {
        if (string.IsNullOrWhiteSpace(value))
        {
            return null;
        }
 
        var match = MicrosoftAccountRegex().Match(value);
        return match.Success ? NormalizeAlias(match.Groups["alias"].Value) : null;
    }
 
    private static string? ExtractAdDomainNameFromAccountIdentifier(string? value)
    {
        if (string.IsNullOrWhiteSpace(value))
        {
            return null;
        }
 
        var atIndex = value.LastIndexOf('@');
        return atIndex >= 0 && atIndex < value.Length - 1
            ? ExtractAdDomainNameFromCorpDnsName(value[(atIndex + 1)..])
            : null;
    }
 
    private static string? NormalizeAlias(string? alias)
    {
        if (string.IsNullOrWhiteSpace(alias))
        {
            return null;
        }
 
        var normalized = alias.Trim();
        return normalized.All(c => char.IsLetterOrDigit(c) || c is '.' or '_' or '-') ? normalized : null;
    }
 
    private static bool HasJsonStringProperty(JsonObject json, string propertyName, string expectedValue)
    {
        return TryGetString(json, propertyName)?.Equals(expectedValue, StringComparison.OrdinalIgnoreCase) == true;
    }
 
    private static string? TryGetString(JsonObject json, string propertyName)
    {
        return json.TryGetPropertyValue(propertyName, out var value) &&
            value is JsonValue jsonValue &&
            jsonValue.TryGetValue<string>(out var text)
            ? text
            : null;
    }
 
    private static bool LooksLikeGitHubToken(string token)
    {
        return GitHubTokenRegex().IsMatch(token);
    }
 
    [GeneratedRegex(@"(?:github_pat_[A-Za-z0-9_]{20,}|gh[opsru]_[A-Za-z0-9_]{20,})")]
    private static partial Regex GitHubTokenRegex();
 
    [GeneratedRegex(@"eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+")]
    private static partial Regex JwtRegex();
 
    [GeneratedRegex(@"(?<![A-Za-z0-9._%+\-\\])(?<alias>[A-Za-z0-9._%+-]+)@(?<domain>(?:[A-Za-z0-9-]+\.)*microsoft\.com)(?![A-Za-z0-9._%+-])", RegexOptions.IgnoreCase)]
    private static partial Regex MicrosoftAccountRegex();
 
    private readonly record struct ProcessResult(int ExitCode, string Stdout, string Stderr);
    private readonly record struct TokenCandidate(string Token);
    private readonly record struct TenantAliasEvidence(string TenantId, string? Alias);
}
 
internal sealed record InternalMicrosoftProbe(string Name, Func<CancellationToken, Task<InternalMicrosoftProbeResult>> DetectAsync);
 
internal readonly record struct InternalMicrosoftProbeResult(bool IsInternalMicrosoft, string? Alias, string? Domain)
{
    public static InternalMicrosoftProbeResult NotDetected { get; } = new(IsInternalMicrosoft: false, Alias: null, Domain: null);
}
 
internal sealed record InternalMicrosoftDetectionResult(bool IsInternalMicrosoft, string? Source, string? Alias, string? Domain);
 
internal sealed record InternalMicrosoftDetectorCacheEntry
{
    public bool IsInternalMicrosoft { get; init; }
    public string? Source { get; init; }
    public string? Alias { get; init; }
    public string? Domain { get; init; }
    public DateTimeOffset LastRunUtc { get; init; }
}