Skip to content

Ensure the typescript app host trusts the dev cert - #15634

Merged
David Negstad (danegsta) merged 11 commits into
mainfrom
danegsta/tsCerts
Aug 13, 2026
Merged

Ensure the typescript app host trusts the dev cert#15634
David Negstad (danegsta) merged 11 commits into
mainfrom
danegsta/tsCerts

Conversation

@danegsta

@danegsta David Negstad (danegsta) commented Mar 27, 2026

Copy link
Copy Markdown
Member

Description

Ensures TypeScript AppHosts trust the ASP.NET Core development certificate when custom AppHost code opens TLS connections to Aspire-managed resources in run mode.

  • Exports the same trusted development certificate selected by DCP into a content-addressed PEM cache under ASPIRE_HOME.
  • Declares the runtime-specific certificate bundle environment variable through TypeScript language support metadata.
  • Preserves an existing NODE_EXTRA_CA_CERTS value by creating a secure, content-addressed combined bundle.
  • Handles Windows environment-variable casing without allowing duplicate aliases to override the generated bundle.

Fixes #15489

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

github-actions Bot commented Mar 27, 2026

Copy link
Copy Markdown
Contributor

🚀 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/microsoft/aspire/main/eng/scripts/get-aspire-cli-pr.sh | bash -s -- 15634

Or

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

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR addresses #15489 by ensuring the TypeScript (Node.js) guest AppHost process trusts the ASP.NET Core development certificate, enabling TLS connections to Aspire-managed HTTPS endpoints without disabling certificate validation in user code.

Changes:

  • Export the current ASP.NET Core HTTPS dev certificate public PEM to a stable path during CLI certificate trust setup.
  • Inject NODE_EXTRA_CA_CERTS for Node.js-based guest AppHosts so Node trusts the exported dev cert.
  • Add CLI unit tests covering PEM export success/failure behavior.

Reviewed changes

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

Show a summary per file
File Description
tests/Aspire.Cli.Tests/Utils/CliTestHelper.cs Updates test DI setup for CertificateService’s new constructor dependencies.
tests/Aspire.Cli.Tests/TestServices/TestCertificateToolRunner.cs Extends test runner to support exporting the dev cert PEM.
tests/Aspire.Cli.Tests/TestServices/TestCertificateService.cs Updates test stub result to include DevCertPemPath.
tests/Aspire.Cli.Tests/Certificates/CertificateServiceTests.cs Adds tests verifying PEM export and that export failures are non-fatal.
src/Aspire.Hosting.CodeGeneration.TypeScript/TypeScriptLanguageSupport.cs Formatting-only change (trailing commas).
src/Aspire.Cli/Projects/GuestAppHostProject.cs Sets NODE_EXTRA_CA_CERTS for Node.js guest AppHosts based on exported PEM path.
src/Aspire.Cli/Certificates/NativeCertificateToolRunner.cs Implements exporting the highest-versioned valid dev cert as PEM.
src/Aspire.Cli/Certificates/ICertificateToolRunner.cs Adds contract for exporting the dev cert PEM.
src/Aspire.Cli/Certificates/CertificateService.cs Exports dev cert PEM as part of ensuring certificate trust and returns the PEM path.

Comment thread src/Aspire.Cli/Certificates/ICertificateToolRunner.cs Outdated
Comment thread src/Aspire.Cli/Projects/GuestAppHostProject.cs Outdated
Comment thread src/Aspire.Cli/Projects/GuestAppHostProject.cs Outdated

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Looks good overall — the approach of exporting the public PEM and setting NODE_EXTRA_CA_CERTS is solid. A few suggestions inline, mostly around avoiding unnecessary work on every run.

Comment thread src/Aspire.Cli/Certificates/NativeCertificateToolRunner.cs Outdated
Comment thread src/Aspire.Cli/Projects/GuestAppHostProject.cs Outdated
Comment thread src/Aspire.Cli/Certificates/CertificateService.cs Outdated
Comment thread src/Aspire.Cli/Certificates/NativeCertificateToolRunner.cs Outdated
@JamesNK

Copy link
Copy Markdown
Member

GuestAppHostProject.cs line 496 — The LanguageId.Contains("nodejs", ...) substring check is fragile and inconsistent with the rest of the codebase. Other places use the KnownLanguageId constants with direct equality, e.g. project.LanguageId == KnownLanguageId.CSharp in AddCommand.cs. This should be:

if (devCertPemPath is not null && LanguageId == KnownLanguageId.TypeScript)

where KnownLanguageId.TypeScript is "typescript/nodejs". TypeScript is the only Node.js-based runtime today, and if more are added later, a dedicated property on RuntimeSpec or LanguageInfo would be the right abstraction.

Comment thread src/Aspire.Cli/Projects/GuestAppHostProject.cs Outdated
@github-actions

Copy link
Copy Markdown
Contributor

Re-running the failed jobs in the CI workflow for this pull request because 2 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.

@github-actions

Copy link
Copy Markdown
Contributor

