Skip to content

Secure AppHost RPC with session token handshake#15344

Merged
joperezr merged 8 commits intorelease/13.2from
sebros/secure-apphost-rpc-token
Mar 19, 2026
Merged

Secure AppHost RPC with session token handshake#15344
joperezr merged 8 commits intorelease/13.2from
sebros/secure-apphost-rpc-token

Conversation

@sebastienros
Copy link
Contributor

@sebastienros sebastienros commented Mar 18, 2026

Description

Secure the local JSON-RPC channel between the Aspire CLI, the managed AppHost server, and guest runtimes by introducing a session token handshake.

The CLI now generates a cryptographically random session token for each AppHost-server session and passes it to the managed AppHost server and guest runtime through environment variables. Clients must authenticate immediately after connecting, before they can invoke AppHost RPC methods. This makes the trust boundary explicit in the implementation instead of relying only on named pipe / Unix domain socket discovery and OS ACLs.

Token flow:

  • The Aspire CLI spawns the managed AppHost server and generates a cryptographically random bearer token for that session.
  • The CLI passes the token to the managed AppHost server via ASPIRE_REMOTE_APPHOST_TOKEN.
  • The CLI also passes the same token to the guest runtime environment when it launches generated guests.
  • CLI-side and guest-side RPC clients authenticate on connect by invoking authenticate(token) before using privileged RPC methods.
  • The managed AppHost server keeps per-connection authentication state and rejects protected methods until authentication succeeds.

Why this design was chosen:

  • It is a small, cross-language change that works uniformly for TypeScript, Python, Go, Java, and Rust generated SDKs.
  • It avoids putting secrets into normal capability payloads, which reduces the risk of accidental token leakage through request logging.
  • It is sufficient for the intended v1 security level: protect against unrelated local processes that learn the socket/pipe path.
  • It intentionally does not try to defend against a same-user local attacker who can inspect process environments or memory; that stronger boundary would require OS peer-identity checks or a different secret delivery mechanism.

Checklist

  • Is this feature complete?
    • Yes. Ready to ship.
    • No. Follow-up changes expected.
  • Are you including unit tests for the changes and scenario tests if relevant?
    • Yes
    • No
  • Did you add public API?
    • Yes
      • If yes, did you have an API Review for it?
        • Yes
        • No
      • Did you add <remarks /> and <code /> elements on your triple slash comments?
        • Yes
        • No
    • No
  • Does the change make any security assumptions or guarantees?
    • Yes
      • If yes, have you done a threat model and had a security review?
        • Yes
        • No
    • No
  • Does the change require an update in our Aspire docs?

@github-actions
Copy link
Contributor

github-actions bot commented Mar 18, 2026

🚀 Dogfood this PR with:

⚠️ WARNING: Do not do this without first carefully reviewing the code of this PR to satisfy yourself it is safe.

curl -fsSL https://raw.githubusercontent.com/dotnet/aspire/main/eng/scripts/get-aspire-cli-pr.sh | bash -s -- 15344

Or

  • Run remotely in PowerShell:
iex "& { $(irm https://raw.githubusercontent.com/dotnet/aspire/main/eng/scripts/get-aspire-cli-pr.ps1) } 15344"

@github-actions
Copy link
Contributor

The transient CI rerun workflow requested reruns for the following jobs after analyzing the failed attempt.
GitHub's job rerun API also reruns dependent jobs, so the retry is being tracked in the rerun attempt.
The job links below point to the failed attempt that matched the retry-safe transient failure rules.

  • Tests / Milvus.Client / Milvus.Client (windows-latest) - Failed step 'Build test project' will be retried because the job log shows a likely transient infrastructure network failure. Matched pattern: /Unable to load the service index for source https://(?:pkgs.dev.azure.com/dnceng|dnceng.pkgs.visualstudio.com)/public/_packaging//i.

@github-actions
Copy link
Contributor

The transient CI rerun workflow requested reruns for the following jobs after analyzing the failed attempt.
GitHub's job rerun API also reruns dependent jobs, so the retry is being tracked in the rerun attempt.
The job links below point to the failed attempt that matched the retry-safe transient failure rules.

@sebastienros sebastienros marked this pull request as ready for review March 18, 2026 17:45
Copilot AI review requested due to automatic review settings March 18, 2026 17:45
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds an authentication handshake to the RemoteHost JSON-RPC channel (using ASPIRE_REMOTE_APPHOST_TOKEN) and updates the CLI + generated SDK transports/tests to participate in that handshake, preventing unauthenticated RPC access to AppHost operations.

Changes:

  • Introduce per-connection JSON-RPC authentication state on the RemoteHost server and gate protected RPC methods until authenticated.
  • Update Aspire CLI flows to generate and pass an authentication token when launching/connecting to the AppHost server.
  • Update generated SDK transports (TS/Go/Python/Java/Rust) plus snapshots/tests to authenticate during connect (or shortly after).

Reviewed changes

Copilot reviewed 36 out of 36 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
tests/Aspire.Hosting.RemoteHost.Tests/JsonRpcAuthenticationTests.cs Adds server/client tests validating auth gating and disconnect behavior on failed auth.
tests/Aspire.Hosting.CodeGeneration.TypeScript.Tests/Snapshots/transport.verified.ts Updates TS transport snapshot to include authentication during connect.
tests/Aspire.Hosting.CodeGeneration.TypeScript.JsTests/tests/transport.test.ts Updates JS/TS transport unit tests to expect authenticate during connect and to provide a test handler.
tests/Aspire.Hosting.CodeGeneration.Rust.Tests/Snapshots/TwoPassScanningGeneratedAspire.verified.rs Updates Rust snapshot to authenticate when token env var is present.
tests/Aspire.Hosting.CodeGeneration.Rust.Tests/Snapshots/AtsGeneratedAspire.verified.rs Updates Rust snapshot to authenticate when token env var is present.
tests/Aspire.Hosting.CodeGeneration.Python.Tests/Snapshots/TwoPassScanningGeneratedAspire.verified.py Updates Python snapshot to authenticate when token env var is present.
tests/Aspire.Hosting.CodeGeneration.Python.Tests/Snapshots/AtsGeneratedAspire.verified.py Updates Python snapshot to authenticate when token env var is present.
tests/Aspire.Hosting.CodeGeneration.Java.Tests/Snapshots/TwoPassScanningGeneratedAspire.verified.java Updates Java snapshot to authenticate when token env var is present.
tests/Aspire.Hosting.CodeGeneration.Java.Tests/Snapshots/AtsGeneratedAspire.verified.java Updates Java snapshot to authenticate when token env var is present.
tests/Aspire.Hosting.CodeGeneration.Go.Tests/Snapshots/TwoPassScanningGeneratedAspire.verified.go Updates Go snapshot to authenticate when token env var is present.
tests/Aspire.Hosting.CodeGeneration.Go.Tests/Snapshots/AtsGeneratedAspire.verified.go Updates Go snapshot to authenticate when token env var is present.
src/Shared/KnownConfigNames.cs Adds KnownConfigNames.RemoteAppHostToken constant for ASPIRE_REMOTE_APPHOST_TOKEN.
src/Aspire.Hosting.RemoteHost/RemoteHostServer.cs Adjusts DI lifetimes (scoped) and registers per-client auth state.
src/Aspire.Hosting.RemoteHost/RemoteAppHostService.cs Adds authenticate RPC method and enforces auth for capability/cancellation calls.
src/Aspire.Hosting.RemoteHost/Language/LanguageService.cs Requires authentication before language/scaffolding RPC methods.
src/Aspire.Hosting.RemoteHost/JsonRpcServer.cs Resolves per-client scoped services per connection and registers multiple RPC targets.
src/Aspire.Hosting.RemoteHost/JsonRpcAuthenticationState.cs New per-connection auth state + fixed-time token comparison and gatekeeper helper.
src/Aspire.Hosting.RemoteHost/CodeGeneration/CodeGenerationService.cs Requires authentication for capabilities/codegen RPC methods.
src/Aspire.Hosting.RemoteHost/Aspire.Hosting.RemoteHost.csproj Links KnownConfigNames.cs into RemoteHost project.
src/Aspire.Hosting.CodeGeneration.TypeScript/Resources/transport.ts TS transport now authenticates during connect using ASPIRE_REMOTE_APPHOST_TOKEN.
src/Aspire.Hosting.CodeGeneration.Rust/Resources/transport.rs Adds Rust authenticate client method.
src/Aspire.Hosting.CodeGeneration.Rust/AtsRustCodeGenerator.cs Emits Rust connect helper that authenticates when token env var is present.
src/Aspire.Hosting.CodeGeneration.Python/Resources/transport.py Adds Python authenticate client method.
src/Aspire.Hosting.CodeGeneration.Python/AtsPythonCodeGenerator.cs Emits Python connect helper that authenticates when token env var is present.
src/Aspire.Hosting.CodeGeneration.Java/Resources/Transport.java Adds Java authenticate client method.
src/Aspire.Hosting.CodeGeneration.Java/AtsJavaCodeGenerator.cs Emits Java connect helper that authenticates when token env var is present.
src/Aspire.Hosting.CodeGeneration.Go/Resources/transport.go Adds Go Authenticate client method.
src/Aspire.Hosting.CodeGeneration.Go/AtsGoCodeGenerator.cs Emits Go connect helper that authenticates when token env var is present.
src/Aspire.Cli/Scaffolding/ScaffoldingService.cs Generates an auth token and passes it to the AppHost server + RPC client during scaffolding.
src/Aspire.Cli/Projects/IAppHostRpcClient.cs Updates RPC client factory API to require authentication token for ConnectAsync.
src/Aspire.Cli/Projects/GuestAppHostProject.cs Generates/passes token for codegen/run/publish AppHost server sessions and guest env vars.
src/Aspire.Cli/Projects/AppHostServerSession.cs Persists per-session auth token and uses it when creating RPC client.
src/Aspire.Cli/Projects/AppHostRpcClient.cs Updates ConnectAsync to authenticate after connect and ensure cleanup on failure.
src/Aspire.Cli/Commands/Sdk/SdkGenerateCommand.cs Generates/passes token when running server for SDK generation.
src/Aspire.Cli/Commands/Sdk/SdkDumpCommand.cs Generates/passes token when running server for capability dump.
src/Aspire.Cli/Aspire.Cli.csproj Links shared TokenGenerator.cs into CLI project.
Comments suppressed due to low confidence (1)

