Credentials Security and Least Privilege

Keep secrets out of your Jenkinsfile, scope them to folders, prefer short-lived tokens, and never let a credential leak into the console log.

Jenkins holds the keys to your registries, clouds, and deploy targets, which makes it a prime target. Treating credentials carefully is the single highest-leverage security habit.

Never inline a secret

Secrets live in the credentials store, referenced by ID. Pull them with credentials('id') in an environment block or the withCredentials step. Jenkins automatically masks these values in the console, so they never print — as long as you do not defeat it by echoing them yourself or passing them on a command line that gets logged.

Scope to the smallest blast radius

  • Prefer folder-scoped credentials over global ones so a rogue pipeline in another folder cannot read them.
  • Give each team its own credential, not one shared super-token.
  • Use least privilege: a deploy token that can only push to one namespace, not an admin key.

Prefer short-lived and rotated

Static long-lived tokens are a liability. Where possible use OIDC / cloud-native federation (e.g. exchanging a Jenkins identity for a short-lived AWS or GCP token) so nothing durable is stored at all. Rotate what you must store.

Example

Example · bash
pipeline {
    agent any
    stages {
        stage('Push image') {
            steps {
                // withCredentials binds secrets only for this block,
                // then unbinds them — tighter than a global environment {}
                withCredentials([usernamePassword(
                    credentialsId: 'registry-deploy',   // folder-scoped, push-only
                    usernameVariable: 'REG_USER',
                    passwordVariable: 'REG_PASS')]) {
                    sh '''
                        set +x   # do not echo the login line
                        echo "$REG_PASS" | docker login registry.acme.io \\
                            -u "$REG_USER" --password-stdin
                        docker push registry.acme.io/app:${BUILD_NUMBER}
                    '''
                }
            }
        }
    }
}

When to use it

  • A team scopes an AWS credential to a single folder so only pipelines inside the Payments folder can access the production key.
  • An engineer uses withCredentials instead of the environment block so a secret is only injected for the minimum number of steps.
  • A security audit confirms no credential values appear in build logs because Jenkins masks anything that matches a stored secret.

More examples

Narrow withCredentials scope

Scopes the secret to the smallest possible block so the env var is undefined outside the withCredentials call.

Example · groovy
stage('Deploy') {
  steps {
    // Credential only injected inside this block
    withCredentials([string(credentialsId: 'prod-api-key',
                            variable: 'API_KEY')]) {
      sh './deploy.sh --key $API_KEY'
    }
    // $API_KEY is undefined here — expired scope
    sh 'echo "Deploy complete"'
  }
}

Never echo a secret

Demonstrates the correct pattern of passing secrets directly to commands rather than printing them.

Example · groovy
// BAD — never do this:
// echo "Key is: ${env.SECRET}"   // prints masked but still risky

// GOOD — pass directly to the tool:
withCredentials([string(credentialsId: 'api-key', variable: 'KEY')]) {
  sh 'curl -H "Authorization: Bearer $KEY" https://api.example.com/deploy'
}

Folder-scoped credential

Defines a folder-scoped credential in JCasC so only pipelines inside the payments folder can resolve it.

Example · yaml
# JCasC: add a credential scoped to the 'payments' folder
credentials:
  system:
    domainCredentials:
      - domain:
          name: payments-folder
          description: Credentials for the payments folder only
        credentials:
          - string:
              id: stripe-secret
              secret: '{AQA...encrypted...}'
              description: Stripe live secret key

Discussion

  • Be the first to comment on this lesson.