🎬 CLI E2E Test Recordings — 52 recordings uploaded (commit 378e063)

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
Banner_NotDisplayedWithNoLogoFlag ▶️ View Recording
CertificatesClean_RemovesCertificates ▶️ View Recording
CertificatesTrust_WithNoCert_CreatesAndTrustsCertificate ▶️ View Recording
CertificatesTrust_WithUntrustedCert_TrustsCertificate ▶️ View Recording
ConfigSetGet_CreatesNestedJsonFormat ▶️ View Recording
CreateAndRunAspireStarterProject ▶️ View Recording
CreateAndRunAspireStarterProjectWithBundle ▶️ View Recording
CreateAndRunEmptyAppHostProject ▶️ View Recording
CreateAndRunJavaEmptyAppHostProject ▶️ View Recording
CreateAndRunJsReactProject ▶️ View Recording
CreateAndRunPythonReactProject ▶️ View Recording
CreateAndRunTypeScriptEmptyAppHostProject ▶️ View Recording
CreateAndRunTypeScriptStarterProject ▶️ View Recording
CreateJavaAppHostWithViteApp ▶️ 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 #23626553641

Comment thread src/Aspire.Cli/Projects/GuestAppHostProject.cs Outdated
@sebastienros

Copy link
Copy Markdown
Contributor

Any chance this gets in 13.3?

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings August 12, 2026 22:22
@github-actions

This comment has been minimized.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 10 out of 10 changed files in this pull request and generated no new comments.

Suppressed comments (3)

src/Aspire.Cli/Projects/GuestAppHostProject.cs:2008

  • This only checks the ambient process and raw context dictionaries, but environmentVariables already includes launch-profile values. Consequently, a NODE_EXTRA_CA_CERTS configured in launchSettings.json is not detected and is overwritten at line 2018 instead of being preserved and warned about. Check the merged dictionary as well.
        var existingNodeExtraCaCerts = Environment.GetEnvironmentVariable("NODE_EXTRA_CA_CERTS")
            ?? (contextEnvironmentVariables.TryGetValue("NODE_EXTRA_CA_CERTS", out var ctxValue) ? ctxValue : null);

src/Aspire.Cli/Certificates/CertificateService.cs:66

  • This bypasses the CLI's configured Aspire state root. CliExecutionContext.AspireHomeDirectory honors ASPIRE_HOME and install-route-specific roots (CliExecutionContext.cs:160-180), while rebuilding HomeDirectory/.aspire does not; with a custom writable Aspire home and a read-only user profile, export silently fails and the Node AppHost remains unable to trust the certificate. Store the PEM under AspireHomeDirectory like other CLI state.
    internal string DevCertPemPath => Path.Combine(
        executionContext.HomeDirectory.FullName, ".aspire", "dev-certs", DevCertPemFileName);

src/Aspire.Cli/Certificates/NativeCertificateToolRunner.cs:195

  • The new native export path has no focused coverage: the added service test replaces this method with a callback, so certificate filtering/selection and the actual PEM file contents are never exercised. Please add a NativeCertificateToolRunnerTests case that supplies a valid development certificate, exports to a temporary path, and verifies that the resulting PEM parses to that certificate.
    public string? ExportDevCertificatePublicPem(string outputPath)

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings August 12, 2026 22:58
@github-actions

Copy link
Copy Markdown
Contributor

Retrying the failed CI jobs for this pull request from the CI run attempt. The rerun is being tracked in the rerun attempt.

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

Copilot-Session: f0f2521e-b6ba-4441-9d8d-08483d863485
Copilot AI review requested due to automatic review settings August 13, 2026 01:25
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: f0f2521e-b6ba-4441-9d8d-08483d863485

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 24 out of 24 changed files in this pull request and generated no new comments.

Suppressed comments (2)

src/Aspire.Cli/Projects/GuestAppHostProject.cs:618

  • Enabling certificate bundles now makes a runtime's otherwise-valid Language identifier subject to an undocumented filename restriction. Replacing / handles the built-in ID, but ConfigureCertificateBundleEnvironmentAsync rejects other common identifier characters such as . or spaces, so a custom runtime like my.runtime will fail AppHost startup. Derive an always-safe cache prefix (for example, by hashing or fully sanitizing the language ID) rather than throwing for unrelated metadata.
                        _guestRuntime.Language.Replace('/', '-'),

src/Aspire.TypeSystem/ILanguageSupport.cs:28

  • This new interface member is not consumed by the runtime path: LanguageService serializes only GetRuntimeSpec(), and the CLI reads RuntimeSpec.CertificateBundleEnvironmentVariable. A custom language implementation that follows this property's documentation but does not duplicate the value into its returned RuntimeSpec will silently get no certificate configuration. Please remove this redundant member or make it authoritative when producing the runtime spec.
    string? CertificateBundleEnvironmentVariable => null;

@github-actions

This comment has been minimized.

@danegsta

Copy link
Copy Markdown
Member Author

PR Testing Report

PR Information

Artifact Version Verification

  • Expected commit: dfa8a62cba936210393dd2df254fd730b80a73a1
  • macOS CLI: 13.6.0-pr.15634.gdfa8a62c
  • Linux CLI: 13.6.0-pr.15634.gdfa8a62c
  • Status: Verified

Changes Analyzed

The PR changes TypeScript AppHost certificate handling in the CLI and TypeScript language support. It exports the trusted ASP.NET Core development certificate, combines it with an existing NODE_EXTRA_CA_CERTS bundle, caches the immutable result, and applies it only in run mode.

Test Scenarios Executed

macOS Redis TLS connection

Coverage: Happy path

Status: Passed

A fresh TypeScript AppHost used Redis over TLS with an existing custom CA bundle. A real Node Redis client connected through the generated rediss: connection and returned PONG with normal certificate validation.

Evidence:

  • tls-result.json: {"uriScheme":"rediss:","response":"PONG"}
  • run-foreground.log
  • tls-probe-resource.log

macOS existing CA preservation, permissions, and cache reuse

Coverage: Boundary

Status: Passed

The generated bundle retained the existing CA as its trailing bytes, contained both certificates, used mode 0600, and reused the same content-addressed path and modification timestamp across two launches.

Evidence:

  • run-ca-path.txt
  • run-ca-path-2.txt
  • inspect-ca.log
  • Bundle path: dev-certs/bundles/typescript-nodejs-096c7aa98315900b3f2fc08e6fca355c.pem

Missing existing bundle

Coverage: Unhappy path

Status: Passed

When NODE_EXTRA_CA_CERTS referenced a missing file, the CLI emitted the expected warning and preserved the configured path unchanged rather than silently replacing user configuration.

Evidence:

  • missing-run.log
  • missing-capture.txt

Publish-mode boundary

Coverage: Boundary

Status: Passed

aspire publish preserved the original NODE_EXTRA_CA_CERTS value and did not inject a development certificate bundle.

Evidence:

  • publish.log
  • publish-capture.txt

Linux TypeScript AppHost TLS validation

Coverage: Happy path and Linux partial-trust path

Status: Passed

After aspire certs trust returned the expected Linux partial-trust result, a fresh TypeScript AppHost connected to a local HTTPS server presenting that development certificate. Node returned HTTP 200 using the generated NODE_EXTRA_CA_CERTS bundle.

The bundle preserved /etc/ssl/certs/ca-certificates.crt, contained 122 certificates, and used mode 0600.

Evidence:

  • linux-https-result-1.json
  • linux-https-run-1.log
  • certs-trust.log
  • Result: {"statusCode":200,"existingCaPreserved":true,"certificateCount":122,"mode":384}

Linux cache reuse

Coverage: Boundary

Status: Passed

Two launches produced the same content-addressed bundle path and unchanged modification timestamp.

Evidence:

  • linux-https-bundle-1.txt
  • linux-https-bundle-2.txt
  • linux-https-result-2.json

Focused unit tests

Coverage: Regression

Status: Passed

CertificateServiceTests and GuestAppHostProjectTests completed with 65 passed, 0 failed, and 0 skipped.

Environment Limitation

The exact Redis TLS probe could not be repeated inside the Linux repo runner because DCP published the Redis port on the Docker Desktop host while the AppHost health check ran inside a sibling container. Redis remained waiting for health due to that runner network topology. The Linux test therefore used a local HTTPS server with the same development certificate, while the exact Redis TLS scenario was validated on macOS.

Summary

Scenario Status
Artifact identity Passed
macOS Redis TLS Passed
macOS CA preservation and cache Passed
Missing bundle safe fallback Passed
Publish boundary Passed
Linux development-certificate TLS Passed
Linux CA preservation and cache Passed
Focused unit tests Passed

Overall Result

PR verified. No product issues found.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Reviewed the TypeScript dev-cert trust flow end to end (language support -> RuntimeSpec -> RPC -> GuestRuntime -> run path), plus the cert selection code moved into X509Certificate2Extensions.

6 issues, none blocking on their own:

  • Correctness / durability (2): unguarded temp-file cleanup that can mask the real failure or discard a successful export; combined-bundle write is not flushed to disk while the cache is validated by file name only, so a truncated bundle can be reused forever.
  • Test coverage (1): nothing covers the run-path wiring or the actual reported scenario from #15489.
  • Maintainability / docs (3): two drifting copies of the restricted-permission atomic-write logic, WHY comments lost when the trust check moved to shared code, and the new public extension point doesn't document its trust scope.

Comment thread src/Aspire.Cli/Certificates/NativeCertificateToolRunner.cs Outdated
Comment thread src/Aspire.Cli/Projects/GuestAppHostProject.cs Outdated
Comment thread src/Aspire.Cli/Projects/GuestAppHostProject.cs
Comment thread src/Shared/X509Certificate2Extensions.cs
Comment thread src/Aspire.Cli/Projects/GuestAppHostProject.cs Outdated
Comment thread src/Aspire.TypeSystem/ILanguageSupport.cs
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: f0f2521e-b6ba-4441-9d8d-08483d863485
Copilot AI review requested due to automatic review settings August 13, 2026 19:44
@github-actions

Copy link
Copy Markdown
Contributor

Tests selector (audit mode)

The full test matrix and all jobs still run in audit mode. The tests and jobs below are what selective CI would run under enforcement.

100 / 100 test projects · 8 jobs, from 26 changed files.

Selected test projects (100 / 100)

Aspire.Acquisition.Tests, Aspire.Azure.AI.Inference.Tests, Aspire.Azure.AI.OpenAI.Tests, Aspire.Azure.Data.Tables.Tests, Aspire.Azure.Messaging.EventHubs.Tests, Aspire.Azure.Messaging.ServiceBus.Tests, Aspire.Azure.Messaging.WebPubSub.Tests, Aspire.Azure.Npgsql.EntityFrameworkCore.PostgreSQL.Tests, Aspire.Azure.Npgsql.Tests, Aspire.Azure.Search.Documents.Tests, Aspire.Azure.Security.KeyVault.Tests, Aspire.Azure.Storage.Blobs.Tests, Aspire.Azure.Storage.Files.DataLake.Tests, Aspire.Azure.Storage.Queues.Tests, Aspire.Cli.EndToEnd.Tests, Aspire.Cli.Tests, Aspire.Confluent.Kafka.Tests, Aspire.Dashboard.Components.Tests, Aspire.Dashboard.Tests, Aspire.Deployment.EndToEnd.Tests, Aspire.EndToEnd.Tests, Aspire.Hosting.Analyzers.Tests, Aspire.Hosting.Azure.Kubernetes.Tests, Aspire.Hosting.Azure.Kusto.Tests, Aspire.Hosting.Azure.Tests, Aspire.Hosting.Blazor.Tests, Aspire.Hosting.Browsers.Tests, Aspire.Hosting.CodeGeneration.Go.Tests, Aspire.Hosting.CodeGeneration.Java.Tests, Aspire.Hosting.CodeGeneration.Python.Tests, Aspire.Hosting.CodeGeneration.Rust.Tests, Aspire.Hosting.CodeGeneration.TypeScript.Tests, Aspire.Hosting.Containers.Tests, Aspire.Hosting.DevTunnels.Tests, Aspire.Hosting.Docker.Tests, Aspire.Hosting.Dotnet.Tests, Aspire.Hosting.DotnetTool.Tests, Aspire.Hosting.EntityFrameworkCore.Tests, Aspire.Hosting.Foundry.Tests, Aspire.Hosting.Garnet.Tests, Aspire.Hosting.GitHub.Models.Tests, Aspire.Hosting.Go.Tests, Aspire.Hosting.JavaScript.Tests, Aspire.Hosting.Kafka.Tests, Aspire.Hosting.Keycloak.Tests, Aspire.Hosting.Kubernetes.Tests, Aspire.Hosting.Maui.Tests, Aspire.Hosting.Milvus.Tests, Aspire.Hosting.MongoDB.Tests, Aspire.Hosting.MySql.Tests, Aspire.Hosting.Nats.Tests, Aspire.Hosting.OpenAI.Tests, Aspire.Hosting.Oracle.Tests, Aspire.Hosting.Orleans.Tests, Aspire.Hosting.PostgreSQL.Tests, Aspire.Hosting.Python.Tests, Aspire.Hosting.Qdrant.Tests, Aspire.Hosting.RabbitMQ.Tests, Aspire.Hosting.Radius.Tests, Aspire.Hosting.Redis.Tests, Aspire.Hosting.RemoteHost.Tests, Aspire.Hosting.Sdk.Tests, Aspire.Hosting.Seq.Tests, Aspire.Hosting.SqlServer.Tests, Aspire.Hosting.Testing.Tests, Aspire.Hosting.Tests, Aspire.Hosting.Valkey.Tests, Aspire.Hosting.Yarp.Tests, Aspire.Keycloak.Authentication.Tests, Aspire.Managed.Tests, Aspire.Microsoft.Azure.Cosmos.Tests, Aspire.Microsoft.Azure.StackExchangeRedis.Tests, Aspire.Microsoft.Data.SqlClient.Tests, Aspire.Microsoft.EntityFrameworkCore.Cosmos.Tests, Aspire.Microsoft.EntityFrameworkCore.SqlServer.Tests, Aspire.Microsoft.Extensions.Configuration.AzureAppConfiguration.Tests, Aspire.Milvus.Client.Tests, Aspire.MongoDB.Driver.Tests, Aspire.MongoDB.Driver.v2.Tests, Aspire.MongoDB.EntityFrameworkCore.Tests, Aspire.MySqlConnector.Tests, Aspire.NATS.Net.Tests, Aspire.Npgsql.EntityFrameworkCore.PostgreSQL.Tests, Aspire.Npgsql.Tests, Aspire.OpenAI.Tests, Aspire.Oracle.EntityFrameworkCore.Tests, Aspire.Playground.Tests, Aspire.Pomelo.EntityFrameworkCore.MySql.Tests, Aspire.Qdrant.Client.Tests, Aspire.RabbitMQ.Client.Tests, Aspire.RabbitMQ.Client.v6.Tests, Aspire.Seq.Tests, Aspire.StackExchange.Redis.DistributedCaching.Tests, Aspire.StackExchange.Redis.OutputCaching.Tests, Aspire.StackExchange.Redis.Tests, Aspire.Templates.Tests, Aspire.TerminalHost.Tests, ConfigurationSchemaGenerator.Tests, Infrastructure.Tests, QuarantineTools.Tests

Selected jobs (8)

cli-starter, deployment-e2e, extension-e2e, homebrew-installer, polyglot, typescript-api-compat, typescript-sdk, winget-installer


How these were chosen — grouped by what changed

⚠️ 89 of the 100 selected test projects come from a single change — src/Shared/X509Certificate2Extensions.cs.

🔧 src/Shared/X509Certificate2Extensions.cs (changed source)
89 via the project graph

show 89

Aspire.Acquisition.Tests, Aspire.Azure.AI.Inference.Tests, Aspire.Azure.AI.OpenAI.Tests, Aspire.Azure.Data.Tables.Tests, Aspire.Azure.Messaging.EventHubs.Tests, Aspire.Azure.Messaging.ServiceBus.Tests, Aspire.Azure.Messaging.WebPubSub.Tests, Aspire.Azure.Npgsql.EntityFrameworkCore.PostgreSQL.Tests, Aspire.Azure.Npgsql.Tests, Aspire.Azure.Search.Documents.Tests, Aspire.Azure.Security.KeyVault.Tests, Aspire.Azure.Storage.Blobs.Tests, Aspire.Azure.Storage.Files.DataLake.Tests, Aspire.Azure.Storage.Queues.Tests, Aspire.Confluent.Kafka.Tests, Aspire.Dashboard.Components.Tests, Aspire.Dashboard.Tests, Aspire.Deployment.EndToEnd.Tests, Aspire.EndToEnd.Tests, Aspire.Hosting.Analyzers.Tests, Aspire.Hosting.Azure.Kubernetes.Tests, Aspire.Hosting.Azure.Kusto.Tests, Aspire.Hosting.Blazor.Tests, Aspire.Hosting.Browsers.Tests, Aspire.Hosting.Containers.Tests, Aspire.Hosting.DevTunnels.Tests, Aspire.Hosting.Docker.Tests, Aspire.Hosting.Dotnet.Tests, Aspire.Hosting.DotnetTool.Tests, Aspire.Hosting.EntityFrameworkCore.Tests, Aspire.Hosting.Foundry.Tests, Aspire.Hosting.Garnet.Tests, Aspire.Hosting.GitHub.Models.Tests, Aspire.Hosting.Go.Tests, Aspire.Hosting.JavaScript.Tests, Aspire.Hosting.Kafka.Tests, Aspire.Hosting.Keycloak.Tests, Aspire.Hosting.Kubernetes.Tests, Aspire.Hosting.Milvus.Tests, Aspire.Hosting.MongoDB.Tests, Aspire.Hosting.MySql.Tests, Aspire.Hosting.Nats.Tests, Aspire.Hosting.OpenAI.Tests, Aspire.Hosting.Oracle.Tests, Aspire.Hosting.Orleans.Tests, Aspire.Hosting.PostgreSQL.Tests, Aspire.Hosting.Python.Tests, Aspire.Hosting.Qdrant.Tests, Aspire.Hosting.RabbitMQ.Tests, Aspire.Hosting.Radius.Tests, Aspire.Hosting.Redis.Tests, Aspire.Hosting.Sdk.Tests, Aspire.Hosting.Seq.Tests, Aspire.Hosting.SqlServer.Tests, Aspire.Hosting.Testing.Tests, Aspire.Hosting.Valkey.Tests, Aspire.Hosting.Yarp.Tests, Aspire.Keycloak.Authentication.Tests, Aspire.Managed.Tests, Aspire.Microsoft.Azure.Cosmos.Tests, Aspire.Microsoft.Azure.StackExchangeRedis.Tests, Aspire.Microsoft.Data.SqlClient.Tests, Aspire.Microsoft.EntityFrameworkCore.Cosmos.Tests, Aspire.Microsoft.EntityFrameworkCore.SqlServer.Tests, Aspire.Microsoft.Extensions.Configuration.AzureAppConfiguration.Tests, Aspire.Milvus.Client.Tests, Aspire.MongoDB.Driver.Tests, Aspire.MongoDB.Driver.v2.Tests, Aspire.MongoDB.EntityFrameworkCore.Tests, Aspire.MySqlConnector.Tests, Aspire.NATS.Net.Tests, Aspire.Npgsql.EntityFrameworkCore.PostgreSQL.Tests, Aspire.Npgsql.Tests, Aspire.OpenAI.Tests, Aspire.Oracle.EntityFrameworkCore.Tests, Aspire.Playground.Tests, Aspire.Pomelo.EntityFrameworkCore.MySql.Tests, Aspire.Qdrant.Client.Tests, Aspire.RabbitMQ.Client.Tests, Aspire.RabbitMQ.Client.v6.Tests, Aspire.Seq.Tests, Aspire.StackExchange.Redis.DistributedCaching.Tests, Aspire.StackExchange.Redis.OutputCaching.Tests, Aspire.StackExchange.Redis.Tests, Aspire.Templates.Tests, Aspire.TerminalHost.Tests, ConfigurationSchemaGenerator.Tests, Infrastructure.Tests, QuarantineTools.Tests

🔧 src/Aspire.Hosting/DeveloperCertificateService.cs (changed source)
6 via the project graph: Aspire.Hosting.Azure.Tests, Aspire.Hosting.CodeGeneration.Go.Tests, Aspire.Hosting.CodeGeneration.Java.Tests, Aspire.Hosting.CodeGeneration.Python.Tests, Aspire.Hosting.CodeGeneration.Rust.Tests, Aspire.Hosting.Maui.Tests

🔧 src/Aspire.Hosting.CodeGeneration.TypeScript/TypeScriptLanguageSupport.cs (changed source)
1 directly: Aspire.Hosting.CodeGeneration.TypeScript.Tests
1 via the project graph: Aspire.Hosting.RemoteHost.Tests

📦 affected project Aspire.Cli
1 test: Aspire.Cli.EndToEnd.Tests

📦 affected project Aspire.Hosting
1 test: Aspire.EndToEnd.Tests

🔧 src/Aspire.TypeSystem/ILanguageSupport.cs (changed source)
1 via the project graph: Aspire.Hosting.Tests

🧪 tests/Aspire.Cli.EndToEnd.Tests/TypeScriptEmptyAppHostTemplateTests.cs (changed test)
1 directly: Aspire.Cli.EndToEnd.Tests

🧪 tests/Aspire.Cli.Tests/Certificates/CertificateServiceTests.cs (changed test)
1 directly: Aspire.Cli.Tests

🧪 tests/Aspire.Cli.Tests/Certificates/NativeCertificateToolRunnerTests.cs (changed test)
1 directly: Aspire.Cli.Tests

🧪 tests/Aspire.Cli.Tests/Commands/CertificatesCommandTests.cs (changed test)
1 directly: Aspire.Cli.Tests

🧪 tests/Aspire.Cli.Tests/Commands/NewCommandTests.cs (changed test)
1 directly: Aspire.Cli.Tests

🧪 tests/Aspire.Cli.Tests/Commands/RunCommandTests.cs (changed test)
1 directly: Aspire.Cli.Tests

🧪 tests/Aspire.Cli.Tests/Projects/GuestAppHostProjectTests.cs (changed test)
1 directly: Aspire.Cli.Tests

🧪 tests/Aspire.Cli.Tests/Projects/TypeScriptAppHostToolchainResolverTests.cs (changed test)
1 directly: Aspire.Cli.Tests

🧪 tests/Aspire.Cli.Tests/Templating/DotNetTemplateFactoryTests.cs (changed test)
1 directly: Aspire.Cli.Tests

🧪 tests/Aspire.Cli.Tests/TestServices/TestCertificateService.cs (changed test)
1 directly: Aspire.Cli.Tests

🧪 tests/Aspire.Cli.Tests/TestServices/TestCertificateToolRunner.cs (changed test)
1 directly: Aspire.Cli.Tests

🧪 tests/Aspire.Cli.Tests/Utils/CliTestHelper.cs (changed test)
1 directly: Aspire.Cli.Tests

🧪 tests/Aspire.Cli.Tests/Utils/TestExecutionContextHelper.cs (changed test)
1 directly: Aspire.Cli.Tests

🧪 tests/Aspire.Hosting.CodeGeneration.TypeScript.Tests/TypeScriptLanguageSupportTests.cs (changed test)
1 directly: Aspire.Hosting.CodeGeneration.TypeScript.Tests

Job reasons

Job Triggered by
cli-starter • affected project Aspire.Cli
• selected test Aspire.Cli.Tests
• selected test Aspire.Acquisition.Tests
deployment-e2e affected project Aspire.Cli
extension-e2e src/Aspire.Cli/Certificates/CertificateCacheWriter.cs, src/Aspire.Cli/Certificates/CertificateService.cs, src/Aspire.Cli/Certificates/ICertificateToolRunner.cs, src/Aspire.Cli/Certificates/NativeCertificateToolRunner.cs, src/Aspire.Cli/Projects/GuestAppHostProject.cs, src/Aspire.Cli/Projects/GuestRuntime.cs, src/Aspire.Cli/Projects/TypeScriptAppHostToolchainResolver.cs, src/Aspire.Hosting.CodeGeneration.TypeScript/TypeScriptLanguageSupport.cs, src/Aspire.Hosting/DeveloperCertificateService.cs, tests/Aspire.Cli.EndToEnd.Tests/TypeScriptEmptyAppHostTemplateTests.cs, tests/Aspire.Cli.Tests/Certificates/CertificateServiceTests.cs, tests/Aspire.Cli.Tests/Certificates/NativeCertificateToolRunnerTests.cs, tests/Aspire.Cli.Tests/Commands/CertificatesCommandTests.cs, tests/Aspire.Cli.Tests/Commands/NewCommandTests.cs, tests/Aspire.Cli.Tests/Commands/RunCommandTests.cs, tests/Aspire.Cli.Tests/Projects/GuestAppHostProjectTests.cs, tests/Aspire.Cli.Tests/Projects/TypeScriptAppHostToolchainResolverTests.cs, tests/Aspire.Cli.Tests/Templating/DotNetTemplateFactoryTests.cs, tests/Aspire.Cli.Tests/TestServices/TestCertificateService.cs, tests/Aspire.Cli.Tests/TestServices/TestCertificateToolRunner.cs, tests/Aspire.Cli.Tests/Utils/CliTestHelper.cs, tests/Aspire.Cli.Tests/Utils/TestExecutionContextHelper.cs
• affected project Aspire.Cli
homebrew-installer selected test Aspire.Acquisition.Tests
polyglot affected project Aspire.Cli
typescript-api-compat affected project Aspire.Cli
typescript-sdk affected project Aspire.Hosting.CodeGeneration.TypeScript
winget-installer selected test Aspire.Acquisition.Tests

Selection computed for commit 59ee364.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 26 out of 26 changed files in this pull request and generated no new comments.

Suppressed comments (1)

src/Aspire.TypeSystem/ILanguageSupport.cs:31

  • Overriding this new public member does not actually opt a language into certificate injection. LanguageService.GetRuntimeSpec() returns only languageSupport.GetRuntimeSpec(), and the CLI reads RuntimeSpec.CertificateBundleEnvironmentVariable; TypeScript works only because its implementation manually copies this property into that separate object. A language provider following this property's documentation alone will see no effect. Please make one member the source of truth—either propagate this value into the returned runtime spec centrally or remove this interface member and direct providers to set the RuntimeSpec property.
    string? CertificateBundleEnvironmentVariable => null;

@github-actions

Copy link
Copy Markdown
Contributor

Retrying the failed CI jobs for this pull request from the CI run attempt. The rerun is being tracked in the rerun attempt.

@danegsta

Copy link
Copy Markdown
Member Author

PR Testing Report

PR Information

Artifact Version Verification

  • Expected commit: 59ee364e51cc42c9915ccf395609e043c85c1b1d
  • macOS CLI: 13.6.0-pr.15634.g59ee364e
  • Linux CLI: 13.6.0-pr.15634.g59ee364e
  • Status: Verified

Both isolated dogfood installations matched the latest PR head before behavioral testing began.

