Skip to content

Accept dev localhost resource service URLs#17639

Merged
danegsta merged 2 commits into
mainfrom
danegsta/dev-localhost-urls
May 29, 2026
Merged

Accept dev localhost resource service URLs#17639
danegsta merged 2 commits into
mainfrom
danegsta/dev-localhost-urls

Conversation

@danegsta
Copy link
Copy Markdown
Member

Description

aspire run fails for polyglot AppHosts scaffolded with *.dev.localhost URLs because the AppHost resource service endpoint validation only accepted Uri.IsLoopback, which does not recognize RFC-reserved localhost subdomains.

This updates the resource service endpoint check to treat localhost, loopback IPs, and .localhost subdomains as local while still rejecting arbitrary DNS names and lookalikes. Kestrel continues to bind the resource service with ListenLocalhost, so this relaxes configuration validation without exposing a new network listener.

User-facing usage

Users can scaffold and run a polyglot AppHost with *.dev.localhost URLs:

aspire new
# Select Starter App (Express/React, TypeScript AppHost)
# Choose Use *.dev.localhost URLs
aspire run

Security considerations

This change relies on RFC 6761's reservation of localhost and names below it for loopback use. The resource service still binds with ListenLocalhost, and non-.localhost hostnames remain rejected.

Validation:

  • dotnet build tests/Aspire.Cli.EndToEnd.Tests/Aspire.Cli.EndToEnd.Tests.csproj
  • dotnet test --project tests/Aspire.Hosting.Tests/Aspire.Hosting.Tests.csproj --no-launch-profile -- --filter-class "*.DashboardServiceHostTests" --filter-not-trait "quarantined=true" --filter-not-trait "outerloop=true"
  • ./localhive.sh -o /tmp/aspire-e2e-devlocalhost -r linux-arm64 --archive
  • ASPIRE_E2E_ARCHIVE=/tmp/aspire-e2e-devlocalhost.tar.gz dotnet test --project tests/Aspire.Cli.EndToEnd.Tests/Aspire.Cli.EndToEnd.Tests.csproj --no-launch-profile -- --filter-method "*.CreateAndRunPolyglotAppHostWithDevLocalhostUrls" --filter-not-trait "quarantined=true" --filter-not-trait "outerloop=true"

Fixes # (issue)

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

Allow DashboardServiceHost to treat RFC-reserved .localhost subdomains as local resource service endpoints so polyglot AppHosts scaffolded with *.dev.localhost can run.

Add unit coverage for local endpoint recognition and a CLI E2E regression test that scaffolds and runs an Express/React polyglot AppHost with dev-localhost URLs.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 29, 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/microsoft/aspire/main/eng/scripts/get-aspire-cli-pr.sh | bash -s -- 17639

Or

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

@davidfowl davidfowl added this to the 13.4 milestone May 29, 2026
@danegsta
Copy link
Copy Markdown
Member Author

PR Testing Report

PR Information

CLI Version Verification

  • Expected Commit: 73bace6
  • Installed Version: 13.5.0-pr.17639.g73bace6e
  • Status: ✅ Verified

Changes Analyzed

Files Changed

  • src/Aspire.Hosting/Dashboard/DashboardServiceHost.cs - Modified
  • tests/Aspire.Hosting.Tests/Dashboard/DashboardServiceHostTests.cs - Modified
  • tests/Aspire.Cli.EndToEnd.Tests/SmokeTests.cs - Modified

Change Categories

  • CLI changes detected
  • Hosting changes detected
  • Dashboard changes detected
  • Template changes detected
  • Client/Component changes detected
  • Test changes detected

Test Scenarios Executed

Scenario 1: Express/React polyglot AppHost with *.dev.localhost

Objective: Verify a real TypeScript polyglot AppHost scaffolded with --localhost-tld true reaches the normal aspire run running state instead of failing resource service URL validation.
Coverage Type: Happy path
Status: ✅ Passed

Steps:

  1. Installed the PR dogfood CLI.
  2. Installed Node/npm in the disposable test container because the TypeScript AppHost requires npm.
  3. Created aspire-ts-starter with --localhost-tld true, --source, --version, --suppress-agent-init, and --non-interactive.
  4. Verified aspire.config.json contains ASPIRE_RESOURCE_SERVICE_ENDPOINT_URL and polyglotdevlocalhost.dev.localhost.
  5. Ran aspire run --non-interactive and verified the normal Ctrl+C running message and dashboard URL were emitted.

