Blog post 11 min read

Jenkins Secrets: Managing Credentials Without Compromising Security

Published on
Blog image

Jenkins, the leading open-source automation server, powers a significant portion of CI/CD pipelines globally. Its extensive plugin ecosystem and flexible pipeline-as-code approach make it the go-to choice for teams automating their build, test, and deployment processes. But like any CI/CD platform, Jenkins needs access to sensitive credentials to work.

Secrets are the sensitive credentials that grant access to your infrastructure: database passwords, API keys, Docker registry credentials, and cloud provider tokens. In Jenkins pipelines, these secrets enable everything from pulling code from private repositories to deploying applications to production environments.

The challenge is keeping these secrets secure while maintaining the automation and flexibility that makes Jenkins valuable. This becomes critical as compliance requirements like SOC 2, GDPR, and HIPAA demand comprehensive audit trails, encryption standards, and access controls.

While Jenkins provides native credential management through its Credentials and Credentials Binding plugins, modern secrets management platforms like Infisical offer advanced capabilities.
In this guide, we’ll explore both approaches, their trade-offs, and help you implement the right solution for your CI/CD security needs.

Understanding Jenkins Credentials

Jenkins’ Credentials Plugin is the built-in solution for managing sensitive data. It stores credentials in an encrypted format on the Jenkins controller, making them available to jobs and pipelines while keeping them hidden from logs and console output.

Jenkins credentials are most commonly scoped as System (Jenkins-only) or Global (available to jobs and Pipelines). In many folder-based setups, teams also use folder-scoped credential stores to limit credentials to a folder and its child jobs.

The plugin supports multiple credential types:

  • Secret text: API tokens and webhook secrets
  • Username and password: Basic authentication credentials
  • Secret file: Certificates, license keys, configuration files
  • SSH Username with private key: For Git operations and SSH connections
  • Certificate: PKCS #12 certificates for mutual TLS

Now that we understand the foundation, let's walk through the practical implementation of Jenkins credentials.

Creating and Managing Jenkins Secrets

Creating and managing secrets in Jenkins involves a few steps.

Adding Credentials via Jenkins UI

Start by adding credentials through the Jenkins interface.

  1. Navigate to Manage Jenkins → Credentials
  2. Select System → Global credentials (unrestricted)
  3. Click Add Credentials
  4. Choose the credential type and scope
  5. Enter a unique ID (e.g., ‘dockerhub-prod-credentials’)
  6. Fill in the credential details
  7. Click OK to save

Using Credentials in Freestyle Jobs

For freestyle projects, credentials are injected through the Build Environment:

  1. In your job configuration, check “Use secret text(s) or file(s)”
  2. Add bindings for your credentials
  3. Specify environment variable names

The credentials become available as environment variables during the build.

Using Credentials in Pipeline Jobs

Pipeline integration is more flexible. To use credentials in a Jenkinsfile, use the following pattern:

pipeline {
    agent any

    stages {
        stage('Build and Push Docker Image') {
            steps {
                script {
                    withCredentials([
                        usernamePassword(
                            credentialsId: 'dockerhub-credentials',
                            passwordVariable: 'DOCKER_PASS',
                            usernameVariable: 'DOCKER_USER'
                        )
                    ]) {
                        sh '''
                            echo $DOCKER_PASS | docker login -u $DOCKER_USER --password-stdin
                            docker build -t myapp:${BUILD_NUMBER} .
                            docker push myapp:${BUILD_NUMBER}
                        '''
                    }
                }
            }
        }

        stage('Deploy to Production') {
            steps {
                withCredentials([
                    string(credentialsId: 'api-token', variable: 'API_TOKEN'),
                    file(credentialsId: 'kubeconfig', variable: 'KUBECONFIG')
                ]) {
                    sh '''
                        kubectl apply -f deployment.yaml
                        curl -H "Authorization: Bearer $API_TOKEN" \\
                             -X POST <https://api.example.com/deploy>
                    '''
                }
            }
        }
    }
}