src/Aspire.Hosting.RemoteHost/JsonRpcServer.cs:212

  • JsonRpcServer calls jsonRpc.StartListening() before wiring the JsonRpc instance into RemoteAppHostService via SetClientConnection(). A client can potentially send an authenticate request immediately after StartListening, and the handler may run before _clientRpc is set, meaning failed authentication won’t actually close the connection as intended. Set the client connection before StartListening (or otherwise ensure the service has the JsonRpc reference before any requests are dispatched).
            using var jsonRpc = new JsonRpc(handler, clientService);

            // Add the shared CodeGenerationService as an additional target for generateCode method
            jsonRpc.AddLocalRpcTarget(codeGenerationService);

            // Add the shared LanguageService as an additional target for language support methods
            jsonRpc.AddLocalRpcTarget(languageService);

            jsonRpc.StartListening();

            // Enable bidirectional communication - allow .NET to call back to TypeScript
            clientService.SetClientConnection(jsonRpc);

            _logger.LogDebug("JsonRpc connection established for client {ClientId} (bidirectional)", clientId);

@github-actions
Copy link
Contributor

Re-running the failed jobs in the CI workflow for this pull request because 3 jobs were identified as retry-safe transient failures in the CI run attempt.
GitHub was asked to rerun all failed jobs for that attempt, and the rerun is being tracked in the rerun attempt.
The job links below point to the failed attempt jobs that matched the retry-safe transient failure rules.

Copy link
Member

@JamesNK JamesNK left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Security design looks solid overall — FixedTimeEquals for comparison, scoped auth state, connection teardown on failed auth, and token generation with sufficient entropy. The main concerns are around code quality (indentation), cross-language consistency (Java named vs positional params), and API design (dictionary mutation side effect).

@sebastienros sebastienros added the Servicing-consider Issue for next servicing release review label Mar 18, 2026
Assert.Equal(session.AuthenticationToken, project.ReceivedEnvironmentVariables[KnownConfigNames.RemoteAppHostToken]);
}

private sealed class RecordingAppHostServerProject : IAppHostServerProject
Copy link
Member

@JamesNK JamesNK Mar 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: I hate private test implementations. Can it be a shared TestAppHostServiceProject in the TestServices directory?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I need to update AGENTS.md to tell it to stop doing this.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Deja-vu

@joperezr joperezr added Servicing-approved Approved for servicing release and removed Servicing-consider Issue for next servicing release review labels Mar 19, 2026
@joperezr
Copy link
Member

@sebastienros do we understand why the failures are happening here?

sebastienros and others added 6 commits March 18, 2026 19:33
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
sebastienros and others added 2 commits March 18, 2026 19:33
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sync the AppHost server project contract with current release/13.2 and make the RemoteHost authentication test tolerate the expected disconnect race after failed auth.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@sebastienros sebastienros force-pushed the sebros/secure-apphost-rpc-token branch from fa99c8e to 10ae547 Compare March 19, 2026 02:35
@github-actions
Copy link
Contributor

🎬 CLI E2E Test Recordings — 52 recordings uploaded (commit 10ae547)

