Understanding the Jenkins Security Permission Model: PermissionGroup, Permission, and PermissionScope

The Jenkins security permission model organizes access control through three hierarchical concepts: Permission represents individual security privileges, PermissionGroup collects related permissions under an owner class, and PermissionScope defines the model objects (Jenkins instance, items, runs) where permissions can be configured.

The Jenkins security permission model provides a granular, extensible framework for access control that plugin developers use to define and enforce authorization rules. Implemented in the jenkinsci/jenkins repository, this model centers on the relationship between PermissionGroup, Permission, and PermissionScope classes in core/src/main/java/hudson/security/ to create a hierarchical security structure that scales from the global Jenkins instance down to individual build runs.

Core Components of the Jenkins Security Architecture

Permission: The Atomic Security Privilege

A Permission in Jenkins represents a single, discrete security privilege such as Read, Configure, or Delete. Defined in core/src/main/java/hudson/security/Permission.java, each permission is uniquely identified by its owner class and name combination.

The Permission constructor signature reveals the model's sophistication:

public Permission(@NonNull PermissionGroup group,
                  @NonNull String name,
                  @CheckForNull Localizable description,
                  @CheckForNull Permission impliedBy,
                  boolean enable,
                  @NonNull PermissionScope[] scopes)

Key parameters include:

  • group – The PermissionGroup that owns this permission
  • name – A Java-identifier-compatible string (e.g., "Read", "Configure")
  • impliedBy – An optional broader permission that implicitly grants this one, forming a permission hierarchy (see lines 88-96 in Permission.java)
  • scopes – An array of PermissionScope values defining where this permission can be configured
  • enabled – A toggle controlling visibility in the permission matrix

All permissions register themselves in a global list (ALL) and can be retrieved via Permission.fromId(String).

PermissionGroup, defined in core/src/main/java/hudson/security/PermissionGroup.java, serves as the registration point and container for permissions belonging to the same owner class. When a permission is instantiated, it automatically adds itself to its group's internal SortedSet (line 99) and to the global registry (PERMISSIONS).

Key behaviors of PermissionGroup include:

  • Iteration – The class implements Iterable<Permission>, allowing enumeration of all permissions within a group
  • Lookup – The find(String name) method retrieves permissions by simple name
  • Containment checking – hasPermissionContainedBy(PermissionScope) determines whether any permission in the group applies to a specific scope

Permission groups maintain a specific sort order: Hudson/Jenkins core groups appear first based on a compare order value, followed by plugin groups sorted by fully-qualified owner class name (lines 31-40).

PermissionScope: Defining Configuration Boundaries

PermissionScope, implemented in core/src/main/java/hudson/security/PermissionScope.java, abstracts the model object a permission acts upon and determines where administrators can configure that permission. Each scope contains:

  • modelClass – The concrete ModelObject subclass (e.g., Jenkins.class, Item.class)
  • containers – Broader scopes that contain this one, enabling implicit configuration at higher levels (lines 66-72)

The built-in scopes defined in lines 94-116 include:

  • JENKINS – The top-level Jenkins instance
  • ITEM_GROUP – Containers such as folders and projects
  • ITEM – Individual jobs and configuration items
  • RUN – Individual build runs
  • COMPUTER – Agent nodes and build machines

The isContainedBy(PermissionScope) method recursively checks containment relationships, ensuring that a scope always contains itself (lines 80-86). This allows permissions scoped to ITEM to be configured at the ITEM_GROUP or JENKINS level through scope inheritance.

How the Three Components Interact

When defining a new permission, developers follow a standard pattern that demonstrates the interplay between these classes:

class MyFeature {
    private static final PermissionGroup PERMISSIONS = 
        new PermissionGroup(MyFeature.class, Messages._MyFeature_Permissions_Title());

    public static final Permission DO_SOMETHING = 
        new Permission(PERMISSIONS, "DoSomething",
                       Messages._MyFeature_DoSomething_Description(),
                       Permission.READ,   // impliedBy hierarchy
                       true,
                       new PermissionScope[]{PermissionScope.ITEM});
}