Multiple Credentials in a Single Stage

When you need several credentials together, include multiple string() calls in the withCredentials array.

stage('Database Migration') {
    steps {
        withCredentials([
            usernamePassword(
                credentialsId: 'db-credentials',
                passwordVariable: 'DB_PASS',
                usernameVariable: 'DB_USER'
            ),
            string(credentialsId: 'db-host', variable: 'DB_HOST'),
            string(credentialsId: 'db-name', variable: 'DB_NAME')
        ]) {
            sh '''
                export DATABASE_URL="postgresql://$DB_USER:$DB_PASS@$DB_HOST/$DB_NAME"
                npm run migrate
            '''
        }
    }
}

Managing Credentials as Code

For teams practicing infrastructure as code, Jenkins Configuration as Code (JCasC) allows you to define credentials in YAML:

credentials:
  system:
    domainCredentials:
      - credentials:
          - usernamePassword:
              id: "github-credentials"
              username: "${GITHUB_USER}"
              password: "${GITHUB_TOKEN}"
              description: "GitHub credentials from environment"

While this improves version control and reproducibility, the underlying security limitations remain and credentials are still stored using Jenkins' decryptable format.

Security Considerations

Jenkins attempts to protect secrets by masking credentials in logs, but this protection is not foolproof.

// Jenkins masks credentials in logs
stage('Secure Operation') {
    steps {
        withCredentials([string(credentialsId: 'secret', variable: 'SECRET')]) {
            sh 'echo $SECRET'  // This will show as **** in logs
        }
    }
}

// But beware of encoding tricks that bypass masking
stage('Unsafe Example') {
    steps {
        withCredentials([string(credentialsId: 'secret', variable: 'SECRET')]) {
            sh 'echo $SECRET | base64'  // This reveals the encoded secret!
        }
    }
}

While these mechanisms appear robust on the surface, they hide a fundamental vulnerability that every Jenkins administrator should understand.

The Critical Security Flaw

Here’s the uncomfortable truth about Jenkins credentials: they’re not truly secure. Anyone with Script Console access (a highly privileged permission) can run Groovy code inside the Jenkins controller runtime and decrypt credentials:

// Run this in Jenkins Script Console (Manage Jenkins → Script Console)
import hudson.util.Secret
import com.cloudbees.plugins.credentials.SystemCredentialsProvider

def credentials = SystemCredentialsProvider.getInstance().getCredentials()
credentials.each { cred ->
    if (cred instanceof com.cloudbees.plugins.credentials.impl.UsernamePasswordCredentialsImpl) {
        println("ID: ${cred.id}")
        println("Username: ${cred.username}")
        println("Password: ${cred.password.plainText}")
        println("---")
    }
}

Jenkins stores credential-encryption material under $JENKINS_HOME/secrets/. If an attacker gains filesystem access to that directory (for example master.key and related secret files) and the encrypted credential values, they can decrypt stored credentials:

// Decrypt any credential if you have the encrypted value
encryptedPassword = '{AQAAABAAAAAQom3LN7ei0wdm9cdOlGOa4GxDHzpndn0BUPeI4biARto=}'
password = hudson.util.Secret.decrypt(encryptedPassword)
println(password)  // Outputs the plain text password

Beyond this core security issue, Jenkins' native credential management faces several practical limitations that become apparent at scale.

Limitations and Challenges

While Jenkins’ Credentials Plugin solves basic storage needs, it has significant operational and security limitations:

Security Vulnerabilities

  • Decryptable by Design: Any Jenkins admin can decrypt all credentials using the Script Console
  • Master Key Exposure: The encryption key is stored in plain text on the file system
  • No Privilege Separation: Users with job configuration access can extract secrets through pipeline modifications

Note: While Role-Based Access Control plugins like Matrix Authorization and Folder-based Authorization can restrict who can modify jobs and access the Script Console, they cannot prevent credential extraction by users who legitimately need job configuration access. RBAC helps limit exposure but doesn't solve the fundamental encryption weakness.

Additional Operational Challenges

Beyond making secrets leakage easy, Jenkins also lacks features that are necessary for robust secrets management:

  • Manual Rotation Burden: No automated credential rotation capabilities. Each update requires manual intervention across all affected jobs
  • No Audit Trail: Jenkins doesn’t track who accessed secrets, when they were used, or what changes were made
  • Team Collaboration Friction: No secure way to share vault passwords across team members or Jenkins instances
  • Static Secrets Only: Cannot generate temporary, auto-expiring credentials for enhanced security
  • Scale Complexity: Managing credentials across multiple Jenkins instances becomes a synchronization nightmare

These gaps can make it harder to meet common compliance expectations, such as:

  • SOC 2: Evidence of access controls and auditability over time
  • HIPAA: Audit controls that record and examine system activity
  • PCI DSS: Strong authentication and documented credential management (including changing defaults and managing passwords)
  • GDPR: Appropriate access controls aligned with security and data minimization principles

These limitations make native Jenkins credentials unsuitable for production environments, especially in regulated industries. Instead, companies need to use a dedicated secrets solution (or build a complex internal management system in-house).

A Modern Alternative: Infisical

Given these constraints, many teams are turning to dedicated secrets management platforms. For teams requiring enterprise-grade secrets management, Infisical provides a native Jenkins plugin that addresses these limitations while maintaining ease of use.

Key Advantages

Using Infisical with Jenkins provides:

  • Automatic rotation with zero downtime
  • Centralized management across all platforms and tools
  • Complete audit logs for compliance
  • Dynamic secrets that expire automatically
  • Fine-grained access control with role-based permissions

To access these, developers just need to spend a few hours (or less) integrating Infisical into their workflow.

Step 1: Installing the Infisical Plugin

Install the plugin through the Jenkins Plugin Manager:

  1. Navigate to Manage Jenkins → Plugins → Available plugins
  2. Search for “Infisical”
  3. Install the plugin and restart Jenkins

Alternatively, the plugin can be installed via the Jenkins CLI:

jenkins-plugin-cli --plugins infisical:29.va_e0dc5ca_b_8fa_

Step 2: Configuring Machine Identity

Create a machine identity in Infisical for Jenkins authentication:

  1. Log into Infisical Dashboard
  2. Navigate to Project Settings → Machine Identities
  3. Create a new identity with Universal Auth
  4. Copy the Client ID and the Client Secret

Add these credentials to Jenkins:

  1. Go to Manage Jenkins → Credentials → System → Global credentials
  2. Add Credentials → Kind: “Infisical Universal Auth Credential”
  3. Enter Client ID and Client Secret
  4. Save with ID like ‘infisical-prod-auth’

Now, Infisical is ready to be fully integrated.

Step 3: Using Infisical in Pipelines

Replace static credentials with dynamic secret retrieval:

pipeline {
    agent any

    stages {
        stage('Deploy Application') {
            steps {
                withInfisical(
                    configuration: [
                        infisicalCredentialId: 'infisical-prod-auth',
                        infisicalProjectSlug: 'backend-services',
                        infisicalEnvironmentSlug: 'production',
                        infisicalUrl: '<https://app.infisical.com>'
                    ],
                    infisicalSecrets: [
                        infisicalSecret(
                            path: '/database',
                            includeImports: true,
                            secretValues: [
                                [infisicalKey: 'DB_HOST'],
                                [infisicalKey: 'DB_USERNAME'],
                                [infisicalKey: 'DB_PASSWORD'],
                                [infisicalKey: 'DB_NAME']
                            ]
                        ),
                        infisicalSecret(
                            path: '/aws',
                            secretValues: [
                                [infisicalKey: 'AWS_ACCESS_KEY_ID'],
                                [infisicalKey: 'AWS_SECRET_ACCESS_KEY'],
                                [infisicalKey: 'AWS_REGION', isRequired: false]
                            ]
                        )
                    ]
                ) {
                    // Secrets available as environment variables
                    sh '''
                        # Database connection
                        export DATABASE_URL="postgresql://$DB_USERNAME:$DB_PASSWORD@$DB_HOST/$DB_NAME"

                        # AWS deployment
                        aws s3 cp build/ s3://my-bucket/ --recursive

                        # Run application
                        npm run deploy
                    '''
                }
            }
        }
    }
}

This approach could be augmented to support multiple environments, too.

Multi-Environment Configuration

Managing secrets across environments is very straightforward. Using the environment slug, secrets can be conditionally integrated:

def getEnvironmentSlug() {
    if (env.BRANCH_NAME == 'main') {
        return 'production'
    } else if (env.BRANCH_NAME == 'develop') {
        return 'development'
    } else {
        return 'staging'
    }
}

pipeline {
    agent any

    stages {
        stage('Dynamic Environment Deploy') {
            steps {
                script {
                    def envSlug = getEnvironmentSlug()

                    withInfisical(
                        configuration: [
                            infisicalCredentialId: 'infisical-auth',
                            infisicalProjectSlug: 'my-project',
                            infisicalEnvironmentSlug: envSlug,
                            infisicalUrl: '<https://app.infisical.com>'
                        ],
                        infisicalSecrets: [
                            infisicalSecret(
                                path: '/',
                                includeImports: true,
                                secretValues: [
                                    [infisicalKey: 'API_URL'],
                                    [infisicalKey: 'API_KEY'],
                                    [infisicalKey: 'FEATURE_FLAGS', isRequired: false]
                                ]
                            )
                        ]
                    ) {
                        sh """
                            echo "Deploying to ${envSlug} environment"
                            echo "API endpoint: \\$API_URL"
                            ./deploy.sh
                        """
                    }
                }
            }
        }
    }
}

Pipeline Snippet Generator

Generate the Infisical configuration using Jenkins Pipeline Syntax:

  1. Navigate to {JENKINS_URL}/job/{JOB_NAME}/pipeline-syntax/
  2. Select "withInfisical: Infisical Plugin" from Sample Step
  3. Configure your settings
  4. Click Generate Pipeline Script.
  5. Copy the generated code to your Jenkinsfile

Making the Right Choice

The decision between using Jenkins native credentials and integrating with a full solution like Infisical is easy.

Use Native Jenkins Credentials when:

  • Strictly dealing with local development environments
  • Proof-of-concept projects
  • Simple, non-production workloads
  • Teams with minimal to no compliance requirements

In short, native Jenkins credentials can be sufficient for no-frills projects.

Meanwhile, use Infisical for:

  • Production environments
  • Systems handling customer data
  • Compliance-regulated industries (healthcare, finance, e-commerce)
  • Teams requiring audit trails
  • Multi-tool environments (Jenkins + Kubernetes + Cloud providers)
  • Any scenario requiring automatic rotation

Infisical is for serious projects with real users in production.

That said, the migration doesn’t need to be all-or-nothing. Start with Infisical for production secrets while keeping development credentials in Jenkins, then expand coverage gradually.

Infisical + Jenkins: The Practical Next Step

Managing secrets in Jenkins starts simple: store them encrypted, inject them into pipelines, and hope for the best. But as teams scale and compliance requirements emerge, the limitations of Jenkins’ native approach become impossible to ignore. The ability to decrypt any credential with a simple script isn’t a bug: it’s a fundamental architectural limitation.

Modern secrets management platforms like Infisical transform Jenkins from a security liability into a compliant, enterprise-ready CI/CD platform. You keep your existing pipelines and workflows while gaining automatic rotation, audit trails, and centralized management across all your tools.

Start small. Choose one production pipeline, migrate its secrets to Infisical, and watch as manual rotation disappears, audit logs populate automatically, and your security posture strengthens. Then expand at your own pace, building a secrets infrastructure that scales with your ambitions rather than constraining them.

Mathew Pregasen avatar

Mathew Pregasen

Technical Writer, Infisical

Starting with Infisical is simple, fast, and free.