Ravindra BagaleCourses & study guides मराठी

Chapter 26: Jenkins Security, RBAC and Hardening

26.4 Script approval and the Groovy sandbox

Why: the problem

Think of Swiggy. Developers write Jenkinsfiles in Groovy. Groovy is a full programming language. It can read any file, open network connections and call Java code inside the controller. If every Jenkinsfile could run any Groovy freely, any developer who can push a branch could read all credentials stored on the controller. So Jenkins runs pipeline code inside a sandbox. The sandbox is a safety box. It allows normal pipeline steps like sh, echo and git, and a list of safe methods. When a script asks for something outside the list, Jenkins stops it with an error and puts the request in a queue called Script Approval. An admin then decides. Most real teams see this error at least once a month. Freshers often click Approve for everything to make the build green. That opens the very door the sandbox was built to close. In this lesson you will trigger the sandbox, read the error, and fix it the right way, mostly without approving anything.

What we will build

You will write a pipeline that uses a method the sandbox blocks. You will read the RejectedAccessException message and open In-process Script Approval. Then you will fix the script in three safe ways: use a pipeline step instead of raw Groovy, move logic into a shell script, and use a trusted shared library. You will also see which signatures are dangerous and must never be approved.

What: every term in plain English

  • Groovy — The language Jenkinsfiles are written in. It runs on the Java virtual machine.
  • Groovy sandbox — A safety box that allows only approved methods in pipeline scripts.
  • RejectedAccessException — The error you get when a script calls a method outside the sandbox.
  • In-process Script Approval — The admin page that lists blocked method signatures waiting for a decision.
  • Signature — The exact method name, like method java.io.File exists. Approving it allows it for every job.
  • Shared library — Reusable pipeline code kept in its own Git repo.
  • Trusted library — A global shared library set by an admin. Its code runs outside the sandbox, so only admins may change its repo.
  • Pipeline Utility Steps — A plugin with safe steps like readJSON, readYaml and writeFile.
Create a pipeline job Paste a script that reads a file with Java Run it Open Script Approval Do not approve; fix the script Run again Clear the queue
Script to sandbox to approval queue to fix

How: step by step

  1. Create a pipeline job

    New Item > sandbox-demo > Pipeline > keep "Use Groovy Sandbox" ticked
    

    What it does: Sandbox is on by default for inline scripts.

    Output

    Saved
    
  2. Paste a script that reads a file with Java

    See Example 1, bad version
    

    What it does: This uses new File(), which the sandbox blocks.

    Output

    (saved)
    
  3. Run it

    Build Now
    

    What it does: The build fails with a clear message.

    Output

    Scripts not permitted to use new java.io.File java.lang.String
    
  4. Open Script Approval

    Manage Jenkins > In-process Script Approval
    

    What it does: The blocked signature waits here.

    Output

    new java.io.File java.lang.String  [Approve] [Deny]
    
  5. Do not approve; fix the script

    Replace new File(...).text with readFile
    

    What it does: readFile is a normal pipeline step and needs no approval.

    Output

    (saved)
    
  6. Run again

    Build Now
    

    What it does: Now green.

    Output

    Finished: SUCCESS
    
  7. Clear the queue

    Script Approval > Deny or Clear Approvals for test entries
    

    What it does: Leave no pending risky approvals.

    Output

    No pending signature approvals
    

Example 1: Hit the sandbox, then fix it with a pipeline step

Stack. Jenkins Pipeline, Groovy sandbox

Goal. Hit the sandbox, then fix it with a pipeline step.

Jenkinsfile (bad)

pipeline {
  agent any
  stages {
    stage('Read version') {
      steps {
        script {
          def v = new File("${env.WORKSPACE}/VERSION").text.trim()
          echo "Version is ${v}"
        }
      }
    }
  }
}

Jenkinsfile (good)

pipeline {
  agent any
  stages {
    stage('Read version') {
      steps {
        script {
          writeFile file: 'VERSION', text: '2.4.1\n'
          def v = readFile('VERSION').trim()
          echo "Version is ${v}"
        }
      }
    }
  }
}

What each line does

  • new File(...) — Raw Java file access. Blocked, and it also runs on the controller, not the agent.
  • readFile('VERSION') — Pipeline step. Reads the file on the agent workspace. Allowed.
  • writeFile — Creates the file for this demo. In a real repo, VERSION comes from Git.
  • .trim() — Removes the newline at the end. String methods like trim are already allowed.
  • script { } — A small block of Groovy inside declarative pipeline. Keep it small.

Green output

--- bad version ---
org.jenkinsci.plugins.scriptsecurity.sandbox.RejectedAccessException: Scripts not permitted to use new java.io.File java.lang.String
Finished: FAILURE
--- good version ---
[Pipeline] writeFile
[Pipeline] readFile
[Pipeline] echo
Version is 2.4.1
Finished: SUCCESS

Expected result. The fix needed no admin approval. Always look for a pipeline step first.

Example 2: Move logic to a shell script and parse JSON with a safe step

Stack. bash, Pipeline Utility Steps plugin

Goal. Move logic to a shell script and parse JSON with a safe step.

scripts/next-tag.sh

