Skip to content

Azure: config names differing only in case silently collide in Key Vault #2143

Description

@KevyVo

Summary

On the azure stack, two config keys that differ only in case — e.g. RECALL_API_KEY vs Recall_Api_Key — are stored as the same Azure Key Vault secret and silently overwrite
each other. defang config set reports success for both, but only the last write survives. Its recorded key becomes the surviving casing, so ListConfig no longer returns the
original name and deploy fails with a misleading error:

Error: missing configs ["RECALL_API_KEY"] (https://s.defang.io/config)

This is azure-only. AWS (SSM Parameter Store) and GCP (Secret Manager) store config names case-sensitively, so the same two names coexist correctly there.

Root cause

Azure Key Vault secret names are case-insensitive. Defang preserves case when encoding a config key into a secret name, and never checks for a collision before writing — so two
differently-cased keys collapse onto one secret on Azure's side.

  1. Encoding preserves caseToSecretName only maps _-; it does not change case. (The case-folding is done by Key Vault, not Defang.)
    src/pkg/clouds/azure/keyvault/keyvault.go:48
    func ToSecretName(key string) string {
        return strings.ReplaceAll(key, "_", "-")
    }
    RECALL_API_KEYRECALL-API-KEY and Recall_Api_KeyRecall-Api-Key are distinct strings, but Key Vault treats them as the same secret.
  2. Write does no collision check — last write wins. SetSecret overwrites the existing secret and its defang-config tag.
    src/pkg/clouds/azure/keyvault/keyvault.go:311 (PutSecret), src/pkg/cli/client/byoc/azure/byoc.go:730-742 (PutConfig)
  3. ListConfig returns the surviving tag's casing (Recall_Api_Key, not RECALL_API_KEY).
    src/pkg/cli/client/byoc/azure/byoc.go:685-709
  4. Validation matches case-sensitively, so the original compose reference is reported missing.
    src/pkg/cli/compose/validation.go:473 (slices.Contains), also src/pkg/cli/configResolution.go:48

Reproduction

On the azure stack, with a compose file referencing ${RECALL_API_KEY}:

defang config set RECALL_API_KEY    # value A
 defang config set Recall_Api_Key    # value B (different case) — silently overwrites A
 defang config                       # lists only ONE entry
 defang compose up                   # Error: missing configs ["RECALL_API_KEY"]

Expected behavior

Since Key Vault cannot represent both names, defang config set should fail loudly on the collision (e.g. config "Recall_Api_Key" conflicts with existing "RECALL_API_KEY") instead of
silently overwriting and failing later at deploy.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    Fields

    No fields configured for Bug.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions