getSecret() in .bicepparam fails with BCP338 when used in a nested property of a structured parameter
- Dominant language
- Bicep
- Stars
- 3.6k
- Forks
- 830
- Avg merge
- 1d 21m
- Merged PRs (30d)
- 79
Description
### 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:
```bicep
@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:
```bicep
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:
```bicep
// main.bicep
@secure()
param password string
```
```bicep
// 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:
```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:
```json
{
"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
Contributor guide
Assessment
This issue has not been assessed yet.