Back to blog
AzureBest PracticesCloud SecurityIdentity & AccessOperations & Compliance

Key Vault Secret Has No Content Type: Why It Matters and How to Fix It

Learn why Azure Key Vault secrets without a content type weaken rotation and auditing, plus CLI, Terraform, and policy steps to fix and prevent it.

TL;DR

This check flags Azure Key Vault secrets that have no content type set. The content type is a metadata field that tells consumers how to interpret a secret, and leaving it blank makes secrets harder to manage, rotate, and audit. Set it with az keyvault secret set --content-type or your IaC of choice.

Azure Key Vault is where a lot of teams stash their crown jewels: database connection strings, API tokens, TLS private keys, and the odd password that someone swore they would migrate to managed identity "next sprint." Key Vault does a good job protecting those values, but it only manages them well if you give it enough metadata to work with. The content type field is one of those small bits of metadata that quietly improves how every consumer handles a secret.

This Lensix check, keyvault_nosecretcontenttype, looks for secrets stored in Key Vault that have no content type configured. It is not a critical vulnerability on its own, but it is a hygiene signal that often correlates with poorly governed vaults.


What this check detects

Every secret object in Azure Key Vault has an optional contentType attribute. It is a free-text string that describes what the secret value actually contains. Common values include text/plain, application/json, application/x-pkcs12, or a custom label like password or connection-string.

The check passes when a secret has a non-empty content type, and fails when the field is missing or blank. That is the whole detection logic. What makes it useful is what an empty content type tends to imply about how the secret was created and how it is being managed.

Note: Content type is metadata only. Azure does not validate, encrypt, or act on the value differently based on what you put there. It is a hint for humans and for the applications that read the secret, not a security control by itself.


Why it matters

An empty content type rarely causes an outage by itself. The real cost shows up over time, across a few areas.

Rotation and automation break down

Mature secret rotation pipelines often branch on content type. A rotation function might handle a application/x-pkcs12 certificate completely differently from a connection-string. If the content type is blank, your automation either has to guess based on the secret name (fragile) or skip the secret entirely (worse). Teams that run Azure Functions or Logic Apps to auto-rotate secrets lose a reliable signal when this field is empty.

Consumers misinterpret the value

An application reading a secret needs to know whether it is getting a raw string, a base64-encoded blob, or a JSON document. Without a content type, that knowledge lives only in tribal memory or a wiki page that is three quarters out of date. When the original author leaves, the next engineer has to reverse engineer the format from the value itself, which is exactly the kind of guesswork you do not want around credentials.

Auditing and inventory get harder

During a security review or incident, you often want to answer questions like "which of our secrets are TLS private keys?" or "where are our database connection strings?" A populated content type makes that a one-line query. A vault full of blank content types forces you to inspect values, which means decrypting and reading secrets you would rather not touch.

Warning: Reading secret values to figure out what they are means pulling plaintext credentials into terminals, logs, and shell history. Good metadata lets you classify and inventory secrets without ever exposing the value.

Correlation with weak governance

In practice, secrets with no content type are frequently the same secrets created ad hoc through the portal, dropped in during a 2 a.m. incident, or pushed by a script that nobody owns anymore. The missing field is a tell. Fixing it gives you a reason to revisit each one and confirm it still belongs there, has an expiry, and has a rotation plan.


How to fix it

You can set the content type on an existing secret, or set it at creation time. Here are the main paths.

Azure CLI

To set a content type on an existing secret, you create a new version of it with the content type attached. The simplest approach is to read the current value and write it back with the metadata. If you want to avoid pulling the value to your terminal, use the version-update path described below instead.

# Set content type while creating or updating a secret value
az keyvault secret set \
  --vault-name my-keyvault \
  --name db-connection-string \
  --value "Server=tcp:..." \
  --content-type "connection-string"

To update only the metadata on the current version without rewriting the value, use az keyvault secret set-attributes against a specific secret version:

# Get the current version id
VERSION=$(az keyvault secret show \
  --vault-name my-keyvault \
  --name tls-cert \
  --query "id" -o tsv | awk -F'/' '{print $NF}')

# Update the content type on that version only
az keyvault secret set-attributes \
  --vault-name my-keyvault \
  --name tls-cert \
  --version "$VERSION" \
  --content-type "application/x-pkcs12"

Tip: Prefer set-attributes over set when the secret value is unchanged. It updates metadata in place without creating a brand new version, so your version history stays meaningful and you never have to read the plaintext value.

Azure portal

  1. Open your Key Vault and go to Objects > Secrets.
  2. Select the secret, then select its current version.
  3. In the Content type field, enter a descriptive value such as application/json or password.
  4. Select Apply to save.

PowerShell

Set-AzKeyVaultSecret `
  -VaultName "my-keyvault" `
  -Name "api-token" `
  -SecretValue (ConvertTo-SecureString "the-token-value" -AsPlainText -Force) `
  -ContentType "text/plain"

Terraform

If you manage secrets with Terraform, set content_type on the resource. This is the cleanest fix because it makes the field mandatory in code review going forward.

resource "azurerm_key_vault_secret" "db_conn" {
  name         = "db-connection-string"
  value        = var.db_connection_string
  key_vault_id = azurerm_key_vault.main.id
  content_type = "connection-string"

  tags = {
    environment = "production"
    rotation    = "90d"
  }
}

Bicep

resource dbSecret 'Microsoft.KeyVault/vaults/secrets@2023-07-01' = {
  parent: keyVault
  name: 'db-connection-string'
  properties: {
    value: dbConnectionString
    contentType: 'connection-string'
  }
}

Danger: Re-running az keyvault secret set with a value creates a new secret version. If any consumer pins to a specific version id rather than the latest, a new version can break it. Confirm your apps reference the unversioned URI before rewriting values, or use set-attributes to avoid creating versions at all.


How to prevent it from happening again

Fixing existing secrets is the easy part. Keeping new ones from sliding in without a content type is where the real value is.

Make it required in IaC review

If your secrets live in Terraform or Bicep, add a policy check that fails the build when content_type is missing. With Terraform and a tool like Checkov or Conftest, you can write an OPA rule that scans the plan:

package keyvault

deny[msg] {
  resource := input.resource_changes[_]
  resource.type == "azurerm_key_vault_secret"
  not resource.change.after.content_type
  msg := sprintf("Key Vault secret '%s' is missing content_type", [resource.address])
}

Wire that into your CI pipeline so a pull request that adds a secret without a content type never merges.

Use Azure Policy for runtime enforcement

IaC checks only cover secrets created through IaC. To catch portal clicks and rogue scripts, add an Azure Policy with a custom definition that audits secrets lacking a content type. Azure ships built-in policies around Key Vault secret expiration; a custom audit policy on content type follows the same shape and surfaces non-compliant secrets in the compliance dashboard.

Note: Azure Policy for Key Vault secrets evaluates the data plane, so make sure the policy assignment has the right scope and that your vault uses RBAC or access policies that allow the policy engine to read secret metadata.

Standardize creation through a wrapper

If engineers create secrets by hand, give them a small wrapper script or pipeline template that requires a content type argument. When the easy path enforces the standard, you stop relying on people remembering.

#!/usr/bin/env bash
# create-secret.sh — refuses to create a secret without a content type
set -euo pipefail

VAULT="$1"; NAME="$2"; VALUE="$3"; CONTENT_TYPE="${4:?content type is required}"

az keyvault secret set \
  --vault-name "$VAULT" \
  --name "$NAME" \
  --value "$VALUE" \
  --content-type "$CONTENT_TYPE" \
  --output none

echo "Created $NAME with content type $CONTENT_TYPE"

Continuous monitoring with Lensix

Run the keyvault_nosecretcontenttype check on a schedule so newly created secrets without a content type show up in your findings within hours rather than at the next quarterly audit. Pair it with the related Key Vault checks for secret expiration and soft delete to cover the full hygiene picture.


Best practices

  • Use a consistent vocabulary. Agree on a short list of content type values across your org, such as connection-string, password, api-key, application/json, and application/x-pkcs12. A handful of well-known values beats fifty one-off labels.
  • Set expiration dates too. Content type pairs naturally with expiry. A secret worth labeling is a secret worth rotating, so set --expires when you set the content type.
  • Tag for ownership. Content type describes the format; tags should capture environment, owner, and rotation cadence. Together they make a secret self-describing.
  • Prefer managed identities where possible. The best secret is the one you do not store. For Azure-to-Azure auth, managed identity removes the credential entirely. Reserve Key Vault for secrets that genuinely cannot be replaced by identity-based access.
  • Avoid putting sensitive hints in the content type. The field is metadata and is readable by anyone with secret list permissions, so do not encode anything sensitive in it. Keep it descriptive, not revealing.

None of this is heavy lifting. Setting a content type takes seconds, and enforcing it in CI takes an afternoon. The payoff is a vault where every secret announces what it is, who owns it, and when it expires, which is exactly the kind of inventory you want before an incident, not during one.