If you've been running a Jenkins instance for more than a few months, chances are the Manage Jenkins > System > Global Properties > Environment Variables section has quietly accumulated a small graveyard of key-value pairs. Someone added NEXUS_TOKEN during a migration in 2022. Someone else pasted a DB_PASSWORD "just for testing" that never got removed. A few PATH overrides exist for reasons nobody remembers.
None of this is visible unless you scroll through the UI manually — and manual scrolling doesn't scale, doesn't get version-controlled, and definitely doesn't produce an audit trail. If you're doing a security review, a compliance check, or just cleaning house before a Jenkins upgrade, you need a way to dump every global property programmatically.
Here are the three most practical approaches, depending on the access you have and how much automation you want.
Soln1: The Script Console (Fastest, Admin Required)
If you have admin access to the Jenkins UI, the Script Console (<your-jenkins-url>/script) is the quickest path. It gives you a raw Groovy shell running inside the Jenkins JVM, with full access to the internal object model — including Jenkins.instance, which holds the global node properties.

Global environment variables live under EnvironmentVariablesNodeProperty, one of possibly several NodeProperty objects attached to the Jenkins instance. Here's a script that iterates through them and prints a clean audit-friendly list:
import jenkins.model.Jenkins
import hudson.slaves.EnvironmentVariablesNodeProperty
def globalNodeProperties = Jenkins.get().getGlobalNodeProperties()
def envVarsProperties = globalNodeProperties.get(EnvironmentVariablesNodeProperty.class)
if (envVarsProperties != null) {
println "=== CUSTOM GLOBAL ENVIRONMENT VARIABLES ==="
envVarsProperties.getEnvVars().each { key, value ->
println "${key} = ${value}"
}
println "==========================================="
} else {
println "No global environment variables found."
}
- Pros - Fastest, zero setup
- Cons - Manual Job, Required Admin Role, Full JVM access (More Impact when misconfigure)

Soln2: Reading config.xml Directly
If you have filesystem or SSH access to the Jenkins controller (or you're building this into an automated compliance job), the global properties are also persisted in plain XML under $JENKINS_HOME/config.xml. This makes them grep-able, diff-able, and easy to feed into external tooling — which is handy if you want to track changes over time in version control or run the check as part of a CI pipeline.
You can find block looks like this inside config.xml:
<globalNodeProperties>
<hudson.slaves.EnvironmentVariablesNodeProperty>
<envVars serialization="custom">
<unserializable-parents/>
<tree-map>
<default>
<comparator class="hudson.util.CaseInsensitiveComparator"/>
</default>
<int>2</int>
<string>NEXUS_TOKEN</string>
<string>xxxxxxxxxxxx</string>
<string>DB_HOST</string>
<string>db.internal.local</string>
</tree-map>
</envVars>
</hudson.slaves.EnvironmentVariablesNodeProperty>
</globalNodeProperties>
- You can use
xmllintor any XML/XPath tool extracts just the key-value pairs like this.
xmllint --xpath "//globalNodeProperties//tree-map/string/text()" \ "$JENKINS_HOME/config.xml" 2>/dev/null | paste - -
- If you can write a code (such as Python / C#) to create an audit report (CSV output, timestamped, easy to attach to a ticket):
import xml.etree.ElementTree as ET
import csv
import datetime
tree = ET.parse("/var/lib/jenkins/config.xml")
root = tree.find(".//globalNodeProperties//tree-map")
pairs = [child.text for child in root.findall("string")]
# tree-map serializes as flat [key1, value1, key2, value2, ...]
kv = list(zip(pairs[::2], pairs[1::2]))
timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
with open(f"jenkins_global_env_audit_{timestamp}.csv", "w", newline="") as f:
writer = csv.writer(f)
writer.writerow(["Key", "Value"])
writer.writerows(kv)
print(f"Exported {len(kv)} global environment variables.")
- Pros - Scriptable / More Customization
- Cons - Requires filesystem/SSH access to the controller
Soln3: From Pipeline (Automated, Heavy Script Approval)
Calling the same Jenkins.instance APIs from a Pipeline works
pipeline {
agent { label 'Jenkins-dotnet' } // or a specific agent/node label
stages {
stage('Audit Global Env Vars') {
steps {
script {
def report = []
Jenkins.instance.getGlobalNodeProperties().each { prop ->
if (prop instanceof hudson.slaves.EnvironmentVariablesNodeProperty) {
prop.envVars.each { k, v -> report << "${k} = ${v}" }
}
}
writeFile file: 'jenkins-global-env-audit.txt', text: report.join('\n')
}
archiveArtifacts artifacts: 'jenkins-global-env-audit.txt'
}
}
}
}
but Pipeline scripts run inside the Groovy Sandbox,
- Internals command/ code like
Jenkins.instance,getGlobalNodeProperties(), andgetEnvVars()aren't on the default whitelist. - The first run throws
RejectedAccessExceptionand points you to Manage Jenkins > In-process Script Approval, where an admin has to approve each signature manually

You can share logic for other pipeline by
- push the logic into a Shared Library with
@NonCPSinstead of inlining it in the Jenkinsfile once an admin marks the library as trusted (Manage Jenkins > System > Global Pipeline Libraries), - It stops prompting per-signature and keeps the audit logic version-controlled.
// vars/auditGlobalEnv.groovy
@NonCPS
def call() {
def report = []
jenkins.model.Jenkins.instance.getGlobalNodeProperties().each { prop ->
if (prop instanceof hudson.slaves.EnvironmentVariablesNodeProperty) {
prop.envVars.each { k, v -> report << "${k} = ${v}" }
}
}
return report.join('\n')
}
- Place on folder Structure Like this
jenkins-shared-library
|--> vars
|--> auditGlobalEnv.groovy
|--> src
|--> resources
- In Jenkins file you can import
// Jenkinsfile (Declarative)
@Library('jenkins-shared-library@master') _
pipeline {
agent { label 'Jenkins-dotnet' }
stages {
stage('Audit') {
steps {
// shared library function is a plain step call — no script{} needed here
writeFile file: 'audit.txt', text: auditGlobalEnv()
archiveArtifacts artifacts: 'audit.txt'
}
}
}
}
Script Approval is a safeguard, not friction to bypass — don't grant blanket approval just to silence it. Also, anyone who can edit the Jenkinsfile (or push to a trusted Shared Library) effectively has read access to every global env var, and archived build artifacts are often more widely readable than a Script Console session ever was
For more information about Jenkins share library, I suggest to read this blog https://devopscube.com/jenkins-shared-library-tutorial/
- Pros - Scriptable / More Customization / version-controlled / build history+ artifact
- Cons - Script Approval setup friction (one-time) / someone can get you secret by call shared lib



