Skip to content

Redact owning resource's own secret env var in describe - #19248

Merged
David Pine (IEvangelist) merged 8 commits into
mainfrom
dapine/fix-describe-owning-secret-leak
Aug 14, 2026
Merged

Redact owning resource's own secret env var in describe#19248
David Pine (IEvangelist) merged 8 commits into
mainfrom
dapine/fix-describe-owning-secret-leak

Conversation

@IEvangelist

@IEvangelist David Pine (IEvangelist) commented Aug 11, 2026

Copy link
Copy Markdown
Member

Description

aspire describe --format json redacted a generated secret parameter (such as the password created by AddPostgres) when it flowed into a dependent resource, but still emitted the value in plaintext via the owning resource's own environment variable (for example POSTGRES_PASSWORD).

The redaction added in #18089 only enumerated top-level ParameterResource instances in the application model. Generated parameters created by helpers such as AddPostgres, AddRedis, and AddSqlServer (via CreateDefaultPasswordParameter -> CreateGeneratedParameter) are referenced by their owning resource but are never added to the model, so they were absent from the redaction set and leaked in plaintext.

This change mirrors ParameterProcessor's dependent-parameter discovery (a recursive dependency walk) so the redaction set matches the full set of secret values that can flow into any resource's environment — including the owning resource's own env vars. Discovery runs as a single multi-root walk over all resources (one shared visited set, so each resource is read at most once) and is peek-only: it reads only the callback results DCP has already cached and never invokes a callback. A running resource only appears in a snapshot after DCP has resolved and cached its environment/argument values, so peeking observes every secret a snapshot can expose, while never repopulating DCP's shared annotation cache or binding a canceled/faulted task to the describe client's cancellation token (which DCP would otherwise reuse on the resource's own execution path).

The redaction set is accumulated add-only rather than recomputed per event, and is scoped to the AppHost (a SecretRedactionHistory singleton shared by every backchannel connection) rather than to a single connection. Each describe/watch client gets its own RPC target; scoping the history per connection would let a client that connects after a value changes start with an empty set and leak the previous value carried by a lagging snapshot. The history accumulates at two levels:

  • Secret parameters (the discovered ParameterResource objects). A restart can change which secret a resource references (DCP forgets and re-evaluates callbacks), and a lagging Stopping/Starting snapshot from the prior incarnation can still carry the previous value, so a secret parameter observed on an earlier pass keeps being re-resolved.
  • Resolved secret values (the actual strings). A parameter's value can be replaced in place: the runtime "Set parameter" path swaps a completed WaitForValueTcs for a new one (ParameterProcessor.SetParameterValue), so re-resolving a retained parameter later yields only the new value. An already-published or still-current snapshot can still carry the previous value, so every secret string ever resolved stays in the redaction set.

This narrows but does not fully close a cold-start residual (a value assigned and reassigned before any connection ever observed it); in practice the always-on dashboard/CLI watch keeps a connection open from startup, so the history is populated continuously.

Peek-only discovery is implemented with internal-only API (no public API surface added): ICallbackResourceAnnotation.TryGetCachedResult reads an already-cached callback result under the annotation lock, and ResourceDependencyDiscoveryOptions.PeekCachedCallbackResultsOnly makes the env/args/launch-tool gatherers read only results that completed successfully.

Regression tests:

  • GetResourceSnapshotsAsync_RedactsSecretParameterReferencedByOwningResourceEnvironment references a secret parameter that is not a top-level model resource and asserts the owning resource's own environment variable is redacted.
  • RetainsPreviouslyReferencedSecret_AfterRestartRepointsResource reproduces the restart generation-skew — it flips the referenced secret A->B, re-primes the cache the way ForgetCachedCallbackResults + re-evaluation does, then publishes the still-present old value-a and asserts it stays redacted (fails without the accumulator).
  • RetainsPreviousSecretValue_AfterParameterValueIsReplaced covers the runtime "Set parameter" path: it redacts value-a, swaps the same parameter's WaitForValueTcs to value-b while a lagging snapshot still carries value-a, and asserts value-a stays redacted (fails when only parameter objects, not resolved values, are accumulated).
  • RetainsPreviousSecretValue_AcrossSeparateConnections proves the AppHost-scoped history: it observes value-a on one RPC target, replaces the value with value-b, then drives a second, independently constructed target (as a newly connected client would) and asserts value-a stays redacted (fails when the history is per-connection; verified via a negative check).
  • RedactsNewlyReferencedSecret_AfterRestartRepointsResource covers picking up the newly referenced secret after a restart.
  • DoesNotInvokeUncachedResourceCallback_AndLeavesItEvaluable and DoesNotInvokeOrPoisonCallbackCache_WhenDescribeIsCanceled prove discovery never invokes or caches a callback (even under cancellation) and leaves it evaluable afterward.
  • PeekCachedCallbackResultsOnly_OnlySeesCachedResultsAndNeverInvokesCallback covers the discovery primitive directly.

This targets main; a selective backport to release/13.5 will follow once merged.

Fixes #19241

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

@github-actions

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 -- 19248

Or

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

@github-actions github-actions Bot added the needs-area-label An area label is needed to ensure this gets routed to the appropriate area owners label Aug 11, 2026
@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

Fixes secret leakage from owning resources in backchannel snapshots.

Changes:

  • Discovers secret parameters through recursive resource dependencies.
  • Caches parameter instances while refreshing resolved values.
  • Adds regression coverage for generated owning-resource secrets.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
src/Aspire.Hosting/Backchannel/AuxiliaryBackchannelRpcTarget.cs Expands secret discovery and redaction.
tests/Aspire.Hosting.Tests/Backchannel/AuxiliaryBackchannelRpcTargetTests.cs Tests owning-resource environment redaction.
Suppressed comments (1)

src/Aspire.Hosting/Backchannel/AuxiliaryBackchannelRpcTarget.cs:1315

  • This makes secret redaction fail open. If a callback succeeded while producing the resource environment but throws during this later discovery pass, its non-top-level secret parameters are omitted and the partial result is cached, so subsequent snapshots can expose those values in plaintext. Cancellation is swallowed the same way. At this confidentiality boundary, propagate discovery failures (and cancellation) rather than continuing with an incomplete redaction set, or conservatively redact the affected resource's environment.
            catch (Exception ex)
            {
                logger.LogDebug(ex, "Failed to compute dependencies for resource {ResourceName} while collecting secret parameters for redaction.", resource.Name);
                continue;

Comment thread src/Aspire.Hosting/Backchannel/AuxiliaryBackchannelRpcTarget.cs Outdated
@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.

@IEvangelist

Copy link
Copy Markdown
Member Author

PR Testing Report — #19248

PR Information

Artifact Version Verification

  • Installed CLI: 13.5.0-pr.19248.g4a10f14e
  • Hive packages: Aspire.Hosting.*.13.5.0-pr.19248.g4a10f14e.nupkg
  • Status: ✅ Verified — installed build carries PR head short SHA g4a10f14e.

Changes Analyzed

  • src/Aspire.Hosting/Backchannel/AuxiliaryBackchannelRpcTarget.cs — discovers secret parameters via recursive dependency walk (mirrors ParameterProcessor), caches the instance set, re-reads resolved values per snapshot; both the batch (GetResourceSnapshotsAsync) and streaming (WatchResourceSnapshotsAsync) paths now use GetResolvedSecretParameterValuesAsync.
  • tests/Aspire.Hosting.Tests/Backchannel/AuxiliaryBackchannelRpcTargetTests.cs — regression test.
  • Category: Hosting change (describe backchannel redaction). No CLI/dashboard/template/extension/CI changes.

Scenario — exact repro from #19241

var pg = builder.AddPostgres("pg");   // auto-generates secret param pg-password (referenced, not in model)
pg.AddDatabase("appdb");
builder.AddContainer("consumer", "nginx")
       .WithEnvironment("PG_CONN", pg.Resource.ConnectionStringExpression);

aspire run (all resources Running/Healthy: pg postgres:18.3, appdb, consumer nginx) → aspire describe --format json (batch) and --follow (NDJSON stream).

Methodology note: This terminal environment applies a pattern‑based secret display mask — connection-string password segments render as ****** in view/console output even when the underlying file holds plaintext. All secret‑presence claims below were verified at the byte level (and cross‑checked by Base64‑encoding raw field values, which bypasses the mask). The generated password is referred to as <pw> (22 chars) and is never reproduced here.

Results

✅ PR fix works — owning resource's own env var is redacted

pg (owning) resource in describe --format json:

Field Value Byte-level check
POSTGRES_PASSWORD null "POSTGRES_PASSWORD": null present; <pw> not present in this field ✅
resource.connectionString null
  • Container actually received a real non-empty <pw> (confirmed via docker inspect), so null is active redaction, not an empty value.
  • --follow streaming path produces the same result: pg NDJSON line has POSTGRES_PASSWORD present and null. ✅
  • This matches the PR's regression test GetResourceSnapshotsAsync_RedactsSecretParameterReferencedByOwningResourceEnvironment (green in CI).

⚠️ Related pre-existing gap — dependent connection-string env var still leaks plaintext (out of scope for this PR)

consumer (dependent) resource in the same describe output:

  • PG_CONN = plaintext 80-byte connection string Host=pg.dev.internal;Port=5432;Username=postgres;Password=<pw>.
  • Proven mask-proof: the raw <pw> byte sequence appears exactly once in describe.json (inside PG_CONN) and once in describe-follow.ndjson; the literal token ****** appears 0 times in the raw file. The consumer container's own PG_CONN env (via docker inspect) is identically plaintext, so describe is faithfully reporting the real value.

Why: RedactIfSecretValue is exact-match only, and its own doc-comment documents this limitation:

"A secret embedded as a substring of a larger composed value (e.g. a connection string) is not detected, because only exact-equality matches are redacted."

This PR does not modify RedactIfSecretValue — it only expands the set of secret values (which can only redact more, never less), so this behavior is identical before and after this PR (pre-existing, not a regression).

Worth flagging: #19241 states the dependent PG_CONN is already redacted (…Username=postgres;******). In this build it is not — it is plaintext. The ****** in that issue is consistent with a terminal display mask (the same illusion reproduced here), rather than actual redaction by Aspire. So the same secret remains observable via describe through the dependent resource's connection-string env var, meaning #19241's broader "consistent redaction everywhere" goal is not fully closed by this PR alone. Recommend confirming whether composed connection-string env vars are expected to be redacted, and if so, tracking that as separate follow-up.

Summary

Scenario Status
Owning pg.POSTGRES_PASSWORD redacted (batch) ✅ Passed
Owning pg.POSTGRES_PASSWORD redacted (--follow stream) ✅ Passed
resource.connectionString redacted ✅ Passed
Container has real non-empty secret (proves null = redaction) ✅ Confirmed
CLI/hive version == PR head ✅ Verified
Dependent consumer.PG_CONN plaintext ⚠️ Pre-existing gap (out of scope; unchanged by this PR)

Overall Result

✅ PR VERIFIED for its stated scope — the owning resource's own generated-secret env var is now redacted in aspire describe (batch + follow), end-to-end with a real AddPostgres container.

⚠️ One related, pre-existing plaintext-leak surface remains (dependent resource connection-string env var PG_CONN), which this PR neither introduces nor claims to fix, but which is relevant to #19241's overall goal and worth a follow-up.

@adamint

Copy link
Copy Markdown
Member

Yes!!! Thank you for doing this

@adamint Adam Ratzman (adamint) left a comment

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.

Requesting changes because secret discovery can still fail open. The existing restart-cache thread and the callback re-evaluation issue below both need to be resolved before this is safe to merge.

Comment thread src/Aspire.Hosting/Backchannel/AuxiliaryBackchannelRpcTarget.cs Outdated
Copilot AI review requested due to automatic review settings August 12, 2026 01:21
@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.

Review details

Suppressed comments (2)

src/Aspire.Hosting/Backchannel/AuxiliaryBackchannelRpcTarget.cs:1316

  • This failure path is fail-open for secret redaction. If runtime environment evaluation already produced a hidden parameter value but dependency discovery later throws (for example, because this call re-invokes a stateful callback), continue omits that parameter and line 1328 permanently caches the incomplete set; subsequent snapshots can then return the secret in plaintext. Redaction must either propagate the discovery failure or conservatively suppress environment values for the affected resource rather than caching a partial result.
            catch (Exception ex)
            {
                logger.LogDebug(ex, "Failed to compute dependencies for resource {ResourceName} while collecting secret parameters for redaction.", resource.Name);
                continue;

src/Aspire.Hosting/Backchannel/AuxiliaryBackchannelRpcTarget.cs:1313

  • This catch also intercepts OperationCanceledException raised from the supplied cancellation token, so a canceled snapshot/watch request can continue traversing resources instead of terminating. Exclude cancellation exceptions from the defensive callback handling.
            catch (Exception ex)
  • Files reviewed: 2/2 changed files
  • Comments generated: 0 new
  • Review effort level: Balanced

@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.

@mitchdenny Mitch Denny (mitchdenny) left a comment

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.

The same-named secret redaction gap is fixed and the updated check suite is green.

@adamint Adam Ratzman (adamint) left a comment

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.

Requesting changes again because both redaction threads remain unresolved on d460159. The same-name fix and test are covered by the current checks, but describe can still leak secrets on the first discovery pass or after a resource restart.

@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 2 out of 2 changed files in this pull request and generated no new comments.

Suppressed comments (3)

src/Aspire.Hosting/Backchannel/AuxiliaryBackchannelRpcTarget.cs:1337

  • Every watch event reaches this loop, while ResourceNotificationService.WatchAsync emits one initial event per resource and every subsequent update. Running recursive discovery independently for every model resource makes each event perform up to R graph traversals; the initial stream can therefore do quadratic-or-worse work and backlog its unbounded notification channel. Use the existing multi-root ResourceExtensions.GetDependenciesAsync(appModel.Resources, ...) overload once per secret-set refresh (top-level parameters are already collected above), or cache this set with restart-aware invalidation.
        foreach (var resource in appModel.Resources)
        {
            IReadOnlySet<IResource> dependencies;
            try
            {

src/Aspire.Hosting/Backchannel/AuxiliaryBackchannelRpcTarget.cs:1327

  • The PR description says dependency-discovery exceptions are logged and skipped so a bad callback does not break describe, but this rethrows, and the added failure test codifies that opposite behavior. Either update the PR description to document the intentional fail-closed availability tradeoff or implement the stated per-resource recovery without returning an under-redacted snapshot.
                logger.LogDebug(ex, "Failed to compute dependencies for resource {ResourceName} while collecting secret parameters for redaction.", resource.Name);
                throw;

src/Aspire.Hosting/Backchannel/AuxiliaryBackchannelRpcTarget.cs:1281

  • This private helper's remarks narrate the old implementation and issue history across 20 lines. Internal Aspire members should have concise documentation; keep only the enduring restart and fail-closed rationale rather than documenting the branch's evolution.
    /// <remarks>
    /// Enumerating only <c>appModel.Resources.OfType&lt;ParameterResource&gt;()</c> misses generated
    /// parameters such as the password created by <c>AddPostgres("pg")</c>, which is referenced by the
    /// owning resource but never added to the model. That gap let the owning resource's own environment
    /// variable (e.g. <c>POSTGRES_PASSWORD</c>) leak the secret in plaintext even though the same value

Move the add-only secret parameter and value accumulators off the
per-connection AuxiliaryBackchannelRpcTarget and into a new AppHost-scoped
SecretRedactionHistory singleton shared by every connection.

Each backchannel connection gets its own RPC target, so a client that
connects after a secret's value is replaced would otherwise start with an
empty redaction set and leak the previous value carried by a lagging
snapshot. Sharing the history across connections for the life of the AppHost
keeps every secret value ever observed redacted for later, independent
connections.

Adds a regression test proving a second, freshly constructed target redacts
a value that only the first target ever observed.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: e6410f72-f73a-4be8-b069-7cc8e08a547a

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.

Copilot wasn't able to review this pull request because it exceeds the maximum number of files (300). Try reducing the number of changed files and requesting a review from Copilot again.

@IEvangelist
David Pine (IEvangelist) changed the base branch from release/13.5 to main August 13, 2026 15:45
@IEvangelist

Copy link
Copy Markdown
Member Author

Retargeted this PR from release/13.5 to main. main still carries the #19241 gap (redaction there enumerates only top-level ParameterResource instances), so the fix belongs in main first; I rebased the branch onto main (clean, no conflicts) and force-pushed. A selective backport to release/13.5 will follow once this merges.

Also addressed the last open thread: the add-only redaction history is now AppHost-scoped via a SecretRedactionHistory singleton shared by every per-connection RPC target, so a value observed by one describe/watch connection stays redacted for a later, independently connected client. Added RetainsPreviousSecretValue_AcrossSeparateConnections to cover it. All review threads are resolved.

Comment thread src/Aspire.Hosting/Backchannel/AuxiliaryBackchannelRpcTarget.cs Outdated

@adamint Adam Ratzman (adamint) left a comment

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.

The intended owning-resource case works end-to-end through a live AppHost and real aspire describe, including restart/repoint and a new client. Two fail-open paths still remain, though: one-shot describe samples the redaction set before per-resource async work, and the history is observation-based so the first backchannel connection after A→B can miss a lagging A value. I left the batch race inline and followed up in the existing cold-history thread. Requesting changes because either path can still emit a plaintext secret.

…ssignment time

Addresses review feedback on #19248 (two remaining concerns):

1. Resolve the secret redaction set per snapshot inside
   CreateResourceSnapshotFromEventAsync (after the MCP discovery await)
   instead of once per describe batch. Building a snapshot can block on
   MCP tool discovery for up to the discovery timeout, during which a
   parameter can resolve; a set computed once up front could miss a
   secret a later resource's snapshot already carries and leak it.

2. Record resolved secret values into the AppHost-scoped
   SecretRedactionHistory the moment ParameterProcessor assigns or
   replaces them, closing the cold-start residual where a value assigned
   (and possibly replaced) before the first backchannel connection was
   absent from the history and could leak from a lagging snapshot.
   Wired via an internal settable property + DI factory so the public
   ParameterProcessor constructor stays unchanged (backport-safe).

Adds regression tests covering a secret resolved during another
resource's MCP-discovery window, a secret replaced before the first
connection, and assignment-time recording in ParameterProcessor.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings August 14, 2026 15:36
@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.

48 / 100 test projects · 4 jobs, from 13 changed files.

Selected test projects (48 / 100)

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.Seq.Tests, Aspire.Hosting.SqlServer.Tests, Aspire.Hosting.Testing.Tests, Aspire.Hosting.Tests, Aspire.Hosting.Valkey.Tests, Aspire.Hosting.Yarp.Tests, Aspire.Playground.Tests

Selected jobs (4)

deployment-e2e, extension-e2e, polyglot, typescript-api-compat


How these were chosen — grouped by what changed

⚠️ 43 of the 48 selected test projects come from a single change — src/Aspire.Hosting/ApplicationModel/CommandLineArgsCallbackAnnotation.cs.

🔧 src/Aspire.Hosting/ApplicationModel/CommandLineArgsCallbackAnnotation.cs (changed source)
43 via the project graph

show 43

Aspire.Hosting.Analyzers.Tests (2 hops), Aspire.Hosting.Azure.Kubernetes.Tests (2 hops), Aspire.Hosting.Azure.Kusto.Tests (2 hops), Aspire.Hosting.Azure.Tests, Aspire.Hosting.Browsers.Tests (2 hops), 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 (2 hops), Aspire.Hosting.DevTunnels.Tests (2 hops), Aspire.Hosting.Docker.Tests (2 hops), Aspire.Hosting.DotnetTool.Tests (2 hops), Aspire.Hosting.EntityFrameworkCore.Tests (2 hops), Aspire.Hosting.Foundry.Tests (2 hops), Aspire.Hosting.Garnet.Tests (2 hops), Aspire.Hosting.GitHub.Models.Tests (2 hops), Aspire.Hosting.Go.Tests (2 hops), Aspire.Hosting.JavaScript.Tests (2 hops), Aspire.Hosting.Kafka.Tests (2 hops), Aspire.Hosting.Keycloak.Tests (2 hops), Aspire.Hosting.Kubernetes.Tests (2 hops), Aspire.Hosting.Maui.Tests, Aspire.Hosting.Milvus.Tests (2 hops), Aspire.Hosting.MongoDB.Tests (2 hops), Aspire.Hosting.MySql.Tests (2 hops), Aspire.Hosting.Nats.Tests (2 hops), Aspire.Hosting.OpenAI.Tests (2 hops), Aspire.Hosting.Oracle.Tests (2 hops), Aspire.Hosting.Orleans.Tests (2 hops), Aspire.Hosting.PostgreSQL.Tests (2 hops), Aspire.Hosting.Python.Tests (2 hops), Aspire.Hosting.Qdrant.Tests (2 hops), Aspire.Hosting.RabbitMQ.Tests (2 hops), Aspire.Hosting.Redis.Tests (2 hops), Aspire.Hosting.RemoteHost.Tests, Aspire.Hosting.Seq.Tests (2 hops), Aspire.Hosting.SqlServer.Tests (2 hops), Aspire.Hosting.Testing.Tests (2 hops), Aspire.Hosting.Valkey.Tests (2 hops), Aspire.Hosting.Yarp.Tests (2 hops), Aspire.Playground.Tests

🧪 tests/Aspire.Hosting.Tests/Backchannel/AuxiliaryBackchannelRpcTargetTests.cs (changed test)
1 directly: Aspire.Hosting.Tests
3 via the project graph: Aspire.Hosting.Blazor.Tests, Aspire.Hosting.Dotnet.Tests, Aspire.Hosting.Radius.Tests

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

🧪 tests/Aspire.Hosting.Tests/Orchestrator/ParameterProcessorTests.cs (changed test)
1 directly: Aspire.Hosting.Tests

🧪 tests/Aspire.Hosting.Tests/ResourceDependencyTests.cs (changed test)
1 directly: Aspire.Hosting.Tests

Job reasons

Job Triggered by
deployment-e2e affected project Aspire.Hosting.Azure
extension-e2e src/Aspire.Hosting/ApplicationModel/CommandLineArgsCallbackAnnotation.cs, src/Aspire.Hosting/ApplicationModel/EnvironmentCallbackAnnotation.cs, src/Aspire.Hosting/ApplicationModel/ICallbackResourceAnnotation.cs, src/Aspire.Hosting/ApplicationModel/LaunchToolArgsCallbackAnnotation.cs, src/Aspire.Hosting/ApplicationModel/ResourceDependencyDiscoveryOptions.cs, src/Aspire.Hosting/ApplicationModel/ResourceExtensions.cs, src/Aspire.Hosting/Backchannel/AuxiliaryBackchannelRpcTarget.cs, src/Aspire.Hosting/Backchannel/SecretRedactionHistory.cs, src/Aspire.Hosting/DistributedApplicationBuilder.cs, src/Aspire.Hosting/Orchestrator/ParameterProcessor.cs
• affected project Aspire.Hosting
polyglot affected project Aspire.Hosting.Go
typescript-api-compat affected project Aspire.Hosting

Selection computed for commit e0eb89c.

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 13 out of 13 changed files in this pull request and generated no new comments.

Suppressed comments (1)

src/Aspire.Hosting/Backchannel/AuxiliaryBackchannelRpcTarget.cs:1329

  • This still performs a whole-model recursive dependency walk once per snapshot. ResourceNotificationService.WatchAsync emits one initial event per resource (ResourceNotificationService.cs:773-788), and the one-shot path also builds one snapshot per resource, so both startup paths now do Θ(N²) callback/dependency scans; every subsequent resource update is Θ(N). Avoid rediscovering the full model on every snapshot—for example, keep a generation-aware discovered-parameter cache that is invalidated when DCP forgets callback results, while continuing to snapshot the add-only resolved-value history per event.
        // Compute the transitive dependency closure of every resource in a single multi-root walk. It shares
        // one visited set across all roots, so each resource's (execution-cached) callbacks are read at most
        // once. Discovering per resource instead would repeat the traversal for every resource and make the
        // initial WatchAsync stream — which emits one event per resource — do quadratic work.

@IEvangelist
David Pine (IEvangelist) merged commit 5faba08 into main Aug 14, 2026
366 checks passed
@IEvangelist
David Pine (IEvangelist) deleted the dapine/fix-describe-owning-secret-leak branch August 14, 2026 17:13
@github-actions github-actions Bot added this to the 13.6 milestone Aug 14, 2026
@IEvangelist

Copy link
Copy Markdown
Member Author

/backport release/13.5

@aspire-repo-bot

Copy link
Copy Markdown
Contributor

Pull request created: #1499

Generated by PR Documentation Check · auto · 66.5 AIC · ⌖ 15.8 AIC · ⊞ 19.6K

@aspire-repo-bot

Copy link
Copy Markdown
Contributor

📝 Documentation has been drafted in microsoft/aspire.dev#1499 targeting release/13.6.

Added an Aside note to reference/cli/commands/aspire-describe.mdx explaining that generated secret values (e.g. passwords created by AddPostgres) are redacted from aspire describe/aspire resources output, including in the owning resource's own environment variables — closing the docs gap for the plaintext-leak fix in this PR.

  • Modified: src/frontend/src/content/docs/reference/cli/commands/aspire-describe.mdx

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

needs-area-label An area label is needed to ensure this gets routed to the appropriate area owners

Projects

None yet

4 participants