Skip to content

getSecret() in .bicepparam fails with BCP338 when used in a nested property of a structured parameter #19934

Description

@JorgeDios

Is your feature request related to a problem? Please describe.

In a .bicepparam file, getSecret() works only when assigned to the root of a top-level
@secure() parameter. When the same call is placed inside a nested property of a structured
parameter value (an object, or an array of objects) it fails to compile — even when the target
leaf property is declared @secure() string
.

The compiler raises:

Error BCP338: Failed to evaluate parameter "logins": Cannot emit unexpected expression of type ParameterKeyVaultReferenceExpression

This prevents using the idiomatic, type-safe approach of modeling deployment inputs as user-defined
types — e.g. an array of objects where each element carries its own secret (admin password,
connection string, API key). Authors are forced to flatten every secret into a separate top-level
@secure() parameter, which defeats the purpose of grouping related configuration into typed objects
and scales poorly (N secrets × M objects becomes N×M flat parameters).

Minimal reproduction

main.bicep — declares a typed parameter with a @secure() leaf:

@export()
type LoginObject = {
  username: string

  @secure()
  password: string
}

@description('A list of logins, each carrying its own secret password.')
param logins LoginObject[]

main.bicepparam — supplies the secret via getSecret() in a nested property:

using './main.bicep'

param logins = [
  {
    username: 'admin'
    password: getSecret('00000000-0000-0000-0000-000000000000', 'example-rg', 'example-kv', 'admin-password')
  }
]

Compile:

bicep build-params main.bicepparam

Actual result:

main.bicepparam(3,16) : Error BCP338: Failed to evaluate parameter "logins": Cannot emit unexpected expression of type ParameterKeyVaultReferenceExpression [https://aka.ms/bicep/core-diagnostics#BCP338]

What works today (baseline for contrast)

The exact same getSecret() call succeeds when assigned to the root of a top-level secure string:

// main.bicep
@secure()
param password string
// main.bicepparam
using './main.bicep'

param password = getSecret('00000000-0000-0000-0000-000000000000', 'example-rg', 'example-kv', 'admin-password')

…and compiles to a Key Vault reference in the generated parameters JSON:

{
  "parameters": {
    "password": {
      "reference": {
        "keyVault": {
          "id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/example-rg/providers/Microsoft.KeyVault/vaults/example-kv"
        },
        "secretName": "admin-password"
      }
    }
  }
}

So the limitation is purely positional: a Key Vault reference is accepted at a parameter's root,
but rejected anywhere nested inside the parameter's value.

Describe the solution you'd like

Allow getSecret() (i.e. a ParameterKeyVaultReferenceExpression) to appear in nested positions
of a parameter value in .bicepparam, emitting nested Key Vault reference objects in the compiled
parameters JSON — for example:

{
  "parameters": {
    "logins": {
      "value": [
        {
          "username": "admin",
          "password": {
            "reference": {
              "keyVault": { "id": ".../vaults/example-kv" },
              "secretName": "admin-password"
            }
          }
        }
      ]
    }
  }
}

If full support requires changes to the ARM deployment engine (resolving Key Vault references nested
within structured parameter values), then as an interim improvement please make the diagnostic
actionable: BCP338's current text (Cannot emit unexpected expression of type ParameterKeyVaultReferenceExpression) is opaque. A targeted message explaining that getSecret()
may only be used at the root of a parameter — and pointing to the supported alternatives — would save
considerable debugging time.

Describe alternatives you've considered

  • Flatten secrets into separate top-level @secure() string parameters and recombine them inside
    the template. Works, but explodes the parameter surface and breaks the typed-object model.
  • Resolve secrets at deploy time outside Bicep (e.g. a pipeline step pulls from Key Vault and
    injects values). Works, but moves secret handling out of the declarative template and loses the
    Key Vault reference indirection (the secret value transits the pipeline).

Additional context

  • The leaf property is explicitly @secure() string, so this is not a request to weaken secret
    handling — it is a request to allow a secure Key Vault reference in a nested secure slot, exactly
    as already allowed at the parameter root.
  • Bicep CLI version: 0.41.2 (3e403ea7c1)
  • OS: Windows 11

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    Status
    Todo

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions