
One caveat is that the getSecret method only works when it’s assigned to a module parameter marked with the @secure decorator.
deploy.bicep
param location string = resourceGroup().location
param tags object
param sqlServerName string
param keyVaultName string
param keyVaultResourceGroupName string
param subscriptionId string = subscription().subscriptionId
resource vaultResource ' Microsoft.KeyVault/vaults@2025-05-01' existing = {
name: keyVaultName
scope: resourceGroup(subscriptionId, keyVaultResourceGroupName )
}
module sqlModule 'modules/sql.bicep' = {
name: 'SqlDeploy'
params: {
location: location
tags: tags
sqlServerName: sqlServerName
administratorLogin: vaultResource.getSecret('sqlUser')
administratorLoginPassword: vaultResource.getSecret('sqlPassword')
}
}
modules/sql.bicep
@description('The resource location')
param location string
@description('The tags for the resources')
param tags object
@description('The name for the SQL Server')
param sqlServerName string
@secure()
@description('The SQL Administrator Login')
param administratorLogin string
@secure()
@description('The SQL Administrator password')
param administratorLoginPassword string
resource sqlServerResource 'Microsoft.Sql/servers/databases/extensions@2024-11-01-preview' = {
name: sqlServerName
location: location
tags:tags
properties: {
administratorLogin: administratorLogin
administratorLoginPassword: administratorLoginPassword
}
}
This way, secrets stay in Key Vault and aren’t exposed in outputs or logs.
4. Weak or default naming conventions
Another “easy” mistake is to use predictable names for resources. Similar to those who have these “easy-to-guess” family passwords, using obvious naming such as teststorage or productionvm isn’t recommended. It’s just too risky, as these names indicate too much details about your environment, inviting attackers to guess resource usage and exploit their chances.
Example:
resource storageaccount 'Microsoft.Storage/storageAccounts@2025-06-01' = {
name: 'prodstorage'
...
}
Better: Use names with parameters with random suffixes or unique IDs like:
param storageName string = 'st${uniqueString(resourceGroup().id)}'
resource storageaccount ''Microsoft.Storage/storageAccounts@2025-06-01' = {
name: storageName
...
}
5. Not validating parameters
Accepting any input values for parameters without restrictions is another, yet unnecessary mistake, happening often with Bicep. Think of typos, unexpected values, or even malicious inputs. These can all cause insecure or broken deployments, which you rather avoid.
Example (too open):
param sku string
Better: Add @allowed constraints or use parameter validation.
@allowed(['Standard_LRS', 'Standard_ZRS', 'Premium_LRS'])
param sku string = 'Standard_LRS'
6. Over-permissive role assignments
Assigning broad roles with too much access, like the “owner” or “contributor” role, when only limited access is required, increases the chance of accidental or malicious changes.
Example:
resource roleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
name: guid(resourceGroup().id, principalId, roleDefinitionId)
properties: {
principalId: principalId
roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', 'b24988ac-6180-42a0-ab88-20f7382dd24c') // Contributor
}
}
Better: Assign the smallest possible role, like Storage Blob Data Reader if the user only needs read access.
7. Skipping policy enforcement
Last but not least, deploying resources without guardrails like Azure Policy or template validation is a serious missout. It risks non-compliant configurations, slipping through open storage accounts, public IPs, you name it.
Example: A developer deploys a storage account with allowBlobPublicAccess = true.
param allowBlobPublicAccess bool = true
Better: Enforce a policy that denies public access at the subscription or management group level. You can also set secure defaults in Bicep:
param allowBlobPublicAccess bool = false