Evidence:

  • New output: /workspace/evidence/happy/new-output.txt
  • Resource service URL: /workspace/evidence/happy/resource-service-url.txt
  • Run output: /workspace/evidence/happy/run-output.txt
  • Result marker: ready

Observations:

  • The AppHost reached the normal running state with the PR CLI.
  • The dashboard URL used the generated .dev.localhost hostname.

Scenario 2: Non-local resource service URL remains rejected

Objective: Verify the validation is not relaxed to arbitrary DNS names.
Coverage Type: Unhappy path
Status: ✅ Passed

Steps:

  1. Created a fresh aspire-ts-starter project with --localhost-tld true.
  2. Rewrote the configured resource service host from polyglotinvalidhost.dev.localhost to example.com.
  3. Ran aspire run --non-interactive and verified startup failed with the local loopback validation error.

Evidence:

  • Original config: /workspace/evidence/negative/aspire.config.before.json
  • Modified config: /workspace/evidence/negative/aspire.config.after.json
  • Run output: /workspace/evidence/negative/run-output.txt
  • Result marker: loopback-error

Expected Unhappy-Path Outcome: A clear validation failure and no successful AppHost running state.

Summary

Scenario Status Notes
Express/React polyglot AppHost with *.dev.localhost ✅ Passed Reached normal aspire run running state.
Non-local resource service URL remains rejected ✅ Passed Rejected example.com resource service URL.

Overall Result

✅ PR VERIFIED

Recommendations

  • No follow-up testing recommendations from this run.

@danegsta danegsta marked this pull request as ready for review May 29, 2026 01:44
Copilot AI review requested due to automatic review settings May 29, 2026 01:44
Copy link
Copy Markdown
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

This PR fixes aspire run failing for polyglot AppHosts that scaffold *.dev.localhost resource service URLs by relaxing the AppHost-side “resource service endpoint must be local” validation to treat RFC 6761 localhost subdomains (and localhost.) as local endpoints, while still rejecting non-local DNS hostnames. This aligns validation with the fact that Kestrel still binds the resource service using ListenLocalhost, so the change relaxes configuration checks without broadening the listener scope.

Changes:

  • Updated DashboardServiceHost endpoint validation to accept .localhost subdomains (and trailing-dot FQDN forms) as local resource service endpoints.
  • Added unit test coverage for the local-endpoint classification logic (including .localhost subdomains and common lookalikes).
  • Added an end-to-end CLI smoke test ensuring a polyglot template using *.dev.localhost can be created and run successfully.

Reviewed changes

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

File Description
src/Aspire.Hosting/Dashboard/DashboardServiceHost.cs Replaces Uri.IsLoopback-only check with IsLocalResourceServiceEndpoint that also accepts .localhost subdomains and localhost..
tests/Aspire.Hosting.Tests/Dashboard/DashboardServiceHostTests.cs Adds coverage for .localhost subdomain acceptance and non-local hostname rejection.
tests/Aspire.Cli.EndToEnd.Tests/SmokeTests.cs Adds E2E regression test for aspire new + aspire run with useDevLocalhost polyglot template setting.

@danegsta
Copy link
Copy Markdown
Member Author

/backport to release/13.4

@github-actions
Copy link
Copy Markdown
Contributor

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

@github-actions
Copy link
Copy Markdown
Contributor

CLI E2E Tests unknown — 108 passed, 0 failed, 2 unknown (commit 73bace6)

