String interpolation

2 minute read

Jenkins Pipeline uses rules identical to Groovy for string interpolation. Groovy’s String interpolation support can be confusing to many newcomers to the language. While Groovy supports declaring a string with either single quotes, or double quotes, for example:

def singlyQuoted = 'Hello' def doublyQuoted = "World"

Only the latter string will support the dollar-sign ($) based string interpolation, for example:

def username = 'Jenkins' echo 'Hello Mr. ${username}' echo "I said, Hello Mr. ${username}"

Would result in:

Hello Mr. ${username} I said, Hello Mr. Jenkins

Understanding how to use string interpolation is vital for using some of Pipeline’s more advanced features.

Interpolation of sensitive environment variables

The content on this page originated at jenkins.io and has been updated for CloudBees products.
Groovy string interpolation should never be used with credentials.

CloudBees provides warnings to Pipeline developers and CloudBees CI administrators when Pipeline authors use potentially unsafe Groovy constructions, which can lead to both password leakage as well as code injection. Groovy string interpolation can leak sensitive environment variables, such as user credentials. This is because the sensitive environment variable is interpolated during Groovy evaluation and the environment variable’s value could be made available earlier than intended, resulting in sensitive data leaking in various contexts.

For example, consider a sensitive environment variable passed to the sh step.

pipeline { agent any environment { EXAMPLE_CREDS = credentials('example-credentials-id') } stages { stage('Example') { steps { /* WRONG! */ sh("curl -u ${EXAMPLE_CREDS_USR}:${EXAMPLE_CREDS_PSW} https://example.com/") } } } }

If Groovy performs the interpolation, the sensitive value will be injected directly into the arguments of the 'sh' step, which among other issues, means that the literal value will be visible as an argument to the sh process on the agent in OS process listings. Using single quotes instead of double quotes when referencing these sensitive environment variables prevents this type of leaking.

pipeline { agent any environment { EXAMPLE_CREDS = credentials('example-credentials-id') } stages { stage('Example') { steps { /* CORRECT */ sh('curl -u $EXAMPLE_CREDS_USR:$EXAMPLE_CREDS_PSW https://example.com/') } } } }

For more information, please see Injecting secrets into builds.

By default warnings are configured to be displayed on the build and log pages when there might be insecure interpolation. To configure these warnings set org.jenkinsci.plugins.workflow.cps.DSL.UNSAFE_GROOVY_INTERPOLATION to the following values:

  • ignore: no warnings will be displayed on the log or build page.

  • fail: build failure when the build encounters the first interpolated groovy string that contains a secret.

A warning about insecure interpolation of sensitive variables is displayed on the build page and is in the following format:

The following steps that have been detected may have insecure interpolation of sensitive variables (click here for an explanation): * <step name>: [<variable1>, ...]

Injection via interpolation

Groovy string interpolation can inject rogue commands into command interpreters via special characters.

Using Groovy string interpolation for user-controlled variables with steps that pass their arguments to command interpreters, such as the 'sh', 'bat', powershell, or pwsh steps, can result in problems analogous to SQL injection. This occurs when a user-controlled variable (generally an environment variable, usually a parameter passed to the build) that contains special characters (for example / \ $ & % ^ > < | ;) is passed to the sh, bat, powershell, or pwsh steps using Groovy interpolation. For example:

pipeline { agent any parameters { string(name: 'STATEMENT', defaultValue: 'hello; ls /', description: 'What should I say?') } stages { stage('Example') { steps { /* WRONG! */ sh("echo ${STATEMENT}") } } } }

In this example, the argument to the sh step is evaluated by Groovy, and STATEMENT is interpolated directly into the argument as if sh('echo hello; ls /') has been written in the Pipeline. When this is processed on the agent, rather than echoing the value 'hello; ls /', it will echo hello then proceed to list the entire root directory of the agent. Any user able to control a variable interpolated by such a step would be able to make the sh step run arbitrary code on the agent. To avoid this problem, make sure arguments to steps such as sh or bat that reference parameters or other user-controlled environment variables use single quotes to avoid Groovy interpolation.

pipeline { agent any parameters { string(name: 'STATEMENT', defaultValue: 'hello; ls /', description: 'What should I say?') } stages { stage('Example') { steps { /* CORRECT */ sh('echo ${STATEMENT}') } } } }

Credential mangling is another issue that can occur when credentials that contain special characters are passed to a step using Groovy interpolation. When the credential value is mangled, it is no longer valid and will no longer be masked in the console log.

pipeline { agent any environment { EXAMPLE_KEY = credentials('example-credentials-id') // Secret value is 'sec%ret' } stages { stage('Example') { steps { /* WRONG! */ bat "echo ${EXAMPLE_KEY}" } } } }

Here, the 'bat' step receives echo sec%ret and the Windows batch shell will simply drop the % `and print out the value `secret. Because there is a single character difference, 'secret' will not be masked. Though the value is not the same as the actual credential, this is still a significant exposure of sensitive information. Again, the use of single quotes prevents this issue. ``

pipeline { agent any environment { EXAMPLE_KEY = credentials('example-credentials-id') // Secret value is 'sec%ret' } stages { stage('Example') { steps { /* CORRECT */ bat 'echo %SECRET_VALUE%' } } } }

A warning about unmasked secrets is displayed in the console in the following format:

Warning: A secret was passed to <step name> Affected argument(s) used the following variable(s): [<variable1>...]