#!/usr/bin/env bash
set -euo pipefail
last=$(git describe --tags --abbrev=0 2>/dev/null || echo "v0.0.0")
v=${last#v}
patch=${v##*.}
base=${v%.*}
echo "v${base}.$((patch+1))"

Jenkinsfile

pipeline {
  agent any
  stages {
    stage('Tag') {
      steps {
        script {
          def tag = sh(script: 'bash scripts/next-tag.sh', returnStdout: true).trim()
          def meta = readJSON text: '{"service":"menu-api","owner":"food"}'
          echo "Next tag ${tag} for ${meta.service} owned by ${meta.owner}"
        }
      }
    }
  }
}

What each line does

  • git describe --tags --abbrev=0 — Finds the latest tag in the repo.
  • ${v##*.} and ${v%.*} — Split 2.4.1 into the last number 1 and the base 2.4, then add one.
  • sh(script: ..., returnStdout: true) — Runs the shell script on the agent and returns its output as text.
  • readJSON text: — From Pipeline Utility Steps. Parses JSON safely, no JsonSlurper approval needed.
  • meta.service — Reads a field from the parsed JSON.

Green output

[Pipeline] sh
+ bash scripts/next-tag.sh
[Pipeline] readJSON
[Pipeline] echo
Next tag v2.4.2 for menu-api owned by food
Finished: SUCCESS

Expected result. Heavy logic lives in bash on the agent. The Jenkinsfile stays short and needs no approvals.

Example 3: Use a trusted shared library for code that truly needs more power

Stack. Jenkins shared library, Slack plugin

Goal. Use a trusted shared library for code that truly needs more power.

vars/notifyTeam.groovy (in library repo shared-lib)

def call(String status, String channel = '#builds') {
  def colour = (status == 'SUCCESS') ? 'good' : 'danger'
  slackSend(channel: channel, color: colour,
            message: "${env.JOB_NAME} #${env.BUILD_NUMBER}: ${status}")
}

Manage Jenkins > System > Global Pipeline Libraries

Name: shared-lib
Default version: main
Retrieval method: Modern SCM > Git
Project repository: https://gitlab.com/shop-platform/shared-lib.git
Load implicitly: off

Jenkinsfile

@Library('shared-lib') _
pipeline {
  agent any
  stages { stage('Build') { steps { sh 'echo build' } } }
  post { always { notifyTeam(currentBuild.currentResult) } }
}

What each line does

  • vars/notifyTeam.groovy — A custom step called notifyTeam. Teams use it like a built-in step.
  • def call(...) — The code that runs when you write notifyTeam(...) in a Jenkinsfile.
  • Global Pipeline Libraries — Set by an admin. Global libraries are trusted and run outside the sandbox.
  • Only admins merge to shared-lib — Because trusted code has full power, protect the main branch with reviews.
  • @Library('shared-lib') _ — Loads the library into this pipeline.
  • currentBuild.currentResult — SUCCESS, UNSTABLE or FAILURE at the end.

Green output

Loading library shared-lib@main
Attempting to resolve main from remote references...
[Pipeline] sh
+ echo build
build
[Pipeline] slackSend
Slack Send Pipeline step running, values are - channel: #builds, color: good
Finished: SUCCESS

Expected result. Teams get a powerful step without any script approvals. The power sits in one reviewed repo.

Verify: did it work?

  1. Check the queue is empty

    Manage Jenkins > In-process Script Approval
    

    What it does: No pending signatures.

    Output

    No pending signature approvals.
    
  2. Search already-approved risky signatures

    Script Approval > Signatures already approved
    

    What it does: Look for staticMethod jenkins.model.Jenkins getInstance, java.io.File, Runtime exec.

    Output

    (none found)
    

When it is red 1

Scripts not permitted to use staticMethod jenkins.model.Jenkins getInstance

Cause. The script tries to reach the controller internals. This gives full admin power.

Fix, step by step

  1. Never approve this for a Jenkinsfile.
  2. Ask what the team needs; usually a plugin step or a trusted library function does it.

When it is red 2

Scripts not permitted to use method groovy.json.JsonSlurperClassic parseText

Cause. Raw JSON parsing class is not on the safe list.

Fix, step by step

  1. Install Pipeline Utility Steps.
  2. Use readJSON text: or readJSON file:.

When it is red 3

java.io.NotSerializableException: java.util.regex.Matcher

Cause. A non-serializable object is kept across a step. Not a sandbox error but common next to it.

Fix, step by step

  1. Move the regex into a method marked @NonCPS.
  2. Or do the text work in sh with grep or sed.

Cleanup

  1. Delete demo job

    Job sandbox-demo > Delete Pipeline
    

    What it does: Keeps the lab clean.

    Output

    Deleted
    
  2. Clear test approvals

    Script Approval > Clear Approvals (lab only)
    

    What it does: Removes anything you approved while testing.

    Output

    Approvals cleared
    

Ravindra Bagale's Tip

Script Approval madhe sagla Approve karu naka. Aadhi pipeline step shodha. Dhyan rakho!

Practice tasks

Easy Trigger the sandbox

Why: Seeing it once removes fear.

What to do: Run the bad Jenkinsfile.

How:
  1. Read the full error line.

Check: You can name the blocked signature.

Medium Fix without approval

Why: Approvals pile up and add risk.

What to do: Rewrite with readFile or readJSON.

How:
  1. Run again.

Check: Green build, empty queue.

Hard Audit approvals

Why: Old approvals hide risk.

What to do: Review the approved list on a team Jenkins.

How:
  1. Mark each one keep or remove.

Check: Risky ones are listed for removal.

Easy Say the Groovy sandbox and script approval in two lines

Why: If you can say it simply, you understand it. Juniors, auditors and interviewers all want the short version first.

What to do: Write two short lines about the Groovy sandbox and script approval for a fresher who knows only Linux and basic Jenkins.

How:
  1. Write the risk it removes.
  2. Write the one setting or command you use for it.
  3. Read it aloud to a friend.

Check: A fresher understands it without asking a question.

Easy Start a fresh lab Jenkins

Why: Security practice needs a throwaway Jenkins. If you lock yourself out, you delete it and start again.

What to do: Run Jenkins LTS in Docker with a named volume.

How:
  1. docker volume create jenkins_home
  2. docker run -d --name jenkins -p 8080:8080 -v jenkins_home:/var/jenkins_home jenkins/jenkins:lts-jdk17
  3. docker exec jenkins cat /var/jenkins_home/secrets/initialAdminPassword

Check: The Jenkins login page opens on localhost:8080 and asks for a password.

Medium Test with a second user

Why: Admins see everything, so they never notice a missing permission. Always test as a normal user.

What to do: Log in as a non-admin user in a private browser window and repeat this lesson.

How:
  1. Create the user dev1.
  2. Open a private window.
  3. Do the lesson steps as dev1 and note what you can and cannot see.

Check: You can list exactly which menus dev1 sees.

Medium Break it on purpose

Why: You must know the red message by face. During an audit or an outage there is no time to read slowly.

What to do: Cause one error from the "When it is red" part of this lesson and fix it.

How:
  1. Follow the error section.
  2. Copy the exact message into your notes.
  3. Write the fix in one line.

Check: You fixed it using only the steps, without searching the web.

Medium Write a security runbook page

Why: At 2 a.m. nobody remembers where a setting lives. A short page saves the on-call person.

What to do: Write a one-page runbook for this lesson.

How:
  1. Title, when to use, exact menu path or command, how to check, how to undo.

Check: A teammate can follow it alone.

Hard Explain it like an interview

Why: Interviewers want real stories, not definitions.

What to do: Record a 60 second answer about this lesson.

How:
  1. Say the risk, the setting you changed, and one error you hit.

Check: Your answer has a real menu path or command and a real error message.

Hard Draw the flow top to bottom

Why: A drawing shows who is checked, where, and in what order.

What to do: Draw this lesson as boxes from top to bottom.

How:
  1. Start at the user in the browser, go through login and permission checks, end at the job or agent.

Check: Each box matches a real check you saw.

Homework: try at home

Try at home

At home, make three blocked scripts on purpose. For each one, find a fix that needs no approval and write the before and after.

Recap

  • Pipeline Groovy runs in a sandbox with a safe list.
  • Blocked calls give RejectedAccessException and go to Script Approval.
  • Fix with pipeline steps or shell first; approve rarely.
  • Never approve controller internals like Jenkins getInstance.
  • Put powerful code in a reviewed trusted shared library.

FAQ

Can I turn off the sandbox for one job?

Only admins can, and it means trusting all of that script. Avoid it.

Why does new File read the controller disk?

Groovy in a pipeline runs on the controller. Only steps like sh run on the agent.

Do declarative pipelines also use the sandbox?

Yes. The script blocks inside them do.

Is approving String methods safe?

Common String methods are already allowed. Check each request anyway.

How do I see who approved what?

Approvals are in scriptApproval.xml; manage it with JCasC and Git for history.

Interview questions

  1. What is the Groovy sandbox?

    A safety layer that lets pipeline scripts call only allowed methods, so pipeline authors cannot take over the controller.

  2. A build fails with RejectedAccessException. What do you do?

    Read the signature, look for a pipeline step or plugin that does the job, or move logic into sh. Approve only if it is clearly safe.

  3. Why is Jenkins getInstance dangerous?

    It gives the script full access to the controller, including credentials and settings.

  4. Trusted vs untrusted shared library?

    Global libraries are trusted and skip the sandbox; folder libraries are sandboxed.

  5. Where does pipeline Groovy run?

    On the controller, inside the CPS engine. Steps like sh run on the agent.

Quick quiz

Q1. What happens when a script calls a blocked method?

It fails with RejectedAccessException and waits in Script Approval.

Q2. What is the first fix to try?

Use a pipeline step such as readFile or readJSON.

Q3. Which signature must never be approved?

Jenkins getInstance, Runtime exec, or java.io.File writes.

Q4. Why is a trusted library powerful?

It runs outside the sandbox.

Q5. Who should merge to a trusted library repo?

Only reviewed admins or platform owners.

Samajla ka?

Ravi Sir's Cards for this lesson

मराठी cards