View all recordings
Status Test Recording Job Artifacts
AddPackageInteractiveWhileAppHostRunningDetached Recording #78422863653 Logs
AddPackageWhileAppHostRunningDetached Recording #78422863653 Logs
AgentCommands_AllHelpOutputs_AreCorrect Recording #78422864233 Logs
AgentInitCommand_DefaultSelection_InstallsDefaultSkills Recording #78422864233 Logs
AgentInitCommand_MigratesDeprecatedConfig Recording #78422864233 Logs
AgentMcpListStructuredLogsReturnsLogsFromStarterApp Recording #78422864335 Logs
AgentMcpListStructuredLogsReturnsLogsFromStarterApp_DevLocalhost Recording #78422864335 Logs
AgentMcpListStructuredLogsReturnsLogsFromStarterApp_Isolated Recording #78422864335 Logs
AllPublishMethodsBuildDockerImages Recording #78422864356 Logs
AspireAddAndStartWorkAgainstLegacyAppHostTs Recording #78422864428 Logs
AspireAddPackageVersionToDirectoryPackagesProps Recording #78422864065 Logs
AspireInitSingleFileAppHostRunsViaDotnetRunAppHost Recording #78422864091 Logs
AspireInitWithExistingAppHostDirRecreatesMissingNuGetConfigAndPreservesFiles Recording #78422864098 Logs
AspireInitWithSolutionFileGeneratesAppHostThatBuildsAgainstChannelHive Recording #78422864098 Logs
AspireStartUpdatesStaleTypeScriptAppHostPath Recording #78422864110 Logs
AspireUpdateRemovesAppHostPackageVersionFromDirectoryPackagesProps Recording #78422864065 Logs
AspireUpdateRemovesOrphanAppHostPackageVersionWhenSdkAlreadyCurrent Recording #78422864065 Logs
Banner_DisplayedOnFirstRun Recording #78422864111 Logs
Banner_DisplayedWithExplicitFlag Recording #78422864111 Logs
Banner_NotDisplayedWithNoLogoFlag Recording #78422864111 Logs
CertificatesClean_RemovesCertificates Recording #78422864031 Logs
CertificatesTrust_WithNoCert_CreatesAndTrustsCertificate Recording #78422864031 Logs
CertificatesTrust_WithUntrustedCert_TrustsCertificate Recording #78422864031 Logs
ConfigSetGet_CreatesNestedJsonFormat Recording #78422863502 Logs
CreateAndRunAspireStarterProject Recording #78422863705 Logs
CreateAndRunAspireStarterProjectWithBundle Recording #78422863698 Logs
CreateAndRunEmptyAppHostProject Recording #78422863825 Logs
CreateAndRunJavaEmptyAppHostProject Recording #78422864182 Logs
CreateAndRunJsReactProject Recording #78422864081 Logs
CreateAndRunPolyglotAppHostWithDevLocalhostUrls Recording #78422863705 Logs
CreateAndRunPythonReactProject Recording #78422864529 Logs
CreateAndRunTypeScriptEmptyAppHostProject Recording #78422864105 Logs
CreateAndRunTypeScriptStarterProject Recording #78422863550 Logs
CreateJavaAppHostWithViteApp Recording #78422864119 Logs
CreateTypeScriptAppHostWithViteApp_AllowsGuestAppPackageManagerToDiffer Recording #78422864176 Logs
CreateTypeScriptAppHostWithViteApp_UsesConfiguredToolchain Recording #78422864176 Logs
DashboardRunWithAgentMcpListTracesReturnsNoTraces Recording #78422864225 Logs
DashboardRunWithAgentMcpListTracesReturnsNoTraces_DevLocalhost Recording #78422864225 Logs
DashboardRunWithOtelTracesReturnsNoTraces Recording #78422864225 Logs
DashboardRunWithOtelTracesReturnsNoTraces_DevLocalhost Recording #78422864225 Logs
DeployK8sBasicApiService Recording #78422864159 Logs
DeployK8sWithExternalHelmChart Recording #78422864232 Logs
DeployK8sWithGarnet Recording #78422864423 Logs
DeployK8sWithMongoDB Recording #78422864493 Logs
DeployK8sWithMySql Recording #78422863838 Logs
DeployK8sWithPostgres Recording #78422864166 Logs
DeployK8sWithRabbitMQ Recording #78422864372 Logs
DeployK8sWithRedis Recording #78422864102 Logs
DeployK8sWithSqlServer Recording #78422864513 Logs
DeployK8sWithValkey Recording #78422864498 Logs
DeployTypeScriptAppToKubernetes Recording #78422864319 Logs
DescribeCommandResolvesReplicaNames Recording #78422863562 Logs
DescribeCommandShowsRunningResources Recording #78422863562 Logs
DetachFormatJsonProducesValidJson Recording #78422864531 Logs
DetachFormatJsonProducesValidJsonWhenRestartingExistingInstance Recording #78422864531 Logs
DoPublishAndDeployListStepsWork Recording #78422864108 Logs
DocsCommand_RendersInteractiveMarkdownFromLocalSource Recording #78422863834 Logs
DoctorCommand_DetectsDeprecatedAgentConfig Recording #78422864233 Logs
DoctorCommand_TypeScriptAppHostReportsMissingConfiguredToolchain Recording #78422863775 Logs
DoctorCommand_WithSslCertDir_ShowsTrusted Recording #78422863775 Logs
DoctorCommand_WithoutSslCertDir_ShowsPartiallyTrusted Recording #78422863775 Logs
GatewayWithoutExternalEndpoint_FailsPublishWithGuidance Recording #78422864125 Logs
GeneratedAspireDevScript_StartsWatchMode_WithConfiguredToolchain Recording #78422864176 Logs
GlobalMigration_HandlesCommentsAndTrailingCommas Recording #78422863502 Logs
GlobalMigration_HandlesMalformedLegacyJson Recording #78422863502 Logs
GlobalMigration_PreservesAllValueTypes Recording #78422863502 Logs
GlobalMigration_SkipsWhenNewConfigExists Recording #78422863502 Logs
GlobalSettings_MigratedFromLegacyFormat Recording #78422863502 Logs
IngressWithoutExternalEndpoint_FailsPublishWithGuidance Recording #78422864125 Logs
InitTypeScriptAppHost_AugmentsExistingViteRepoInWorkspaceSubdirectory Recording #78422864176 Logs
InteractiveCSharpInitCreatesExpectedFiles Recording #78422863916 Logs
InvalidAppHostPathWithComments_IsHealedOnRun Recording #78422864349 Logs
JavaScriptHostingApisRunFromTypeScriptAppHost Recording #78422864356 Logs
LatestCliCanStartStableChannelAppHost Recording #78422863705 Logs
LatestCliCanStartStableChannelTypeScriptAppHost Recording #78422863705 Logs
LegacySettingsMigration_AdjustsRelativeAppHostPath Recording #78422864110 Logs
LogsCommandShowsResourceLogs Recording #78422864383 Logs
OtelLogsReturnsStructuredLogsFromStarterApp Recording #78422864121 Logs
OtelLogsReturnsStructuredLogsFromStarterAppIsolated Recording #78422864121 Logs
PsCommandListsRunningAppHost Recording #78422863846 Logs
PsFormatJsonOutputsOnlyJsonToStdout Recording #78422863846 Logs
PublishJavaScriptPatternsGeneratesExpectedDockerComposeArtifacts Recording #78422864257 Logs
PublishWithConfigureEnvFileUpdatesEnvOutput Recording #78422864257 Logs
PublishWithDockerComposeServiceCallbackSucceeds Recording #78422864257 Logs
PublishWithoutOutputPathUsesAppHostDirectoryDefault Recording #78422864257 Logs
ResourceCommand_FailedExecution_DisplaysAppHostLogPathAndLogContainsEntries Recording #78422864206 Logs
ResourceCommand_SetAndDeleteParameterUpdatesDescribeOutput Recording #78422864206 Logs
RestoreGeneratesSdkFiles Recording #78422863870 Logs
RestoreGeneratesSdkFiles_WithConfiguredToolchain Recording #78422864326 Logs
RestoreRefreshesGeneratedSdkAfterAddingIntegration Recording #78422864326 Logs
RestoreSupportsConfigOnlyHelperPackageAndCrossPackageTypes Recording #78422863853 Logs
RunFromParentDirectory_UsesExistingConfigNearAppHost Recording #78422863770 Logs
RunReportsSyntaxErrorsForDotNetAppHost Recording #78422863897 Logs
RunReportsSyntaxErrorsForTypeScriptAppHost Recording #78422863897 Logs
SecretCrudOnDotNetAppHost Recording #78422864499 Logs
SecretCrudOnTypeScriptAppHost Recording #78422863569 Logs
StagingChannel_ConfigureAndVerifySettings_ThenSwitchChannels Recording #78422864333 Logs
StartAndWaitForTypeScriptSqlServerAppHostWithNativeAssets Recording #78422864150 Logs
StartReportsSyntaxErrorsForDotNetAppHost Recording #78422863897 Logs
StartReportsSyntaxErrorsForTypeScriptAppHost Recording #78422863897 Logs
StopAllAppHostsFromAppHostDirectory Recording #78422864247 Logs
StopJavaPolyglotAppHostUsingApphostDirectory Recording #78422863906 Logs
StopNonInteractiveSingleAppHost Recording #78422864247 Logs
StopTypeScriptPolyglotAppHostUsingApphostDirectory Recording #78422864132 Logs
StopWithNoRunningAppHostExitsSuccessfully Recording #78422863653 Logs
UnAwaitedChainsCompileWithAutoResolvePromises Recording #78422864326 Logs
UpdateProjectChannelToStable_CSharpEmptyAppHost_PreservesAspireConfigChannel Recording #78422863771 Logs
UpdateProjectChannelToStable_CSharpSingleFileInit_PreservesAspireConfigChannel Recording #78422863771 Logs
UpdateProjectChannelToStable_TypeScriptSingleFileInit_PreservesAspireConfigChannel Recording #78422863771 Logs
UpdateProjectChannelToStable_TypeScript_PreviewsStablePackagesAndPreservesChannel Recording #78422863771 Logs