View recordings
Test Recording
AddPackageInteractiveWhileAppHostRunningDetached ▶️ View Recording
AddPackageWhileAppHostRunningDetached ▶️ View Recording
AgentCommands_AllHelpOutputs_AreCorrect ▶️ View Recording
AgentInitCommand_DefaultSelection_InstallsSkillOnly ▶️ View Recording
AgentInitCommand_MigratesDeprecatedConfig ▶️ View Recording
AspireAddPackageVersionToDirectoryPackagesProps ▶️ View Recording
AspireUpdateRemovesAppHostPackageVersionFromDirectoryPackagesProps ▶️ View Recording
Banner_DisplayedOnFirstRun ▶️ View Recording
Banner_DisplayedWithExplicitFlag ▶️ View Recording
CertificatesClean_RemovesCertificates ▶️ View Recording
CertificatesTrust_WithNoCert_CreatesAndTrustsCertificate ▶️ View Recording
CertificatesTrust_WithUntrustedCert_TrustsCertificate ▶️ View Recording
ConfigSetGet_CreatesNestedJsonFormat ▶️ View Recording
CreateAndDeployToDockerCompose ▶️ View Recording
CreateAndDeployToDockerComposeInteractive ▶️ View Recording
CreateAndPublishToKubernetes ▶️ View Recording
CreateAndRunAspireStarterProject ▶️ View Recording
CreateAndRunAspireStarterProjectWithBundle ▶️ View Recording
CreateAndRunEmptyAppHostProject ▶️ View Recording
CreateAndRunJsReactProject ▶️ View Recording
CreateAndRunPythonReactProject ▶️ View Recording
CreateAndRunTypeScriptEmptyAppHostProject ▶️ View Recording
CreateAndRunTypeScriptStarterProject ▶️ View Recording
CreateStartAndStopAspireProject ▶️ View Recording
CreateTypeScriptAppHostWithViteApp ▶️ View Recording
DescribeCommandResolvesReplicaNames ▶️ View Recording
DescribeCommandShowsRunningResources ▶️ View Recording
DetachFormatJsonProducesValidJson ▶️ View Recording
DoctorCommand_DetectsDeprecatedAgentConfig ▶️ View Recording
DoctorCommand_WithSslCertDir_ShowsTrusted ▶️ View Recording
DoctorCommand_WithoutSslCertDir_ShowsPartiallyTrusted ▶️ View Recording
GlobalMigration_HandlesCommentsAndTrailingCommas ▶️ View Recording
GlobalMigration_HandlesMalformedLegacyJson ▶️ View Recording
GlobalMigration_PreservesAllValueTypes ▶️ View Recording
GlobalMigration_SkipsWhenNewConfigExists ▶️ View Recording
GlobalSettings_MigratedFromLegacyFormat ▶️ View Recording
InvalidAppHostPathWithComments_IsHealedOnRun ▶️ View Recording
LogsCommandShowsResourceLogs ▶️ View Recording
PsCommandListsRunningAppHost ▶️ View Recording
PsFormatJsonOutputsOnlyJsonToStdout ▶️ View Recording
PublishWithDockerComposeServiceCallbackSucceeds ▶️ View Recording
RestoreGeneratesSdkFiles ▶️ View Recording
RunWithMissingAwaitShowsHelpfulError ▶️ View Recording
SecretCrudOnDotNetAppHost ▶️ View Recording
SecretCrudOnTypeScriptAppHost ▶️ View Recording
StagingChannel_ConfigureAndVerifySettings_ThenSwitchChannels ▶️ View Recording
StopAllAppHostsFromAppHostDirectory ▶️ View Recording
StopAllAppHostsFromUnrelatedDirectory ▶️ View Recording
StopNonInteractiveMultipleAppHostsShowsError ▶️ View Recording
StopNonInteractiveSingleAppHost ▶️ View Recording
StopWithNoRunningAppHostExitsSuccessfully ▶️ View Recording
TypeScriptAppHostWithProjectReferenceIntegration ▶️ View Recording

📹 Recordings uploaded automatically from CI run #23277104122

@joperezr joperezr merged commit 930e921 into release/13.2 Mar 19, 2026
257 checks passed
@joperezr joperezr deleted the sebros/secure-apphost-rpc-token branch March 19, 2026 04:07
@dotnet-policy-service dotnet-policy-service bot added this to the 13.2 milestone Mar 19, 2026
Copilot AI pushed a commit that referenced this pull request Mar 19, 2026
* Secure AppHost RPC with session token handshake

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Fix TypeScript RPC authentication request

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Address AppHost RPC auth review feedback

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Update SDKs with token checks

* Move scoped registrations to correct location

* Consolidate temporary AppHost sessions

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Address PR review feedback

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Fix CI regressions after branch drift

Sync the AppHost server project contract with current release/13.2 and make the RemoteHost authentication test tolerate the expected disconnect race after failed auth.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Servicing-approved Approved for servicing release

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants