Repository navigation
Description
Summary
An x-radius-sensitive property that is populated by a recipe's secure output (e.g. a connectionString mapped from a Bicep @secure() / securestring module output) is never delivered to a container that connects to that resource. Non-sensitive connection properties (e.g. host) are injected as CONNECTION_<NAME>_<PROP> env vars as expected, but the sensitive one is silently absent. There is no secure (secretKeyRef) injection path for it either.
This breaks the "output‑secret" pattern for user‑defined resource types backed by direct AVM / Terraform modules, where the platform (not the developer) generates the credential and the app is expected to consume it only via the injected connection env var.
Repro
User‑defined type Radius.Messaging/kafka (apiVersion 2025-08-01-preview) with:
properties:
host: { type: string, readOnly: true }
connectionString: { type: string, readOnly: true, x-radius-sensitive: true }A direct‑module Bicep recipe maps the AVM Event Hubs namespace outputs onto the properties:
'Radius.Messaging/kafka': {
kind: 'bicep'
source: 'mcr.microsoft.com/bicep/avm/res/event-hub/namespace:0.14.2'
outputs: {
connectionString: 'primaryConnectionString' // AVM @secure() output
host: 'name'
}
}
'Radius.Compute/containers': {
kind: 'bicep'
source: 'ghcr.io/radius-project/kube-recipes/containers:latest'
}A container connects to it:
connections: { kafka: { source: kafkaBroker.id } }Full, runnable repro (CI workflow + app + recipe): radius-project/resource-types-verification, tests/kafka/azure-app/ (branch willdavsmith-redis-avm-secrets-check, PR #5).
Expected vs. actual
- Expected: the container receives both
CONNECTION_KAFKA_HOSTandCONNECTION_KAFKA_CONNECTIONSTRING(the latter ideally via a Kubernetes Secret +secretKeyRef, so the value is never a plaintext literal). - Actual: only
CONNECTION_KAFKA_HOSTis injected.CONNECTION_KAFKA_CONNECTIONSTRINGis unset, so the app cannot authenticate (in our repro the pod CrashLoops in its startup shim, which requires the var).
Root‑cause analysis (verified against current main)
The secure value is correctly captured by the deployment engine and the recipe driver, but the redaction pipeline + the recipe‑based container model together have no path to deliver a sensitive recipe‑output property to a connecting container.
-
Engine + driver capture the secure value correctly. The Bicep deployment engine returns the
@secure()output value (secure outputs validate as strings and are not stripped). The driver routessecurestring/secureobjectoutputs to the Secrets map and applies theoutputs:mapping:pkg/recipes/driver/bicep/bicep.go—collectARMOutputs(routes secure types tosecrets),ApplyOutputsMapping(connectionString ← primaryConnectionString).
-
The sensitive field is redacted to
nilin the persisted resource. Before the recipe runs, the controller detectsx-radius-sensitivepaths, decrypts a working copy for the recipe, redacts the stored copy tonil, and persists it:pkg/portableresources/backend/controller/createorupdateresource.go—Run()(sensitive‑field detection →RedactFields→Save).pkg/schema/annotations.go—RedactFields/redactAtSegmentsset the field tonil.
The processor later writes the secret value into the resource status / secret store (ApplyDeploymentOutput) and into top‑level properties in memory (addOutputValuestoResourceProperties), but the secret’s durable home is the secret store, not a plaintext top‑level property:pkg/dynamicrp/backend/processor/dynamicresource.go.
-
A connecting resource reads the target’s raw DB properties with no decryption. When the container’s recipe executes, the controller fetches each connected resource’s properties straight from the database and hands them to the recipe context:
pkg/portableresources/backend/controller/createorupdateresource.go—executeRecipeIfNeeded()→resourceutil.GetAllPropertiesFromResource()(no decryption) →ConnectedResourcesProperties.pkg/recipes/driver/bicep/bicep.go—recipeContext.Resource.Connections = opts.Recipe.ConnectedResourcesProperties.
So the container recipe sees whatever is in the DB for the sensitive field — which is the redactednil, not the secret value.
-
The
Radius.Compute/containersrecipe has no secure‑injection path for this case. All connection env‑var injection happens inside the reciperadius-project/resource-types-contrib:Compute/containers/recipes/kubernetes/bicep/kubernetes-containers.bicep, which has exactly three mechanisms:connectionEnvVars— plaintext literalvalue: string(prop.value)from the connected resource’s properties (used for normal connections like kafka).secretsEnvFrom—envFrom.secretRef, only when the connection source isRadius.Security/secrets.secretNameEnvFrom—envFrom.secretRef, only when the connection carries asecretNameproperty naming a Kubernetes Secret.
None of these applies to anx-radius-sensitiveproperty produced by a recipe output on an ordinary user‑defined type, so even if the value were present it could only be injected as a plaintext literal.
Why host works but connectionString doesn’t
host is not sensitive, so it is never redacted; it persists as a normal top‑level property and is injected as a literal CONNECTION_KAFKA_HOST. connectionString is x-radius-sensitive, so it is redacted out of the readable top‑level properties (its value living in the secret store), and the container recipe has no secretKeyRef path to pull it back. Hence the asymmetry.
Impact
- The platform‑generated / output‑secret credential pattern does not work end‑to‑end for direct‑module (AVM/Terraform) backed types whose secret is a recipe output consumed by a connected container. This is the common shape for managed datastores/brokers where the cloud generates the credential (Event Hubs SAS, Redis access key, etc.).
- It is latent for any such type: e.g. a Redis type mapping
url ← primaryConnectionStringhas the same gap; it just isn’t exercised by an app/connection test today. - Note this is not a missing‑test issue (cf. Direct module recipes: add cloud E2E functional tests for limitations not coverable in noncloud CI #12202, which tracks adding cloud E2E for direct‑module secret routing). The routing into Secrets works; the consumption path to a connected container is missing.
Proposed solutions (any one unblocks the pattern)
- Secure connection injection (preferred): give the
Radius.Compute/containersmodel a way to inject anx-radius-sensitiveconnection property via a Kubernetes Secret +secretKeyRef, by surfacing the connected resource’s sensitive values to the container recipe through a secret reference (rather than redacted/raw properties). This keeps the value out of plaintext env literals. - Decrypted connection context for sensitive properties: when building
ConnectedResourcesProperties, resolvex-radius-sensitiveproperties from the secret store so the value is available to the consuming recipe (with the recipe injecting it as asecretKeyRef, not a literal). - Document + first‑class
secretNamebridge: define a supported convention where a recipe can expose asecretNamefor its sensitive outputs so the existingsecretNameEnvFrompath injects them viasecretRef.
References
- Repro:
radius-project/resource-types-verificationtests/kafka/azure-app/(PR #5). - Related: Direct module recipes: add cloud E2E functional tests for limitations not coverable in noncloud CI #12202 (direct‑module cloud E2E coverage, incl. sensitive‑output routing), Implement direct module support for Recipes #12109 / Direct module support for Recipes #11876 (direct‑module recipes design + implementation).
- Code (paths on
main):pkg/portableresources/backend/controller/createorupdateresource.go,pkg/dynamicrp/backend/processor/dynamicresource.go,pkg/schema/annotations.go,pkg/recipes/driver/bicep/bicep.go, andradius-project/resource-types-contrib:Compute/containers/recipes/kubernetes/bicep/kubernetes-containers.bicep.
Activity
radius-triage commentedon Jun 30, 2026
👍 We've reviewed this issue and have agreed to add it to our backlog. Please subscribe to this issue for notifications, we'll provide updates when we pick it up.
We also welcome community contributions! If you would like to pick this item up sooner and submit a pull request, please visit our contribution guidelines and assign this to yourself by commenting "/assign" on this issue.
For more information on our triage process please visit our triage overview
nicolejms commentedon Sep 11, 2026
Partially complete at current main baseline
ec979015e67de6222b2b83c462de1ae70790c3e4.Implemented
secretsblock.Remaining gaps
x-radius-sensitiveconnectionString. The processor still drops recipe secret outputs without the reserved block, so the new path does not transparently cover that schema. Support or an accepted migration for this case remains necessary.