In this pattern:

  1. The PermissionGroup registers the owner class (MyFeature.class) and provides the UI title
  2. The Permission adds itself to the group and the global registry while specifying Permission.READ as its implied parent
  3. The PermissionScope (ITEM) restricts configuration to individual items, though administrators may also set it at ITEM_GROUP or JENKINS levels due to scope containment

During authorization checks, Jenkins evaluates whether a user holds the required permission directly or through the impliedBy chain, and whether the current context matches the permission's declared scopes.

Implementing Custom Permissions in Jenkins Plugins

Creating a Permission Group and Permission

Plugin developers typically define permissions as static fields within their plugin class:

// In src/main/java/com/example/MyPlugin.java
public class MyPlugin {
    // Register a group for this plugin's permissions
    private static final PermissionGroup PERMISSIONS =
        new PermissionGroup(MyPlugin.class, Messages._MyPlugin_Permissions_Title());

    // Permission scoped to ItemGroup level (folders, projects)
    public static final Permission CREATE_JOB = new Permission(
        PERMISSIONS,
        "CreateJob",
        Messages._MyPlugin_CreateJob_Description(),
        Permission.READ,          // inherits generic read privilege
        true,
        new PermissionScope[]{PermissionScope.ITEM_GROUP});
}

Checking Permissions at Runtime

To enforce security checks in your plugin code:

// Inside a job configuration page or management link
if (Jenkins.get().hasPermission(MyPlugin.CREATE_JOB)) {
    // Render UI elements allowing job creation
}

Enumerating Permissions Programmatically

To inspect all permissions within a specific group:

PermissionGroup pg = PermissionGroup.get(MyPlugin.class);
for (Permission p : pg.getPermissions()) {
    System.out.println(p.getId() + " enabled: " + p.getEnabled());
}

Summary

  • Permission represents atomic security privileges with unique IDs, descriptions, and optional implied-by hierarchies defined in core/src/main/java/hudson/security/Permission.java.
  • PermissionGroup organizes related permissions by owner class, implements Iterable<Permission> for enumeration, and maintains registration in core/src/main/java/hudson/security/PermissionGroup.java.
  • PermissionScope defines configuration boundaries through model objects (Jenkins, Item, Run, Computer) with recursive containment logic in core/src/main/java/hudson/security/PermissionScope.java.
  • The three components work together to create a hierarchical security model where permissions can be configured at broader scopes and inherited at narrower ones.
  • Plugin developers use static initialization patterns to register custom permissions that integrate with Jenkins' matrix-based authorization strategies.

Frequently Asked Questions

What is the difference between Permission and PermissionGroup in Jenkins?

A Permission represents a single security privilege like "Read" or "Configure," while a PermissionGroup acts as a container that collects all permissions belonging to the same owner class. According to the Jenkins source code in PermissionGroup.java, when a permission is constructed, it automatically registers itself with its group, which then maintains a sorted set of all related permissions and provides iteration and lookup capabilities.

How does PermissionScope determine where a permission can be configured?

PermissionScope defines the model object level—such as JENKINS, ITEM, or RUN—where a permission is applicable. As implemented in core/src/main/java/hudson/security/PermissionScope.java, scopes form a containment hierarchy where broader scopes (like ITEM_GROUP) contain narrower ones (like ITEM). The isContainedBy() method recursively checks these relationships, allowing permissions scoped to items to be configured at the Jenkins global level because JENKINS contains ITEM through the intermediate ITEM_GROUP scope.

What is the purpose of the impliedBy parameter in the Permission constructor?

The impliedBy parameter creates a permission hierarchy where holding a broader permission automatically grants implied narrower permissions. For example, if a user has Permission.ADMINISTER, they implicitly hold all other permissions that specify ADMINISTER as their impliedBy value. This mechanism, visible in lines 88-96 of Permission.java, reduces administrative overhead by allowing high-level roles to automatically inherit granular privileges without explicit configuration.

How do I check if a user has a specific permission in a Jenkins plugin?

Use the hasPermission(Permission) method available on Jenkins instances or model objects. For example, Jenkins.get().hasPermission(MyPlugin.CREATE_JOB) verifies that the current security context possesses the required permission. This check evaluates both direct permission grants and the impliedBy hierarchy, returning true if the user holds the specific permission or any permission that implies it according to the security realm configuration.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →