Censoring secrets from logs in infrastructure as code
15 Oct 2025
Table of Contents
This is not exhaustive, but here’re a few coding hygiene tidbits for making sure that when you need to use a secret in your infrastructure as code (IaC), it doesn’t leak into its execution runtime’s system logs.
Azure Pipelines
Authenticate secretlessly
As of late, you can authenticate an Azure Pipeline’s runtime into most Microsoft cloud-hosted services secretlessly. No more rotating keys; yay!
Just tell your identity provider (“IdP”) – e.g. Entra ID – to be expecting your Azure Pipelines Service Connection, and tell your Azure Pipelines Service Connection to present itself to your IdP over OIDC-based Workload Identity Federation, and you’re all set.
Mask inter-step secrets
If you’re passing a secret from one YAML CI/CD pipeline step to the next…
…Then set the step output’s value using the ##vso[task.setvariable...;isSecret=true] syntax.
This should make Azure Pipelines automatically mask the value of any the variables in the logs generated from running the steps.
GitHub Actions
Authenticate secretlessly
As of late, you can authenticate a GitHub Actions Workflow’s runtime into most Microsoft cloud-hosted services secretlessly. No more rotating keys; yay!
Just tell your IdP – to be expecting your GitHub repository, and tell your GitHub Workflow to present itself to your IdP over OIDC-based Workload Identity Federation, and you’re all set.
Mask inter-step secrets
If you’re passing a secret from one YAML CI/CD pipeline step to the next…
…Then set the step output’s value using the ##vso[task.setvariable...;isSecret=true] syntax.
…Then set the step output’s value using the ::add-mask:: syntax.
This should make GitHub Actions automatically mask the value of any the variables in the logs generated from running the steps.
HCL
I believe these are true of both frameworks / CLIs that use the Hashicorp Configuration Language (“HCL”) – Terraform and OpenTofu.
Leverage existing authenticated system state
Determine whether you even need to leverage an explicit secret in the first place.
Often, a running Terraform/OpenTofu module can authenticate into target infrastructure secretlessly.
For example, if the host runtime is running a logged-in Azure CLI, Azure-related modules can piggyback on the Azure CLI’s authenticated state. (Which you already read about how to set up secretlessly in Azure Pipelines and in GitHub Actions above.)
Mask secrets between modules
When crossing module boundaries, always add sensitive = true flags to to all relevant input variable parameter definitions and to all relevant output variable return-type definitions.
Terraform/OpenTofu should automatically censor the value of any such variables in the output of terraform/tofu command execution and log messages.
Mask secrets within a module
Within a module, use ephemeral syntax to output secrets to subsequent resources.
Also, be sure you’re only passing such a secret into resource providers that offer properly secured _wo (write-only) input parameters.
This not only keeps such values out of logs, but also out of Terraform’s / OpenTofu’s state file.
Ansible
Blegh. This is the one that drives me nuts. I can’t stand how bad the secrets masking/censorship is, compared to the other frameworks I’ve got in this article.
Disable logs of secretful tasks
When invoking an Ansible task, if it will need to be passed a secret as input, or if its logs (including error logs) might expose a secret generated during its runtime…
…Then add a no_log: True flag to the task’s attributes when invoking it.
Ansible should, when run in a non-debugging context, suppress all logs that the task flagged no_log: True would normally emit.
Nuisance known issue
Unlike many other frameworks/languages shown here, Ansible doesn’t offer a syntax for merely censoring/masking secrets, while preserving the rest of the log output.
Many developers find this frustrating when they wish they could see the rest of an error message.
Workaround
The best available workaround I’ve been able to find is to modularize your Ansible code so that each task invocation is only responsible for one real-world job.
By limiting the scope of the task whose logs are suppressed, the inconvenience of missing such logs can be minimized.
Debug mode vulnerability known issue
Thought you’d handled everything by adding no_log? Think again!
Ansible ignores no_log, and displays all logs, including any secrets they might contain, when running on a controller node whose ANSIBLE_DEBUG environment variable is set to True.
Please be extremely cautious when choosing to execute an Ansible playbook.
Is there any chance that the playbook might run in “debug mode,” or that someone might accidentally reconfigure it to do so?
What steps can you take, on your team, to ensure that that can’t happen?
How can you avoid this situation, in all contexts where the control node’s runtime logs might persist somewhere that is difficult to truly erase? For example:
* On a runtime that auto-logs to DataDog.
* On a CI/CD pipeline platform that persists logs for later review by the team.
PowerShell
Leverage existing authenticated system state
Determine whether you even need to leverage an explicit secret in the first place.
Often, a running PowerShell script can authenticate into target infrastructure secretlessly.
For example, if the host runtime is running a logged-in Azure CLI or Azure PowerShell, then Azure-related PowerShell modules can piggyback on that authenticated state.
Mask inter-function-call secrets
at parameter definition
When authoring a reusable function, if it will need to be passed a secret as input…
…Then define the input variable parameter’s data type as [SecureString], not [String].
at invocation
Furthermore, be sure that whatever code calls your reusable function does not accidentally leak the value of the secret while buiding a SecureString.
For example, piping the output of az keyvault secret show directly into ConvertTo-SecureString should help prevent the output of az keyvault secret show from accidentally ending up in PowerShell’s success stream.
$powershell_memory_cached_secret_value = ( `
az keyvault secret show `
--vault-name 'YourKeyVaultName' `
--name 'YourSecretName' `
--query 'value' `
--output 'tsv' `
) | ConvertTo-SecureString `
-AsPlainText `
-Force