Skip to content

[access-secrets-github] - Track stored-secret environment and GitHub executor slices #1512

Description

@JoshuaRowePhantom

Part of #1505

Parent

Dependencies

Summary

Tracking parent: No production commit lands directly against this issue. Implementation is complete only through the structurally attached one-commit detail issues below.

Implement stored-secret, environment, and GitHub CLI executors behind IAccessBroker, preserve fail-closed opaque manifest handles, and remove the parallel ISecretProvider, modal consent, static GitHub resolver, IApiKeyResolver, and direct token delegate paths. This is a direct final cutover with no adapters or grant migration.

Current Limitation / Root Cause

Services/Secrets/SecretProvider.cs batches unresolved uses, persists old scope/source records, and only then resolves values. GitHubAuthTokenResolver reads GITHUB_TOKEN or invokes gh auth token outside common policy. AgentFactory, CopilotSdkChatClient, repository/persistence factories, and usage providers retain direct environment/API-key/GitHub paths. SecretRetriever can return the same SecureString across uses. These paths cannot enforce authorization-before-read, exact source/generation, independent lease ownership, or broker-wide shutdown.

Affected Files

Path Change
Llm.Interfaces/Access/StoredSecretAccessContracts.cs Closed store/environment requests/results
Llm.Interfaces/Access/GitHubCredentialAccessContracts.cs Closed GitHub request/result/purpose
Services/Access/StoredSecretAccessExecutor.cs (new) Safe inspect and post-approval exact-source read
Services/Access/GitHubCredentialAccessExecutor.cs (new) Environment/CLI acquisition after approval
Llm.Core/Secrets/AgentDefinitionSecretMaterializer.cs Create one broker request per use and retain opaque handles
Llm.Core/Secrets/*, Services/Secrets/* Replace old policy/dialog/provider contracts; retain platform store as executor dependency
GitHubAuthTokenResolver.cs, IApiKeyResolver.cs, EnvironmentApiKeyResolver.cs Remove caller-facing paths
AgentFactory.cs, CopilotSdkChatClient.cs, repository/persistence/usage consumers Submit typed broker requests
Existing secret/GitHub tests Replace with cases below

Design / Fix

public sealed record StoredSecretAccessRequest
    : CredentialAccessRequest<StoredSecretAccessResult>
{
    public required SecretReference Reference { get; init; }
}

public sealed class StoredSecretAccessResult : ICredentialAccessResult
{
    public required SensitiveValueLease Value { get; init; }
    public ValueTask DisposeAsync();
}

public sealed record GitHubCredentialAccessRequest
    : CredentialAccessRequest<GitHubCredentialAccessResult>
{
    public required GitHubCredentialPurpose Purpose { get; init; }
}

public sealed class GitHubCredentialAccessResult : ICredentialAccessResult
{
    public required SensitiveValueLease Token { get; init; }
    public required CredentialSourceKind SourceKind { get; init; }
    public ValueTask DisposeAsync();
}

internal sealed class StoredSecretAccessExecutor :
    ICredentialAccessExecutor<StoredSecretAccessRequest, StoredSecretAccessResult>
{
    public ValueTask<CredentialInspection> InspectAsync(
        StoredSecretAccessRequest request,
        CancellationToken cancellationToken);
    public ValueTask<CredentialExecutionOutcome<StoredSecretAccessResult>> ExecuteAsync(
        StoredSecretAccessRequest request,
        CredentialInspection inspection,
        ReleaseAuthorization authorization,
        ProviderInteractionPermit? interactionPermit,
        ICredentialExecutionContext context,
        CancellationToken cancellationToken);
}

internal sealed class GitHubCredentialAccessExecutor :
    ICredentialAccessExecutor<GitHubCredentialAccessRequest, GitHubCredentialAccessResult>
{
    public ValueTask<CredentialInspection> InspectAsync(
        GitHubCredentialAccessRequest request,
        CancellationToken cancellationToken);
    public ValueTask<CredentialExecutionOutcome<GitHubCredentialAccessResult>> ExecuteAsync(
        GitHubCredentialAccessRequest request,
        CredentialInspection inspection,
        ReleaseAuthorization authorization,
        ProviderInteractionPermit? interactionPermit,
        ICredentialExecutionContext context,
        CancellationToken cancellationToken);
}

StoredSecretAccessExecutor.InspectAsync may enumerate safe store names/existence and environment source availability; it cannot call ReadAsync or read an environment value. GitHubCredentialAccessExecutor.InspectAsync identifies configured environment/CLI/cache source metadata but cannot read GITHUB_TOKEN or invoke gh.

After broker approval, ExecuteAsync reads exactly the selected source and validates its identity. Allow is valid only for an inspection marked usable; if the entry disappears, return renewal-needed and update the same row. Never switch source silently. gh auth token output is reduced directly into an independently owned lease and never logged. If an interactive GitHub executor is absent, missing credentials return sanitized failure rather than a false Perform row.

Manifest scanning retains ${SECRET:<opaque-use-handle>} and fail-closed materialization, but every occurrence submits its own named-initializer StoredSecretAccessRequest. The handle points to an independently owned lease, not a shared retriever. UseAsync is invoked only at the final SDK/HTTP constructor boundary.

Replace old grants with IReleaseAuthorizationPolicy; do not read, transform, or migrate old records. Remove OAuthSecretSource: OAuth is a provider route. Directly remove ISecretProvider, SecretUseDialog, static resolver/API-key abstractions, and production delegates after their call sites move in this issue.

Implementation Detail Issues

  1. [access-stored-secret] - Implement platform-store and environment secret executor #1539 and [access-github] - Implement approved environment and gh CLI token executor #1541 in parallel.
  2. [access-secret-materialization] - Bind manifest secret handles to broker leases #1540, [access-github-consumers] - Migrate repository and persistence token callers #1543, and [access-github-usage] - Migrate GitHub usage providers to broker leases #1566 in parallel.
  3. [access-agent-secrets] - Migrate AgentFactory and Copilot client credential uses #1542 and [access-secret-dialog-cutover] - Remove modal secret dialog GUI implementation #1569 in parallel once their dependencies are ready.
  4. [access-secret-cutover] - Remove secret provider and GitHub resolver entry points #1544, [access-api-key-cutover] - Remove API-key resolvers and OAuth source marker #1567, and [access-secret-dialog-cutover] - Remove modal secret dialog contracts #1568 as separately bounded cutovers.

Commit Plan

Commit issue Small production scope
#1539 Stored-secret request/result and StoredSecretAccessExecutor
#1540 AgentDefinitionSecretMaterializer, SecretPlaceholderResolver
#1541 GitHub request/result and GitHubCredentialAccessExecutor
#1542 AgentFactory, CopilotSdkChatClient, typed AgentServices slot
#1543 EntityRepository, AgentPersistenceStoreSourceFactory
#1566 Two GitHub usage providers and GitHubCredentialUsageAdapter
#1544 Remove ISecretProvider, SecretProvider, GitHubAuthTokenResolver
#1567 Remove IApiKeyResolver, EnvironmentApiKeyResolver, OAuthSecretSource
#1568 Remove secret-dialog host/input/result contracts
#1569 Remove Avalonia secret-dialog host/view-model/window

Security and Privacy Invariants

  • Policy approval precedes platform-store/environment/CLI material access.
  • Exact source is selected and remembered; no fallback after approval.
  • No token/value enters manifest, registry, UI, logs, notifications, entities, or persistence.
  • Each use owns one lease; no shared SecureString.
  • GitHub account/source mismatch fails closed.

Tracking-parent acceptance

  • Every attached detail issue is independently implementable and testable.
  • Each detail issue produces one small commit affecting 1–3 production types plus focused tests.
  • This parent produces no aggregate implementation commit.

Exclusions

  • No GitHub device flow.
  • No OAuth/Entra/Dev Tunnel protocol.
  • No compatibility path, old grant migration, or modal batch dialog.

Expected Tests

Test Name Class What It Verifies
InspectAsync_PlatformStore_DoesNotReadSecretValue StoredSecretAccessExecutorTests Metadata-only inspection.
ExecuteAsync_PlatformStore_ReadsOnlyAfterReleaseCommit StoredSecretAccessExecutorTests Required ordering.
ExecuteAsync_SelectedSourceMissing_ReturnsRenewalNeededWithoutSwitchingSource StoredSecretAccessExecutorTests Exact-source behavior.
AgentDefinitionSecretMaterializer_MultipleUses_SubmitsIndependentBrokerRequests AgentDefinitionSecretMaterializerTests No batch contract.
AgentDefinitionSecretMaterializer_AccessFailure_FailsClosedWithoutPlaintextPlaceholder AgentDefinitionSecretMaterializerTests Manifest safety.
GitHubExecutor_InspectAsync_DoesNotReadEnvironmentOrInvokeCli GitHubCredentialAccessExecutorTests Safe inspect.
GitHubExecutor_ExecuteAsync_CliSource_InvokesAfterApprovalAndDoesNotLogStdout GitHubCredentialAccessExecutorTests CLI privacy/ordering.
GitHubExecutor_CompatibleCallers_ReturnIndependentLeases GitHubCredentialAccessExecutorTests Ownership.
GitHubExecutor_ResolvedAccountMismatch_FailsClosed GitHubCredentialAccessExecutorTests Identity binding.
AccessMigration_SecretGitHubAndApiKeyCallers_UseAccessBrokerOnly AccessMigrationTests Parallel paths removed.
AccessMigration_OldSecretProviderAndOAuthSecretSource_HaveNoProductionReferences AccessMigrationTests Final cutover.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    diagnosedRoot cause identifiedenhancementNew feature or request

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions