aspire run crashes with Null character in path when aspire.config.json records an invalid appHost.path. #17624 fixed this for the read path, but the write path in ProjectLocator.CreateSettingsFileAsync resolves the same value without the guard that fix added.
Repro
Two AppHosts in a workspace, and an aspire.config.json whose appHost.path contains a NUL byte (legal in JSON, so it survives parsing):
{"appHost":{"path":"First\u0000AppHost.csproj"}}
$ aspire run --apphost SecondAppHost/SecondAppHost.csproj
❌ An unexpected error occurred: Null character in path. (Parameter 'path')
Reproduced on main (a50e553a8c) with a locally built CLI. No environment variables set. Any character in Path.GetInvalidPathChars() should behave the same.
Cause
The canonical readers validate before resolving. GetAppHostProjectFileFromSettingsAsync calls IsValidConfiguredAppHostPath first (ProjectLocator.cs:658 and :715), which rejects '\0' and Path.GetInvalidPathChars() precisely because Path.Combine / Path.GetFullPath throw ArgumentException on them. The comment at ProjectLocator.cs:653-657 cites #17624.
The upward config search in CreateSettingsFileAsync resolves the recorded path with no such guard:
var resolvedPath = Path.GetFullPath(
Path.IsPathRooted(existingPath) ? existingPath : Path.Combine(configDir, existingPath));
ProjectLocator.cs:1124-1125 (line numbers as of 60e8e122f2). The ArgumentException escapes as a generic "An unexpected error occurred", which is the same symptom #17624 was filed for.
An explicit --apphost never runs the settings reader, so nothing validates the recorded string before this point.
Why this needs a design decision, not just a guard
IsValidConfiguredAppHostPath takes a silent flag and either displays an error or logs a warning. CreateSettingsFileAsync has no such flag, and it is a write path, so fixing it means deciding what should happen when a write encounters a malformed recorded value — fail, warn and overwrite, or silently overwrite. That choice is user-visible and deserves its own tests.
Notes
Found while working on #19080 (PR #19126). That PR does not touch this code path and adds no new unguarded resolution; it is flagged there so the crash is not misread as a regression. Filing separately because #17624 already missed this spot once, and a PR description stops being discoverable after merge.
aspire runcrashes withNull character in pathwhenaspire.config.jsonrecords an invalidappHost.path. #17624 fixed this for the read path, but the write path inProjectLocator.CreateSettingsFileAsyncresolves the same value without the guard that fix added.Repro
Two AppHosts in a workspace, and an
aspire.config.jsonwhoseappHost.pathcontains a NUL byte (legal in JSON, so it survives parsing):{"appHost":{"path":"First\u0000AppHost.csproj"}}Reproduced on
main(a50e553a8c) with a locally built CLI. No environment variables set. Any character inPath.GetInvalidPathChars()should behave the same.Cause
The canonical readers validate before resolving.
GetAppHostProjectFileFromSettingsAsynccallsIsValidConfiguredAppHostPathfirst (ProjectLocator.cs:658and:715), which rejects'\0'andPath.GetInvalidPathChars()precisely becausePath.Combine/Path.GetFullPaththrowArgumentExceptionon them. The comment atProjectLocator.cs:653-657cites #17624.The upward config search in
CreateSettingsFileAsyncresolves the recorded path with no such guard:ProjectLocator.cs:1124-1125(line numbers as of60e8e122f2). TheArgumentExceptionescapes as a generic "An unexpected error occurred", which is the same symptom #17624 was filed for.An explicit
--apphostnever runs the settings reader, so nothing validates the recorded string before this point.Why this needs a design decision, not just a guard
IsValidConfiguredAppHostPathtakes asilentflag and either displays an error or logs a warning.CreateSettingsFileAsynchas no such flag, and it is a write path, so fixing it means deciding what should happen when a write encounters a malformed recorded value — fail, warn and overwrite, or silently overwrite. That choice is user-visible and deserves its own tests.Notes
Found while working on #19080 (PR #19126). That PR does not touch this code path and adds no new unguarded resolution; it is flagged there so the crash is not misread as a regression. Filing separately because #17624 already missed this spot once, and a PR description stops being discoverable after merge.