Changes Analyzed

The PR changes TypeScript AppHost certificate handling in the CLI and TypeScript language support. It exports the trusted ASP.NET Core development certificate, combines it with an existing NODE_EXTRA_CA_CERTS bundle, securely caches the immutable result, and applies the development certificate only in run mode.

The latest commit also centralizes certificate-cache writes, durably flushes and atomically publishes cache files, restricts Unix permissions, handles concurrent writers, preserves trust rationale documentation, and adds end-to-end coverage for the Node environment.

Change Categories

  • CLI certificate and TypeScript AppHost behavior
  • TypeScript language support
  • Unit and end-to-end tests
  • Dashboard
  • Hosting integration
  • Component
  • VS Code extension
  • CI infrastructure

Test Scenarios Executed

Scenario 1: macOS development-certificate TLS

Objective: Verify a fresh TypeScript AppHost can make a real Node HTTPS request to a server presenting the ASP.NET Core development certificate while preserving an existing CA.

Coverage type: Happy path

Status: Passed

Node returned HTTP 200 with normal certificate validation. The generated bundle contained the development certificate first and preserved the existing custom CA bytes unchanged.

Evidence:

  • scenario-tls/CertTrustMac/scenario-result-1.json
  • scenario-tls/CertTrustMac/run-1.log
  • Result: {"statusCode":200,"certificateCount":2,"mode":600,"directoryMode":700,"existingCaPreserved":true}

Scenario 2: Linux development-certificate TLS and partial trust

Objective: Verify the Linux partial-trust path and real Node TLS validation in the repo container runner.

Coverage type: Happy path and platform boundary

Status: Passed

aspire certs trust --non-interactive returned the expected partial-trust result. A fresh TypeScript AppHost then made a successful HTTPS request to a local server presenting that certificate. The generated bundle preserved /etc/ssl/certs/ca-certificates.crt.

Evidence:

  • scenario-tls/CertTrustLinux/scenario-result-1.json
  • Result: {"statusCode":200,"certificateCount":122,"mode":600,"directoryMode":700,"existingCaPreserved":true}

Scenario 3: Secure content-addressed cache and reuse

Objective: Verify restrictive permissions, atomic cache publication, temporary-file cleanup, and stable reuse across launches.

Coverage type: Boundary

Status: Passed on macOS and Linux

On both platforms, the bundle file used mode 0600, its cache directory used mode 0700, no temporary files remained, and a second launch reused the same content-addressed path and modification timestamp.

Evidence:

  • macOS: scenario-tls/CertTrustMac/scenario-result-2.json
  • Linux: scenario-tls/CertTrustLinux/scenario-result-2.json
  • macOS bundle: dev-certs/bundles/typescript-nodejs-449c0edb858042fcba0e88a0cfc6a18e.pem
  • Linux bundle: /workspace/.aspire/dev-certs/bundles/typescript-nodejs-a94241520cc09e6319bcbaed0a217947.pem

Scenario 4: Missing existing CA bundle

Objective: Verify a missing NODE_EXTRA_CA_CERTS file is handled safely without replacing the user's configured value.

Coverage type: Unhappy path

Status: Passed on macOS and Linux

Both platforms preserved missing-ca.pem unchanged and emitted the expected warning that the existing certificate bundle would be used unchanged.

Expected outcome: Preserve the original environment value, warn clearly, and avoid synthesizing an incomplete replacement bundle.

Evidence:

  • macOS: scenario-missing/MissingBundleMac/scenario-result.json
  • macOS: scenario-missing/MissingBundleMac/missing-run.log
  • Linux: scenario-missing/MissingBundleLinux/scenario-result.json
  • Linux: scenario-missing/MissingBundleLinux/missing-run.log
  • Result: {"preserved":"missing-ca.pem","warning":true}

Scenario 5: Publish-mode boundary

Objective: Verify publish mode does not inject a development-certificate bundle or modify the existing Node CA setting.

Coverage type: Boundary

Status: Passed on macOS and Linux

aspire publish preserved publish-sentinel.pem exactly and completed without the run-mode bundle-generation warning.

Evidence:

  • macOS: scenario-publish/PublishBoundaryMac/scenario-result.json
  • macOS: scenario-publish/PublishBoundaryMac/publish.log
  • Linux: scenario-publish/PublishBoundaryLinux/scenario-result.json
  • Linux: scenario-publish/PublishBoundaryLinux/publish.log
  • Result: {"preserved":"publish-sentinel.pem","bundleGenerationWarning":false}

Scenario 6: Focused repository tests

Objective: Run the affected certificate-service, native certificate-tool, and GuestAppHost project test classes against the latest source.

Coverage type: Regression

Status: Passed

CertificateServiceTests, NativeCertificateToolRunnerTests, and GuestAppHostProjectTests completed with 70 passed, 0 failed, and 0 skipped.

Environment Observation

On macOS, detached aspire start exited with code 255 before the guest AppHost launched. Foreground aspire run completed the same real TLS scenario successfully, and the detached child log showed no certificate failure. This matches the detached-start behavior observed before the latest PR changes and is not classified as a PR defect.

The Linux runner used a temporary Node-enabled image because the repository runner image does not include Node. Public NuGet and npm feeds were not used; generated projects used the approved internal feeds.

Summary

Scenario macOS Linux
PR artifact identity Passed Passed
Real Node TLS with development certificate Passed Passed
Existing CA preservation Passed Passed
0600 file and 0700 directory permissions Passed Passed
Cache path and timestamp reuse Passed Passed
Temporary-file cleanup Passed Passed
Missing existing bundle fallback Passed Passed
Publish-mode boundary Passed Passed
Focused repository tests Passed N/A

Overall Result

PR verified. No product issues found.

@danegsta
David Negstad (danegsta) merged commit 9ebf5e5 into main Aug 13, 2026
724 of 727 checks passed
@danegsta
David Negstad (danegsta) deleted the danegsta/tsCerts branch August 13, 2026 20:39
@github-actions github-actions Bot added this to the 13.6 milestone Aug 13, 2026
@aspire-repo-bot

Copy link
Copy Markdown
Contributor

Pull request created: #1493

Generated by PR Documentation Check · auto · 55 AIC · ⌖ 5.96 AIC · ⊞ 19.6K

@aspire-repo-bot

Copy link
Copy Markdown
Contributor

📝 Documentation has been drafted in microsoft/aspire.dev#1493 targeting release/13.5.

Added a new "Trusting the certificate for outbound TLS connections" subsection to the TypeScript AppHost docs page, explaining the CLI's content-addressed PEM cache under ASPIRE_HOME, how NODE_EXTRA_CA_CERTS is set/preserved, and a cross-reference to the Certificate configuration page.

  • Modified: src/frontend/src/content/docs/app-host/typescript-apphost.mdx

Note

This draft PR needs human review before merging.

@danegsta

Copy link
Copy Markdown
Member Author

/backport to release/13.5

@github-actions

Copy link
Copy Markdown
Contributor

Started backporting to release/13.5 (link to workflow run)

@aspire-repo-bot

Copy link
Copy Markdown
Contributor

David Negstad (@danegsta) backporting to release/13.5 failed, the patch most likely resulted in conflicts. Please backport manually!

git am output
$ git am --3way --empty=keep --ignore-whitespace --keep-non-patch changes.patch

Applying: Build a PEM bundle for the dev cert
Using index info to reconstruct a base tree...
M	src/Aspire.Cli/Certificates/CertificateService.cs
M	src/Aspire.Cli/Certificates/ICertificateToolRunner.cs
M	src/Aspire.Cli/Certificates/NativeCertificateToolRunner.cs
M	src/Aspire.Cli/Projects/GuestAppHostProject.cs
M	src/Aspire.Hosting.CodeGeneration.TypeScript/TypeScriptLanguageSupport.cs
M	tests/Aspire.Cli.Tests/Certificates/CertificateServiceTests.cs
M	tests/Aspire.Cli.Tests/TestServices/TestCertificateService.cs
M	tests/Aspire.Cli.Tests/TestServices/TestCertificateToolRunner.cs
M	tests/Aspire.Cli.Tests/Utils/CliTestHelper.cs
Falling back to patching base and 3-way merge...
Auto-merging src/Aspire.Cli/Certificates/CertificateService.cs
CONFLICT (content): Merge conflict in src/Aspire.Cli/Certificates/CertificateService.cs
Auto-merging src/Aspire.Cli/Certificates/ICertificateToolRunner.cs
Auto-merging src/Aspire.Cli/Certificates/NativeCertificateToolRunner.cs
Auto-merging src/Aspire.Cli/Projects/GuestAppHostProject.cs
CONFLICT (content): Merge conflict in src/Aspire.Cli/Projects/GuestAppHostProject.cs
Auto-merging src/Aspire.Hosting.CodeGeneration.TypeScript/TypeScriptLanguageSupport.cs
CONFLICT (content): Merge conflict in src/Aspire.Hosting.CodeGeneration.TypeScript/TypeScriptLanguageSupport.cs
Auto-merging tests/Aspire.Cli.Tests/Certificates/CertificateServiceTests.cs
CONFLICT (content): Merge conflict in tests/Aspire.Cli.Tests/Certificates/CertificateServiceTests.cs
Auto-merging tests/Aspire.Cli.Tests/TestServices/TestCertificateService.cs
CONFLICT (content): Merge conflict in tests/Aspire.Cli.Tests/TestServices/TestCertificateService.cs
Auto-merging tests/Aspire.Cli.Tests/TestServices/TestCertificateToolRunner.cs
Auto-merging tests/Aspire.Cli.Tests/Utils/CliTestHelper.cs
CONFLICT (content): Merge conflict in tests/Aspire.Cli.Tests/Utils/CliTestHelper.cs
error: Failed to merge in the changes.
hint: Use 'git am --show-current-patch=diff' to see the failed patch
hint: When you have resolved this problem, run "git am --continue".
hint: If you prefer to skip this patch, run "git am --skip" instead.
hint: To restore the original branch and stop patching, run "git am --abort".
hint: Disable this message with "git config set advice.mergeConflict false"
Patch failed at 0001 Build a PEM bundle for the dev cert
Error: The process '/usr/bin/git' failed with exit code 128

Link to workflow output

Jose Perez Rodriguez (joperezr) pushed a commit that referenced this pull request Aug 13, 2026
* Build a PEM bundle for the dev cert

* Use response from cert bundle directly

* Combine existing Node CA certificate bundles



* Refine TypeScript development certificate handling





* Handle duplicate certificate environment aliases





* Clarify certificate trust scope





* Handle case-sensitive certificate bundle paths





* Use standard Windows path comparison





* Address certificate bundle review feedback





---------

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: f0f2521e-b6ba-4441-9d8d-08483d863485
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

TypeScript AppHost Node process does not trust dev certs

7 participants