📹 Recordings uploaded automatically from CI run #26611998521

Comment on lines +191 to +200
var host = uri.Host.EndsWith(".", StringComparison.Ordinal)
? uri.Host[..^1]
: uri.Host;

// RFC 6761 reserves "localhost." and names under it for loopback resolution:
// https://www.rfc-editor.org/rfc/rfc6761#section-6.3. Uri.IsLoopback only
// recognizes "localhost" itself, but Aspire-generated polyglot AppHosts can use
// scoped names like "myapp.dev.localhost".
return string.Equals(host, KnownHostNames.Localhost, StringComparison.OrdinalIgnoreCase)
|| host.EndsWith(".localhost", StringComparison.OrdinalIgnoreCase);
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.

There are existing helper methods for testing whether a host is localhost or dev.localhost, etc on EndpointHostHelpers

const string projectName = "PolyglotDevLocalhost";
await auto.AspireNewAsync(projectName, counter, template: AspireTemplate.ExpressReact, useDevLocalhost: true);

await auto.RunCommandFailFastAsync($"cd {projectName}", counter);
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.

Need to rebase. This has been replaced with RunCommandAsync

Copy link
Copy Markdown
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.

LGTM. The validation logic correctly handles RFC 6761 .localhost subdomains and trailing-dot edge cases, and Kestrel still binds only to loopback — no new network surface exposed. Tests have good positive/negative coverage.

