Jenkins: The CI/CD Server With a Large Attack Surface

Jenkins is powerful, flexible, and ubiquitous β€” it powers CI/CD for millions of organisations. It's also notoriously difficult to secure. Its plugin ecosystem (over 1,800 plugins) is a constant source of CVEs. Its default configuration prioritises convenience over security. And its web interface, if exposed, has been a target for opportunistic attackers since its earliest versions.

The core risk: Jenkins has access to your source code, your secrets, and your deployment infrastructure. A compromised Jenkins instance is effectively a compromised build pipeline β€” and from there, potentially a compromised production environment.

Jenkins CVEs are common: Jenkins and its plugins have hundreds of published CVEs. Many organisations run Jenkins versions that are years out of date. Staying current with Jenkins and plugin updates is security-critical, not optional maintenance.

Access Control: Role-Based Security in Jenkins

Out of the box, Jenkins uses a simple "anyone can do anything" or "logged-in users can do anything" model. Neither is appropriate for production use. Install the Role-based Authorization Strategy plugin and configure explicit roles:

  • Admin: Full Jenkins access β€” only for Jenkins administrators
  • Developer: Can trigger builds, view logs, and create/edit their own jobs
  • Read-only: Can view build status and logs β€” useful for QA and leadership
  • Deploy: Can approve and trigger deployment jobs β€” separate from build access

Integrate Jenkins with your organisation's SSO (LDAP, Active Directory, or OIDC via the OIDC Login plugin). This ensures that offboarded employees lose Jenkins access immediately when their identity provider account is disabled.

Never use the admin account for builds: Running pipeline jobs with full admin credentials means a compromised Jenkinsfile has admin access to Jenkins. Create a dedicated service account with only the permissions each pipeline needs.

Credential Management: Never Hardcode Secrets

Jenkins has a built-in Credentials store. All secrets used by pipelines β€” SSH keys, passwords, API tokens, certificates β€” should be stored here and referenced by ID in Jenkinsfiles. Never hardcode credentials in pipeline definitions or pass them as plaintext parameters.

Jenkinsfile β€” secure credential usage Groovy
pipeline {
  agent any
  stages {
    stage('Deploy') {
      steps {
        withCredentials([
          string(credentialsId: 'deploy-api-key', variable: 'API_KEY'),
          sshUserPrivateKey(credentialsId: 'deploy-ssh', keyFileVariable: 'SSH_KEY')
        ]) {
          sh '''
            ./deploy.sh  # API_KEY and SSH_KEY available as env vars
          '''
        }
        // credentials are automatically masked in logs
      }
    }
  }
}

For cloud provider credentials, prefer short-lived instance profile credentials (AWS IAM Roles, GCP Workload Identity) over long-lived keys stored in the Credentials store.

Pipeline Security: Declarative vs Scripted Pipelines

Jenkins supports two pipeline syntaxes: Declarative and Scripted. Declarative pipelines have a restricted syntax that limits what can be expressed β€” this is actually a security advantage. Scripted pipelines allow arbitrary Groovy code, which is powerful but dangerous.

Prefer Declarative pipelines for most use cases. When Scripted pipelines are needed (for complex logic), enable the In-process Script Approval mechanism so administrators must explicitly approve any Groovy scripts run by pipelines.

Sandbox mode: Jenkins runs pipeline scripts in a Groovy sandbox by default. Scripts that attempt to use non-whitelisted APIs require explicit admin approval. Don't whitelist broadly β€” only approve specific methods that are needed.

Agent Isolation: Don't Run Builds on the Controller

The Jenkins controller (master) orchestrates builds β€” it should never execute build steps directly. Configure Number of executors on the controller to 0 and run all builds on dedicated agents. This limits the blast radius if a malicious build step tries to access the Jenkins controller's filesystem or credentials.

For maximum isolation, use ephemeral agents that are created fresh for each build and destroyed after completion. The Kubernetes plugin spins up a pod for each build job β€” each job gets a clean environment with no persistent state.

Jenkinsfile β€” Kubernetes pod agent Groovy
pipeline {
  agent {
    kubernetes {
      yaml '''
        apiVersion: v1
        kind: Pod
        spec:
          containers:
          - name: node
            image: node:20-alpine
            command: [sleep, infinity]
      '''
      defaultContainer 'node'
    }
  }
  stages {
    stage('Test') {
      steps {
        sh 'npm ci && npm test'
      }
    }
  }
}

Plugin Hygiene: The Jenkins Security Risk Nobody Talks About

Jenkins plugins are the biggest source of vulnerabilities in most Jenkins installations. A typical Jenkins instance has 50-200 plugins installed, many of which receive infrequent security updates or have been abandoned entirely.

Plugin hygiene rules:

  • Subscribe to the Jenkins Security Advisories mailing list
  • Remove plugins you don't use β€” every unused plugin is unnecessary attack surface
  • Use the Plugin Manager to check for available security updates weekly
  • Check the Jenkins Security Center before installing any new plugin
  • Prefer core Jenkins features or well-maintained plugins over obscure community plugins

Plugin CVEs are frequent: The Jenkins security team published over 60 plugin advisories in 2025 alone. Running unpatched plugins is one of the most common ways Jenkins instances get compromised.

Network Hardening and Reverse Proxy Setup

Jenkins should never be directly exposed to the internet. Place it behind a reverse proxy (nginx or Apache) and restrict access to internal networks or VPN users only. Configure HTTPS termination at the proxy β€” never run Jenkins over plain HTTP in production.

The Jenkins agent protocol port (TCP 50000 by default) should also be restricted β€” only accessible from your agent network, never from the internet. Use the Jenkins JNLP agent tunnel through your reverse proxy rather than exposing port 50000 directly.

Audit Logging and Monitoring for Jenkins

The Audit Trail plugin logs all Jenkins administrative actions β€” job creation, deletion, configuration changes, credential access, and build triggers. Enable it and ship logs to your SIEM. Alert on: admin logins outside business hours, new credentials created, job configuration changes to production deployment pipelines.

Integrating Security Scanning into Jenkins Pipelines

Security scanning in Jenkins follows the same principles as any CI/CD system β€” scan early, fail on critical findings, and surface results in the PR/build interface. A Jenkinsfile with integrated security scanning:

Jenkinsfile β€” with security scanning Groovy
pipeline {
  agent any
  stages {
    stage('Test') {
      steps { sh 'npm ci && npm test' }
    }
    stage('Security Scan') {
      steps {
        withCredentials([string(credentialsId: 'aquilax-key', variable: 'AQUILAX_API_KEY')]) {
          sh 'aquilax scan --fail-on critical --format sarif -o results.sarif'
        }
      }
      post {
        always {
          recordIssues(tools: [sarif(pattern: 'results.sarif')])
        }
      }
    }
    stage('Deploy') {
      when { branch 'main' }
      steps { sh './deploy.sh' }
    }
  }
}

Jenkins hardening is a journey: A single-day effort can cover access control, credential management, and agent isolation. Plugin hygiene and monitoring are ongoing processes. Prioritise the controls with the highest blast radius reduction first.

Add Security Scanning to Your Jenkins Pipelines

AquilaX integrates with Jenkins via REST API and pipeline steps, providing SAST, SCA, and secrets scanning with configurable security gates β€” in minutes, not days.

Start Free Scan