@danegsta
Copy link
Copy Markdown
Member Author

@copilot follow up on fixes for the PR comments

Co-authored-by: danegsta <50252651+danegsta@users.noreply.github.com>
Copy link
Copy Markdown
Contributor

Copilot AI commented May 29, 2026

@copilot follow up on fixes for the PR comments

Fixed in 5fa2946. DashboardServiceHost now uses EndpointHostHelpers.IsLocalhostOrLocalhostTld(...) for the shared localhost check, and the new smoke test was updated to RunCommandAsync. I also re-ran the targeted DashboardServiceHostTests and rebuilt Aspire.Cli.EndToEnd.Tests.

@danegsta
Copy link
Copy Markdown
Member Author

/backport to release/13.4

@danegsta danegsta enabled auto-merge (squash) May 29, 2026 04:33
@github-actions
Copy link
Copy Markdown
Contributor

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

@danegsta danegsta merged commit 7dd56df into main May 29, 2026
311 checks passed
@danegsta danegsta deleted the danegsta/dev-localhost-urls branch May 29, 2026 05:06
@microsoft-github-policy-service microsoft-github-policy-service Bot modified the milestones: 13.4, 13.5 May 29, 2026
aspire-repo-bot Bot added a commit to microsoft/aspire.dev that referenced this pull request May 29, 2026
Documents the fix from microsoft/aspire#17639 where aspire run now
correctly accepts *.localhost subdomains (per RFC 6761) as local
resource service endpoints, enabling polyglot AppHosts scaffolded
with *.dev.localhost URLs to run without error.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@aspire-repo-bot
Copy link
Copy Markdown
Contributor

Pull request created: #1120

Generated by PR Documentation Check

@aspire-repo-bot
Copy link
Copy Markdown
Contributor

📝 Documentation has been drafted in microsoft/aspire.dev#1120 targeting release/13.4.

Updated src/frontend/src/content/docs/reference/cli/commands/aspire-new.mdx to document that *.dev.localhost URLs now work correctly for polyglot AppHosts (e.g. TypeScript/Express/React) when running aspire run. The new subsection explains the RFC 6761 *.localhost local-endpoint recognition, confirms the resource service still binds via ListenLocalhost, and provides a migration note for users who previously saw the "ASPIRE_RESOURCE_SERVICE_ENDPOINT_URL must contain a local loopback address" error.

Note

This draft PR needs human review before merging.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants