[{"data":1,"prerenderedAt":791},["ShallowReactive",2],{"/en-us/blog/the-ultimate-guide-to-least-privilege-access-with-gitlab":3,"navigation-en-us":38,"banner-en-us":438,"footer-en-us":448,"blog-post-authors-en-us-Fernando Diaz":690,"blog-related-posts-en-us-the-ultimate-guide-to-least-privilege-access-with-gitlab":704,"assessment-promotions-en-us":743,"next-steps-en-us":781},{"id":4,"title":5,"authorSlugs":6,"body":8,"categorySlug":9,"config":10,"content":14,"description":8,"extension":26,"isFeatured":12,"meta":27,"navigation":12,"path":28,"publishedDate":20,"seo":29,"stem":34,"tagSlugs":35,"__hash__":37},"blogPosts/en-us/blog/the-ultimate-guide-to-least-privilege-access-with-gitlab.yml","The Ultimate Guide To Least Privilege Access With Gitlab",[7],"fernando-diaz",null,"security",{"slug":11,"featured":12,"template":13},"the-ultimate-guide-to-least-privilege-access-with-gitlab",true,"BlogPost",{"title":15,"description":16,"authors":17,"heroImage":19,"date":20,"body":21,"category":9,"tags":22},"The ultimate guide to least privilege access with GitLab","This tutorial demonstrates how to achieve least privilege access using custom roles, security policies, compliance pipelines, branch protections, and more.",[18],"Fernando Diaz","https://res.cloudinary.com/about-gitlab-com/image/upload/v1750099438/Blog/Hero%20Images/Blog/Hero%20Images/built-in-security_built-in-security.jpeg_1750099438377.jpg","2024-03-06","The principle of least privilege ([PoLP](https://csrc.nist.gov/glossary/term/least_privilege)) is a concept in which a user's access rights should be limited to the bare minimum needed for them to complete the tasks required within their respective roles. By implementing PoLP you can enhance your organization's [security posture](https://csrc.nist.gov/glossary/term/security_posture), complementing zero trust, in the following ways:\n\n- **Reduction of attack surface:** If credentials are compromised, the\nbreach will be limited to only the paths where the compromised account has access.\n\n- **Protection against human error:** Users will not be able to perform\nactions that are not required for their role.\n\n- **Adherence to compliance:** Separation of duties and least privilege best\npractices are required for several compliance mandates such as SOC2 and\nHIPAA.\n\n- **Reduced system downtime:** By preventing everyone from accessing\ncritical parts of the software development lifecycle (SDLC), there is less likelihood of downtime.\n\nGitLab provides a variety of different features that allow you to customize the actions a user can perform which assist in the achievement of PoLP.\nThese features include:\n\n- **[Custom roles and granular security\npermissions](#custom-roles-and-granular-security-permissions):** Allows creation of roles with permissions that are specific to particular functions required by the organization.\n\n- **[Security policies](#security-policies):** Allows policies to be created\nthat prevent insecure code from being merged into production branches without approval, and run security scanners regardless of your pipeline definition.\n\n- **[Branch protections and Code\nOwners](#branch-protections-and-code-owners):** Imposes further restrictions on certain branches to control permissions such as who can merge, push, etc.\nto defined branches.\n\n- **[Compliance pipelines and\nframeworks](#compliance-pipelines-and-frameworks):** Identifies that your project has certain compliance requirements or needs additional oversight, enforcing a pipeline configuration to the projects on which it is applied.\n\nIn this blog post, you'll learn each of the features mentioned, how they improve your organization's security posture, as well as how to implement them.\n\nWatch my video, which introduces you to achieving PoLP with GitLab:\n\n\u003C!-- blank line -->\n\n\u003Cfigure class=\"video_container\">\n  \u003Ciframe src=\"https://www.youtube.com/embed/jvZ3eqWMeSY?si=DedSYiBNy2kTLJKo\" frameborder=\"0\" allowfullscreen=\"true\"> \u003C/iframe>\n\u003C/figure>\n\n\u003C!-- blank line -->\n\n## Custom roles and granular security permissions\n\nGitLab allows you to create [custom roles](https://docs.gitlab.com/ee/user/custom_roles.html), which apply additional permissions to base roles to meet the security needs of your organization. The available [base roles](https://docs.gitlab.com/ee/user/permissions.html#roles) are as follows:\n\n- Guest\n\n- Reporter\n\n- Developer\n\n- Maintainer\n\n- Owner\n\nEach base role applies a particular set of permissions to a user. Base roles apply different permissions for [group members](https://docs.gitlab.com/ee/user/permissions.html#group-members-permissions), [project members](https://docs.gitlab.com/ee/user/permissions.html#project-members-permissions), and in [project features](https://docs.gitlab.com/ee/user/permissions.html#project-features-permissions).\nFor example, the table below shows which roles can view the project [dependency list](https://docs.gitlab.com/ee/user/application_security/dependency_list/):\n\n| Base role    | Can view project dependency list     |\n| ---------- | ---------- |\n| Guest      | ❌       |\n| Reporter      | ❌       |\n| Developer      | ✅       |\n| Maintainer      | ✅       |\n| Owner       | ✅     |\n\n\u003Cbr>\u003C/br>\n\nThe dependency list also known as a software bill of materials ([SBOM](https://about.gitlab.com/blog/the-ultimate-guide-to-sboms/)), displays your project's dependencies and key details about those dependencies. It makes sense that only those actively working on a project should be able to see what dependencies are present to limit any exploitation of your application using its dependencies.\n\nHowever, there are cases in which a Guest may need to see the SBOM to assist the organization in [achieving compliance](https://www.whitehouse.gov/briefing-room/presidential-actions/2021/05/12/executive-order-on-improving-the-nations-cybersecurity/).\nBy using custom roles, a new role can be created with all the limited permissions of the Guest role, and additionally, the ability to view the project dependency list can be added. Therefore, we have a Guest assisting us with compliance with the least privileged access required for their job.\n\nWatch my video on custom roles and granular security permissions with\nGitLab:\n\n\u003C!-- blank line -->\n\n\u003Cfigure class=\"video_container\">\n  \u003Ciframe src=\"https://www.youtube.com/embed/WyrhkpO5WkI?si=4B4mNYNK9UyNrru8\" frameborder=\"0\" allowfullscreen=\"true\"> \u003C/iframe>\n\u003C/figure>\n\n\u003C!-- blank line -->\n\n### Granular permissions\n\nAs of the GitLab 16.8 release, the following granular permissions can be added to any base role:\n\n- Viewing project code\n\n- Viewing vulnerability reports\n\n- Changing the status of vulnerabilities\n\n- Viewing SBOMs\n\n- Approving merge requests\n\n- Managing project/group access tokens\n\n- Adding/removing group members\n\n- Archiving/unarchiving/removing projects\n\n- Admin Terraform state\n\nWe will continue to add [more granular permissions](https://docs.gitlab.com/ee/user/custom_roles/abilities.html)\nwith each GitLab release. You can learn more about our roadmap for this feature by referring to the [Granular Security Permissions\nEpic](https://gitlab.com/groups/gitlab-org/-/epics/10684) and provide feedback in the [customer feedback\nIssue](https://gitlab.com/gitlab-org/gitlab/-/issues/391760). You also have the ability to contribute to GitLab and [develop your own granular permissions](https://docs.gitlab.com/ee/development/permissions/custom_roles.html).\n\n### Implementation prerequisites\n\nThe requirements for implementing custom roles are as follows:\n\n- Owner role in the top-level group in which you are creating the custom\nrole\n\n- Administrator for the self-managed instance in which you are creating the\ncustom role\n\n- GitLab Ultimate tier in the top-level group\n\n- A [personal access token with the API\nscope](https://docs.gitlab.com/ee/user/profile/personal_access_tokens.html#create-a-personal-access-token)\n\nTo see custom roles in action requires:\n\n- a private project within the top-level group or its subgroups\n\n- a guest user within the private project\n\nWhen you enable a custom role for a user with the Guest role, that user has access to elevated permissions, and therefore:\n\n- is considered a billable user on self-managed GitLab\n\n- uses a seat on GitLab.com\n\n### Creating the custom role with granular permissions\n\nNow that you know the benefits of implementing custom roles with granular permissions, let's implement them within our GitLab instance:\n\n1. On the left sidebar, select **Search or go to**.\n    - In GitLab SaaS find and select the top-level group in which you want to create a custom role.\n    - In GitLab Self-Managed find and select **Admin Area**.\n2. Select **Settings > Roles and Permissions**.\n    - In GitLab Self-Managed use the top dropdown list to find and select the top-level group in which you want to create a custom role.\n3. Select **Add new role**.\n\n4. Under Base role to use as a template, select **Guest** for this tutorial.\n\n5. Under Role name, enter the custom role’s title.\n\n6. Under Permissions for the custom role, select **Read Vulnerability** for\nthis tutorial.\n\n7. Select **Create a new role**.\n\n![Create new role screen](https://res.cloudinary.com/about-gitlab-com/image/upload/v1750099455/Blog/Content%20Images/Blog/Content%20Images/image1_aHR0cHM6_1750099455072.png)\n\n\u003Ccenter>\u003Ci>Interface for creating a custom role\u003C/i>\u003C/center>\n\n\u003Cp>\u003C/p>\n\nAfter creating the role you should be able to see the new custom role along with its ID, Base role, and Permissions. Be sure to save the ID as it will be used when we assign the custom role to a guest user.\n\n![Custom role screen](https://res.cloudinary.com/about-gitlab-com/image/upload/v1750099455/Blog/Content%20Images/Blog/Content%20Images/image2_aHR0cHM6_1750099455073.png)\n\n\u003Ccenter>\u003Ci>Security Auditor role created\u003C/i>\u003C/center>\n\n\u003Cp>\u003C/p>\n\nNow we must assign the custom role to a group or project member. This can be done as follows:\n\n1. Invite a user as a direct member with the Guest role to your top-level\ngroup where the custom role was created.\n\n2. You can invite them to a sub-group or private project within the\ntop-level group as well.\n\n* The guest user should not be able to see any code within the project they\nhave been assigned to.\n\n* Open your terminal.\n\n3. Export the required environment variables:\n\n* Your [personal access token with API\nscope](https://docs.gitlab.com/ee/user/profile/personal_access_tokens.html#create-a-personal-access-token)\n\n```shell\n\n$ export TOKEN=glpat-XXXXXXXXXXXX\n\n$ echo $TOKEN\n\nglpat-XXXXXXXXXXXX\n\n\n```\n\n* The ID of the user we will be granting a custom role to. You can obtain\nthe user id by providing the username to the [User\nAPI](https://docs.gitlab.com/ee/api/users.html#list-users). For more information on using the GitLab API, see the [REST API documentation](https://docs.gitlab.com/ee/api/rest/).\n\n```shell\n\n$ curl \"https://gitlab.example.com/api/v4/users?username=fjdiaz\"\n\n[{\"id\":4710074,\"username\":\"fjdiaz\",\"name\":\"Fern\",\"state\":\"active\",\"locked\":false,\"avatar_url\":\"https://gitlab.com/uploads/-/system/user/avatar/4710074/avatar.png\",\"web_url\":\"https://gitlab.com/fjdiaz\"}]\n\n\n$ export USER_ID=4710074\n\n$ echo $USER_ID\n\n4710074\n\n```\n\n* The ID of the custom role. You can obtain the custom role ID from the ID\ncolumn in the [custom roles\nUI](https://docs.gitlab.com/ee/user/custom_roles.html#gitlab-saas) or the [member roles\nAPI](https://docs.gitlab.com/ee/api/member_roles.html#add-a-member-role-to-a-group).\n\n```shell\n\n$ export CUSTOM_ROLE_ID=1000782\n\n$ echo $CUSTOM_ROLE_ID\n\n1000782\n\n```\n\n* The ID of your group or project. You can obtain the group id from the\n[group UI](https://docs.gitlab.com/ee/user/group/#get-the-group-id) or using the [groups API](https://docs.gitlab.com/ee/api/groups.html). You can obtain the project ID from the [project\nUI](https://docs.gitlab.com/ee/user/project/working_with_projects.html#access-the-project-overview-page-by-using-the-project-id)\nor using the [projects API](https://docs.gitlab.com/ee/api/projects.html).\n\n```shell\n\n$ export GROUP_ID=10087220\n\n$ echo $GROUP_ID\n\n10087220\n\n\n$ export PROJECT_ID=45738177\n\n$ echo $PROJECT_ID\n\n45738177\n\n```\n\n4. Associate the guest user with the custom role using the appropriate\n[group or project\nAPIs](https://docs.gitlab.com/ee/api/members.html#edit-a-member-of-a-group-or-project).\n\n* If the user just needs to role in a project, update the project\nmembership:\n\n```text\n\n\"Authorization: Bearer $TOKEN\" --data '{\"member_role_id\": $CUSTOM_ROLE_ID,\n\"access_level\": 10}'\n\"https://gitlab.example.com/api/v4/projects/$PROJECT_ID/members/$USER_ID\"\n\n```\n\n* If the user just needs to role in a group, update the group membership:\n\n```shell\n\n$ curl --request PUT --header \"Content-Type: application/json\" --header\n\"Authorization: Bearer $TOKEN\" --data '{\"member_role_id\": $CUSTOM_ROLE_ID,\n\"access_level\": 10}'\n\"https://gitlab.example.com/api/v4/groups/$GROUP_ID/members/$USER_ID\"\n\n```\n\nNow that the custom role has been applied to a guest user, when they login, they can see the Vulnerability dashboard present in the Secure tab. Notice, however, that they are still not allowed to see the source code.\n\nThis is useful because it allows users to audit the system without being able to make changes to the code base, which applies the PoLP for those auditing the system for vulnerabilities.\n\n## Security policies\n\nGitLab provides [security policies](https://docs.gitlab.com/ee/user/application_security/policies/) to help you achieve least privilege access. There are two different types of security policies provided by GitLab:\n\n- [Scan Execution\npolicies](https://docs.gitlab.com/ee/user/application_security/policies/scan-execution-policies.html)\nallow project maintainers and administrators the confidence of knowing that the scans they set up have not been changed, altered, or disabled.\n\n- [Merge Request Approval\npolicies](https://docs.gitlab.com/ee/user/application_security/policies/scan-result-policies.html)\nprevent insecure code from being merged into production without appropriate approval.\n\nSome examples of how both policy types can be used in unison to provide least privilege access are as follows:\n\n- remove the ability for developers to disable security scanners\n\n- remove the ability for developers to merge insecure code\n\nPolicies are stored in a separate repo from the project they are being applied to called the Security Policy Project (SPP). This allows for separate permissions to be set to the SPP vs. the application repo, thus strengthening your ability to separate duties and apply PoLP.\n\n![Security policy hierarchy](https://res.cloudinary.com/about-gitlab-com/image/upload/v1750099455/Blog/Content%20Images/Blog/Content%20Images/image19_aHR0cHM6_1750099455074.png)\n\n\u003Ccenter>\u003Ci>Security policy hierarchy\u003C/i>\u003C/center>\u003Cp>\u003C/p>\n\nTo enforce the policies contained in an SPP you link it to a project, subgroup, group, or multiples of each. An SPP can contain multiple policies but they are enforced together. An SPP enforced on a group or subgroup applies to everything below the hierarchy, including all subgroups and their projects.\n\nSecurity policies can be managed via the policy management UI as well as via yaml. Using the policy editor you can create, edit, and delete policies.\n\n![Policy management interface](https://res.cloudinary.com/about-gitlab-com/image/upload/v1750099455/Blog/Content%20Images/Blog/Content%20Images/image20_aHR0cHM6_1750099455076.png)\n\n\u003Ccenter>\u003Ci>Policy management interface\u003C/i>\u003C/center>\u003Cp>\u003C/p>\n\nFeel free to leverage the [Simple Notes demo environment](https://gitlab.com/gitlab-da/tutorials/security-and-governance/devsecops/simply-vulnerable-notes)\nto try this yourself by following the provided [DevSecOps tutorial](https://gitlab-de.gitlab.io/tutorials/security-and-governance/devsecops/simply-vulnerable-notes/).\n\n### Creating a Scan Execution policy\n\nNow let's take a look at how to create a Scan Execution policy. Before getting started make sure you have met the following criteria:\n\n- GitLab Ultimate tier in the top-level group\n\n- Owner role to create/assign an SPP\n\n- Developer role or greater to create/edit/delete individual security\npolicies\n\nWe will be creating a policy that automatically runs a SAST scan with each pipeline, regardless of the SAST template is defined within the gitlab-ci.yml:\n\n1. On the left sidebar, select **Search or go to** and search for the\nproject to which you wish to add a policy.\n\n2. On the project left sidebar, go to **Secure > Policies**.\n\n3. Select **New policy**.\n\n4.  In the **Scan Execution Policy** section, select **Select policy**.\n\n5. Complete the fields:\n    - **Name:** The name of the policy\n    - **Description:** The description of the Policy\n    - **Policy status:** Whether it is enabled or not\n    - **Actions:** What actions to take when the defined conditions are met\n\n![Scan Execution policy actions](https://res.cloudinary.com/about-gitlab-com/image/upload/v1750099455/Blog/Content%20Images/Blog/Content%20Images/image15_aHR0cHM6_1750099455077.png)\n\n  \u003Ccenter>\u003Ci>Scan Execution policy actions\u003C/i>\u003C/center>\u003Cp>\u003C/p>\n\n  - **Conditions:** Conditions which must be met (a pipeline is triggered or on a set schedule) in order for an action to take place.\n\n    ![Scan Execution policy conditions](https://res.cloudinary.com/about-gitlab-com/image/upload/v1750099455/Blog/Content%20Images/Blog/Content%20Images/image8_aHR0cHM6_1750099455078.png)\n  \u003Ccenter>\u003Ci>Scan Execution policy conditions\u003C/i>\u003C/center>\u003Cp>\u003C/p>\n\n- Press the **Configure with a merge request** button.\n\nNow that the policy has been created, all we need to do is run a pipeline to see that SAST will be present even if it is not defined in the\n.gitlab-ci.yml.\n\n### Creating a Merge Request Approval policy\n\nNow let's take a look at how to create a Merge Request Approval policy.\nBefore getting started make sure you have met the following criteria:\n\n- GitLab Ultimate tier in the top-level group\n\n- Owner role to create/assign an SPP\n\n- Developer role or greater to create/edit/delete individual security\npolicies\n\n- Security scanners added to project\n\nWe will be creating a policy that requires approval from project maintainers if any security scanner detects a vulnerability when compared with any branch:\n\n1. On the left sidebar, select **Search or go to** and search for the\nproject to which you wish to add a policy.\n\n2. On the project left sidebar, go to **Secure > Policies**\n\n3. Select **New policy**\n\n4. In the **Merge Request Approval policy** section, select **Select\npolicy**.\n\n5. Complete the fields:\n    - **Name:** The name of the policy\n    - **Description:** The description of the policy\n    - **Policy status:** Whether it is enabled or not\n    - **Rules:** The conditions which must be met for an action (require approval) to take place.\n\n![Merge Request Approval policy rules](https://res.cloudinary.com/about-gitlab-com/image/upload/v1750099455/Blog/Content%20Images/Blog/Content%20Images/image17_aHR0cHM6_1750099455079.png)\n\n\u003Ccenter>\u003Ci>Merge Request Approval policy rules\u003C/i>\u003C/center>\u003Cp>\u003C/p>\n\n    - **Actions:** The action to be taken whenever the conditions in the rules (defined vulnerabilities/licenses detected) are met.\n\n![Merge Request Approval  policy actions](https://res.cloudinary.com/about-gitlab-com/image/upload/v1750099455/Blog/Content%20Images/Blog/Content%20Images/image6_aHR0cHM6_1750099455080.png)\n\n\u003Ccenter>\u003Ci>Merge Request Approval  policy actions\u003C/i>\u003C/center>\u003Cp>\u003C/p>\n\n    - **Override project approval settings:** If selected, the following choices will overwrite project settings but only affect the branches selected in the policy.\n\n![Merge Request Approval policy approval settings](https://res.cloudinary.com/about-gitlab-com/image/upload/v1750099455/Blog/Content%20Images/Blog/Content%20Images/image21_aHR0cHM6_1750099455081.png)\n\n\u003Ccenter>\u003Ci>Merge Request Approval policy approval settings\u003C/i>\u003C/center>\u003Cp>\u003C/p>\n\n6. Press the **Configure with a merge request** button.\n\nNow that the policy has been created, all we need to do is run a pipeline and if SAST detects any vulnerabilities then approvals will be required from the selected approver before the code change can be merged. Merge Request\nApproval policies can be used with all GitLab security scanners, including license scanning.\n\n![Merge Request Approval policies blocking code from being merged in an\nMR](https://res.cloudinary.com/about-gitlab-com/image/upload/v1750099455/Blog/Content%20Images/Blog/Content%20Images/image3_aHR0cHM6_1750099455082.png)\n\n\u003Ccenter>\u003Ci>Merge Request Approval policies blocking code from being merged in an MR\u003C/i>\u003C/center>\u003Cp>\u003C/p>\n\n## Branch protections and Code Owners\n\n[Branch protections](https://docs.gitlab.com/ee/user/project/protected_branches.html)\nallow you to impose additional restrictions on particular branches within your repository. This further strengthens the PoLP for the interactions on a particular set of branches.\n\nFor example, a protected branch can control:\n\n- which users can merge into the branch\n\n- which users can push to the branch\n\n- if users can force push to the branch\n\n- if changes to files listed in the CODEOWNERS file can be pushed directly\nto the branch\n\n- which users can unprotect the branch\n\n### Applying branch protections\n\nBranch protections are available in all tiers and offerings of GitLab.\nBranch protections can be applied to a single project or a group of projects. You can apply branch protections for required roles to push and merge as follows:\n\n1. On the left sidebar, select **Search or go to** and find your project or\ngroup.\n\n2. Select **Settings > Repository**.\n\n3. Expand **Protected branches**.\n\n4. Select **Add protected branch**.\n    - For groups, from the **Branch** text box, type the branch name or a wildcard.\n    - For projects, from the **Branch** dropdown list, select the branch you want to protect.\n5. From the **Allowed to merge** list, select a role that can merge into\nthis branch.\n\n6. From the **Allowed to push and merge** list, select a role that can push\nto this branch.\n\n7. Select **Protect**.\n\nYou should now see the protected branch added to the list.\n\n![Protected branches settings](https://res.cloudinary.com/about-gitlab-com/image/upload/v1750099455/Blog/Content%20Images/Blog/Content%20Images/image14_aHR0cHM6_1750099455082.png)\n\n\u003Ccenter>\u003Ci>Protected branches settings\u003C/i>\u003C/center>\u003Cp>\u003C/p>\n\nThe Owner role is required to add branch protections to a group and the\nMaintainer role or greater is required to add branch protections to a project.\n\n### Code Owners\n\nIf you want to further limit what files developers can perform changes on, one of the best features to implement is [Code\nOwners](https://docs.gitlab.com/ee/user/project/codeowners/). Code Owners allows you to define who has the expertise for specific parts of your project’s codebase. Defining the owners of files and directories in Code\nOwners will:\n\n- require owners to approve changes as well as merge requests before they\nmerge into a protected branch\n\n- identify owners by displaying the Code Owner names on the files and\ndirectories they own\n\nTo set up Code Owners, follow these steps:\n\n1. Create a CODEOWNERS file in your preferred location.\n\n2. Define some rules in the file following the Code Owners syntax reference.\nYou can configure all eligible approvers' approval rules and require Code\nOwner approval on a protected branch.\n\n3. Commit your changes, and push them up to GitLab.\n\nNow, when looking at files, you can see who the Code Owners are for a particular file.\n\n![Code Owners displayed for file](https://res.cloudinary.com/about-gitlab-com/image/upload/v1750099455/Blog/Content%20Images/Blog/Content%20Images/image5_aHR0cHM6_1750099455083.png)\n\n\u003Ccenter>\u003Ci>Code Owners displayed for file\u003C/i>\u003C/center>\u003Cp>\u003C/p>\n\nIf you implement Code Owner approvals, then when creating a merge request, the Code Owners must approve before the code can be merged.\n\n![Code Owners approvals](https://res.cloudinary.com/about-gitlab-com/image/upload/v1750099455/Blog/Content%20Images/Blog/Content%20Images/image7_aHR0cHM6_1750099455084.png)\n\n\u003Ccenter>\u003Ci>Code Owners approvals\u003C/i>\u003C/center>\u003Cp>\u003C/p>\n\n### Additional approval settings\n\nThere are additional approval settings that can be applied before code can be committed with a merge request. These additional approval settings are as follows:\n\n- prevent approval by author\n\n- prevent approvals by users who add commits\n\n- prevent editing approval rules in merge requests\n\n- require user re-authentication (password or SAML) to approve\n\nAdditionally, whenever a commit is added, you can:\n\n- keep approvals\n\n- remove all approvals\n\n- remove approvals by Code Owners if their files changed\n\n![Additional Approval settings](https://res.cloudinary.com/about-gitlab-com/image/upload/v1750099455/Blog/Content%20Images/Blog/Content%20Images/image12_aHR0cHM6_1750099455084.png)\n\n\u003Ccenter>\u003Ci>Additional Approval settings\u003C/i>\u003C/center>\u003Cp>\u003C/p>\n\nTo configure additional approval settings you can perform the following steps:\n\n1. On the left sidebar, select **Search or go to** and find your project.\n\n2. Select **Settings > Merge requests**.\n\n3. Scroll down to the **Merge request approvals** section.\n\n4. Under **Approval settings** select the approval settings you would like\nto apply.\n\n5. Press the **Save changes** button.\n\nThese can also be applied to your top-level group by performing the following steps:\n\n1. On the left sidebar, select **Search or go to** and find your top-level\ngroup.\n\n2. Select **Settings > General**.\n\n3. Expand the **Merge request approvals** section.\n\n4. Under **Approval settings** select the approval settings you would like\nto apply.\n\n5. Press the **Save changes** button.\n\nBy leveraging these approval settings you can make sure that code always obtains oversight by a person who was not involved in creating the code, thereby preventing a conflict of interest.\n\n## Compliance pipelines and frameworks\n\nYou can create a compliance framework that is a label to identify that your project has certain compliance requirements or needs additional oversight.\nThe label can optionally enforce compliance pipeline configuration to the projects on which it is applied.\n\nFeel free to leverage the [Compliance Frameworks\nDemo](https://gitlab.com/gitlab-da/tutorials/security-and-governance/compliance-frameworks)\ngroup to see an example of compliance frameworks and their usage.\n\n### Create a compliance pipeline\n\nTo create a compliance pipeline, all you need to do is create a new project which will store a `.gitlab-ci.yml` file that we wish to use in another project. The new compliance pipeline project can have separate permissions from the project to which you will apply it. This is beneficial because it prevents developers from making changes to pipelines that must run.\n\nYou can see I have created the following [pipeline definition](https://gitlab.com/gitlab-da/tutorials/security-and-governance/compliance-frameworks)\nwhich:\n\n- runs the SAST security scanner\n\n- runs the secret detection scanner\n\n- runs a SOC2 compliance job\n\n- runs the original pipeline defined in the project to which we will apply\nthis pipeline. This allows developers to focus on the actual application development and the compliance team to focus on defining the SOC2 rules.\n\n### Create and apply a compliance framework\n\nNow that the compliance pipeline for SOC2 has been defined, we must define a compliance framework and apply it to our project. In this case, I will apply it to my Accounting Department project.\n\nTo create a compliance framework label, follow these steps:\n\n1. On the left sidebar, select **Search or go to** and find your group.\n\n2. Select **Settings > General**.\n\n3. Expand the **Compliance frameworks** section.\n\n4. Click the **Add framework** button.\n\n5. Create a new compliance framework and populate the following sections:\n    - **Name:** The name of your compliance framework\n    - **Description:** A description of your compliance framework\n    - **Compliance pipeline configuration:** The location of the compliance pipeline to run.\n    - **Background color:** A color for the compliance framework label\n\n![PoLP - image 15](https://res.cloudinary.com/about-gitlab-com/image/upload/v1750099455/Blog/Content%20Images/Blog/Content%20Images/image11_aHR0cHM6_1750099455085.png)\n\n    \u003Ccenter>\u003Ci>Creating a compliance framework\u003C/i>\u003C/center>\u003Cp>\u003C/p>\n\n6. Press the **Add framework** button.\n\nAnd now you should see your newly added framework under active compliance frameworks.\n\n![Active compliance frameworks](https://res.cloudinary.com/about-gitlab-com/image/upload/v1750099455/Blog/Content%20Images/Blog/Content%20Images/image9_aHR0cHM6_1750099455085.png)\n\n\u003Ccenter>\u003Ci>Active compliance frameworks\u003C/i>\u003C/center>\u003Cp>\u003C/p>\n\nNow let’s go ahead and assign this compliance label to our Accounting\nDepartment project:\n\n1. On the left sidebar, select **Search or go to** and find your project.\n\n2. Select **Settings > General**.\n\n3. Expand **Compliance frameworks**.\n\n4. Select the compliance framework created above.\n\n![Adding a compliance framework](https://res.cloudinary.com/about-gitlab-com/image/upload/v1750099455/Blog/Content%20Images/Blog/Content%20Images/image4_aHR0cHM6_1750099455086.png)\n\n\u003Ccenter>\u003Ci>Adding a compliance framework\u003C/i>\u003C/center>\n\n5. Select **Save changes**.\n\nThe project should now have the compliance framework label applied.\n\n![Project running a compliance pipeline](https://res.cloudinary.com/about-gitlab-com/image/upload/v1750099455/Blog/Content%20Images/Blog/Content%20Images/image10_aHR0cHM6_1750099455086.png)\n\n\u003Ccenter>\u003Ci>Project running a compliance pipeline\u003C/i>\u003C/center>\u003Cp>\u003C/p>\n\nThis enables separation of duties and prevents compliance pipelines from being altered by those without permissions.\n\nSecurity Policy Scope and Pipeline Execution\n\nOver the past several releases, GitLab has introduced two experimental features, Security Policy Scope and Pipeline Execution, to make it even easier to adhere to PoLP. These features are very similar to Compliance\nPipelines and Compliance Frameworks and can be managed from GitLab’s security policy UI.\n\n**Note:** These features are currently considered experimental. An experiment is a feature that is in the process of being developed. It is not production ready. We encourage users to try experimental features and provide feedback.\n\nThe [pipeline execution policy action](https://docs.gitlab.com/ee/user/application_security/policies/scan-execution-policies.html#pipeline-execution-policy-action)\nintroduces a new scan action type into Scan Execution policies for creating and enforcing custom CI in your target development projects. You can execute a custom pipeline along with your current pipeline. This allows you to enforce compliance by always forcing particular actions to run that are not just security scanners and that cannot be overwritten by those without permissions.\n\n![Pipeline Execution policy scope selection](https://res.cloudinary.com/about-gitlab-com/image/upload/v1750099455/Blog/Content%20Images/Blog/Content%20Images/image18_aHR0cHM6_1750099455087.png)\n\n\u003Ccenter>\u003Ci>Pipeline Execution policy scope selection - insert code block\u003C/i>\u003C/center>\u003Cp>\u003C/p>\n\n![Pipeline Execution policy scope selection](https://res.cloudinary.com/about-gitlab-com/image/upload/v1750099455/Blog/Content%20Images/Blog/Content%20Images/image13_aHR0cHM6_1750099455087.png)\n\n\u003Ccenter>\u003Ci>Pipeline Execution policy scope selection - link existing CI file\u003C/i>\u003C/center>\u003Cp>\u003C/p>\n\nThe [Security policy scope](https://docs.gitlab.com/ee/user/application_security/policies/scan-result-policies.html#security-policy-scopes)\ncan be applied to either Merge Request Approval or Scan Execution policies.\nScopes enable you to administer policies with a particular scope, meaning you can:\n\n- Include only projects containing a compliance framework label\n\n- Include or exclude selected projects from enforcement\n\nTo enable these experimental features, follow these steps:\n\n1. On the left sidebar, select **Search or go to** and find your top-level\ngroup.\n\n2. Select **Settings > General**.\n\n3. Expand **Permissions and group features**.\n\n4. Scroll down to the **Security policy management** section.\n\n5. Select the following checkboxes\n\n**Security policy pipeline execution action:** Create and enforce custom CI jobs and scripts using this new policy action.\n\n6. **Security policy scopes:** Granularly scope each policy you create to\nprojects containing a compliance framework label, or a list of projects.\n\n7. **Enforce for all subgroups (optional):** Subgroups cannot change these\nsettings.\n\n8. Scroll down to the **Experiment and Beta features** section.\n\n9. Select the **Use Experiment and Beta features** checkbox.\n\n10. Scroll down and press the **Save changes** button.\n\nNow, whenever you are creating a security policy, the following options will be available:\n\n- Inserting a CI code block (Scan Execution policy only)\n\n- Loading CI/CD code from file (Scan Execution policy only)\n\n- Linking an existing CI file from another project (Scan Execution policy\nonly)\n\n- Scoping a policy to projects with selected compliance framework (Group\nLevel only)\n\n- Scoping a policy towards specific projects (Group Level only)\n\n- Scoping a policy towards all projects in group (Group Level only)\n\nTo learn more about these features, check out the following documentation:\n\n- [Pipeline Execution Policy action (Scan Execution\npolicy)](https://docs.gitlab.com/ee/user/application_security/policies/scan-execution-policies.html#pipeline-execution-policy-action)\n\n- [Security Policy Scopes (Scan Execution\npolicy)](https://docs.gitlab.com/ee/user/application_security/policies/scan-execution-policies.html#security-policy-scopes)\n\n- [Security Policy Scopes (Merge Request Approval\npolicy)](https://docs.gitlab.com/ee/user/application_security/policies/scan-result-policies.html#security-policy-scopes)\n\n## Additional resources\n\nThanks for reading! These are some of the ways that GitLab allows you to strengthen your organization's security posture through the enablement of\nPoLP. To learn more about GitLab and the other ways we can strengthen your organization's security throughout all parts of the SDLC, check out the following links:\n\n- [GitLab Security and\nCompliance](https://about.gitlab.com/solutions/application-security-testing/)\n\n- [GitLab Application Security\nDocumentation](https://docs.gitlab.com/ee/user/application_security/)\n\n- [GitLab DevSecOps Demo\nProject](https://gitlab.com/gitlab-da/tutorials/security-and-governance/devsecops/simply-vulnerable-notes)\n\n- [GitLab DevSecOps\nTutorial](https://gitlab-de.gitlab.io/tutorials/security-and-governance/devsecops/simply-vulnerable-notes/)\n\n- [GitLab Roles and Permissions\nDocumentation](https://docs.gitlab.com/ee/user/permissions.html)\n\n- [GitLab Custom Roles\nDocumentation](https://docs.gitlab.com/ee/user/custom_roles.html)\n\n- [GitLab Security Policies\nDocumentation](https://docs.gitlab.com/ee/user/application_security/policies/)\n\n- [GitLab Compliance Frameworks\nDocumentation](https://docs.gitlab.com/ee/user/group/compliance_frameworks.html)\n\n- [GitLab Code Owners\nDocumentation](https://docs.gitlab.com/ee/user/project/codeowners/)\n\n- [GitLab Branch Protections\nDocumentation](https://docs.gitlab.com/ee/user/project/protected_branches.html)",[23,24,9,25],"zero trust","tutorial","features","yml",{},"/en-us/blog/the-ultimate-guide-to-least-privilege-access-with-gitlab",{"ogTitle":15,"ogImage":19,"ogDescription":16,"ogSiteName":30,"noIndex":31,"ogType":32,"ogUrl":33,"title":15,"canonicalUrls":33,"description":16},"https://about.gitlab.com",false,"article","https://about.gitlab.com/blog/the-ultimate-guide-to-least-privilege-access-with-gitlab","en-us/blog/the-ultimate-guide-to-least-privilege-access-with-gitlab",[36,24,9,25],"zero-trust","gA00UQ7CxXz_PW2cR3YCOx82nZR6r0w3tBjVL4n5kXo",{"data":39},{"logo":40,"freeTrial":45,"sales":50,"login":55,"items":60,"search":368,"minimal":399,"duo":418,"pricingDeployment":428},{"config":41},{"href":42,"dataGaName":43,"dataGaLocation":44},"/","gitlab logo","header",{"text":46,"config":47},"Get free trial",{"href":48,"dataGaName":49,"dataGaLocation":44},"https://gitlab.com/-/trial_registrations/new?glm_source=about.gitlab.com&glm_content=default-saas-trial/","free trial",{"text":51,"config":52},"Talk to sales",{"href":53,"dataGaName":54,"dataGaLocation":44},"/sales/","sales",{"text":56,"config":57},"Sign in",{"href":58,"dataGaName":59,"dataGaLocation":44},"https://gitlab.com/users/sign_in/","sign in",[61,88,183,188,289,349],{"text":62,"config":63,"cards":65},"Platform",{"dataNavLevelOne":64},"platform",[66,72,80],{"title":62,"description":67,"link":68},"The intelligent orchestration platform for DevSecOps",{"text":69,"config":70},"Explore our Platform",{"href":71,"dataGaName":64,"dataGaLocation":44},"/platform/",{"title":73,"description":74,"link":75},"GitLab Duo Agent Platform","Agentic AI for the entire software lifecycle",{"text":76,"config":77},"Meet GitLab Duo",{"href":78,"dataGaName":79,"dataGaLocation":44},"/gitlab-duo-agent-platform/","gitlab duo agent platform",{"title":81,"description":82,"link":83},"Why GitLab","See the top reasons enterprises choose GitLab",{"text":84,"config":85},"Learn more",{"href":86,"dataGaName":87,"dataGaLocation":44},"/why-gitlab/","why gitlab",{"text":89,"left":12,"config":90,"link":92,"lists":96,"footer":165},"Product",{"dataNavLevelOne":91},"solutions",{"text":93,"config":94},"View all Solutions",{"href":95,"dataGaName":91,"dataGaLocation":44},"/solutions/",[97,121,144],{"title":98,"description":99,"link":100,"items":105},"Automation","CI/CD and automation to accelerate deployment",{"config":101},{"icon":102,"href":103,"dataGaName":104,"dataGaLocation":44},"AutomatedCodeAlt","/solutions/delivery-automation/","automated software delivery",[106,110,113,117],{"text":107,"config":108},"CI/CD",{"href":109,"dataGaLocation":44,"dataGaName":107},"/solutions/continuous-integration/",{"text":73,"config":111},{"href":78,"dataGaLocation":44,"dataGaName":112},"gitlab duo agent platform - product menu",{"text":114,"config":115},"Source Code Management",{"href":116,"dataGaLocation":44,"dataGaName":114},"/solutions/source-code-management/",{"text":118,"config":119},"Automated Software Delivery",{"href":103,"dataGaLocation":44,"dataGaName":120},"Automated software delivery",{"title":122,"description":123,"link":124,"items":129},"Security","Deliver code faster without compromising security",{"config":125},{"href":126,"dataGaName":127,"dataGaLocation":44,"icon":128},"/solutions/application-security-testing/","security and compliance","ShieldCheckLight",[130,134,139],{"text":131,"config":132},"Application Security Testing",{"href":126,"dataGaName":133,"dataGaLocation":44},"Application security testing",{"text":135,"config":136},"Software Supply Chain Security",{"href":137,"dataGaLocation":44,"dataGaName":138},"/solutions/supply-chain/","Software supply chain security",{"text":140,"config":141},"Software Compliance",{"href":142,"dataGaName":143,"dataGaLocation":44},"/solutions/software-compliance/","software compliance",{"title":145,"link":146,"items":151},"Measurement",{"config":147},{"icon":148,"href":149,"dataGaName":150,"dataGaLocation":44},"DigitalTransformation","/solutions/visibility-measurement/","visibility and measurement",[152,156,160],{"text":153,"config":154},"Visibility & Measurement",{"href":149,"dataGaLocation":44,"dataGaName":155},"Visibility and Measurement",{"text":157,"config":158},"Value Stream Management",{"href":159,"dataGaLocation":44,"dataGaName":157},"/solutions/value-stream-management/",{"text":161,"config":162},"Analytics & Insights",{"href":163,"dataGaLocation":44,"dataGaName":164},"/solutions/analytics-and-insights/","Analytics and insights",{"title":166,"items":167},"GitLab for",[168,173,178],{"text":169,"config":170},"Enterprise",{"href":171,"dataGaLocation":44,"dataGaName":172},"/enterprise/","enterprise",{"text":174,"config":175},"Small Business",{"href":176,"dataGaLocation":44,"dataGaName":177},"/small-business/","small business",{"text":179,"config":180},"Public Sector",{"href":181,"dataGaLocation":44,"dataGaName":182},"/solutions/public-sector/","public sector",{"text":184,"config":185},"Pricing",{"href":186,"dataGaName":187,"dataGaLocation":44,"dataNavLevelOne":187},"/pricing/","pricing",{"text":189,"config":190,"link":192,"lists":196,"feature":276},"Resources",{"dataNavLevelOne":191},"resources",{"text":193,"config":194},"View all resources",{"href":195,"dataGaName":191,"dataGaLocation":44},"/resources/",[197,230,248],{"title":198,"items":199},"Getting started",[200,205,210,215,220,225],{"text":201,"config":202},"Install",{"href":203,"dataGaName":204,"dataGaLocation":44},"/install/","install",{"text":206,"config":207},"Quick start guides",{"href":208,"dataGaName":209,"dataGaLocation":44},"/get-started/","quick setup checklists",{"text":211,"config":212},"Learn",{"href":213,"dataGaLocation":44,"dataGaName":214},"https://university.gitlab.com/","learn",{"text":216,"config":217},"Product documentation",{"href":218,"dataGaName":219,"dataGaLocation":44},"https://docs.gitlab.com/","product documentation",{"text":221,"config":222},"Best practice videos",{"href":223,"dataGaName":224,"dataGaLocation":44},"/getting-started-videos/","best practice videos",{"text":226,"config":227},"Integrations",{"href":228,"dataGaName":229,"dataGaLocation":44},"/integrations/","integrations",{"title":231,"items":232},"Discover",[233,238,243],{"text":234,"config":235},"Customer success stories",{"href":236,"dataGaName":237,"dataGaLocation":44},"/customers/","customer success stories",{"text":239,"config":240},"Blog",{"href":241,"dataGaName":242,"dataGaLocation":44},"/blog/","blog",{"text":244,"config":245},"Remote",{"href":246,"dataGaName":247,"dataGaLocation":44},"https://handbook.gitlab.com/handbook/company/culture/all-remote/","remote",{"title":249,"items":250},"Connect",[251,256,261,266,271],{"text":252,"config":253},"GitLab Services",{"href":254,"dataGaName":255,"dataGaLocation":44},"/services/","services",{"text":257,"config":258},"Community",{"href":259,"dataGaName":260,"dataGaLocation":44},"/community/","community",{"text":262,"config":263},"Forum",{"href":264,"dataGaName":265,"dataGaLocation":44},"https://forum.gitlab.com/","forum",{"text":267,"config":268},"Events",{"href":269,"dataGaName":270,"dataGaLocation":44},"/events/","events",{"text":272,"config":273},"Partners",{"href":274,"dataGaName":275,"dataGaLocation":44},"/partners/","partners",{"backgroundColor":277,"textColor":278,"text":279,"image":280,"link":284},"#2f2a6b","#fff","Insights for the future of software development",{"altText":281,"config":282},"the source promo card",{"src":283},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1758208064/dzl0dbift9xdizyelkk4.svg",{"text":285,"config":286},"Read the latest",{"href":287,"dataGaName":288,"dataGaLocation":44},"/the-source/","the source",{"text":290,"config":291,"lists":293},"Company",{"dataNavLevelOne":292},"company",[294],{"items":295},[296,301,307,309,314,319,324,329,334,339,344],{"text":297,"config":298},"About",{"href":299,"dataGaName":300,"dataGaLocation":44},"/company/","about",{"text":302,"config":303,"footerGa":306},"Jobs",{"href":304,"dataGaName":305,"dataGaLocation":44},"/jobs/","jobs",{"dataGaName":305},{"text":267,"config":308},{"href":269,"dataGaName":270,"dataGaLocation":44},{"text":310,"config":311},"Leadership",{"href":312,"dataGaName":313,"dataGaLocation":44},"/company/team/e-group/","leadership",{"text":315,"config":316},"Team",{"href":317,"dataGaName":318,"dataGaLocation":44},"/company/team/","team",{"text":320,"config":321},"Handbook",{"href":322,"dataGaName":323,"dataGaLocation":44},"https://handbook.gitlab.com/","handbook",{"text":325,"config":326},"Investor relations",{"href":327,"dataGaName":328,"dataGaLocation":44},"https://ir.gitlab.com/","investor relations",{"text":330,"config":331},"Trust Center",{"href":332,"dataGaName":333,"dataGaLocation":44},"/security/","trust center",{"text":335,"config":336},"AI Transparency Center",{"href":337,"dataGaName":338,"dataGaLocation":44},"/ai-transparency-center/","ai transparency center",{"text":340,"config":341},"Newsletter",{"href":342,"dataGaName":343,"dataGaLocation":44},"/company/contact/#contact-forms","newsletter",{"text":345,"config":346},"Press",{"href":347,"dataGaName":348,"dataGaLocation":44},"/press/","press",{"text":350,"config":351,"lists":352},"Contact us",{"dataNavLevelOne":292},[353],{"items":354},[355,358,363],{"text":51,"config":356},{"href":53,"dataGaName":357,"dataGaLocation":44},"talk to sales",{"text":359,"config":360},"Support portal",{"href":361,"dataGaName":362,"dataGaLocation":44},"https://support.gitlab.com","support portal",{"text":364,"config":365},"Customer portal",{"href":366,"dataGaName":367,"dataGaLocation":44},"https://customers.gitlab.com/customers/sign_in/","customer portal",{"close":369,"login":370,"suggestions":377},"Close",{"text":371,"link":372},"To search repositories and projects, login to",{"text":373,"config":374},"gitlab.com",{"href":58,"dataGaName":375,"dataGaLocation":376},"search login","search",{"text":378,"default":379},"Suggestions",[380,382,386,388,392,396],{"text":73,"config":381},{"href":78,"dataGaName":73,"dataGaLocation":376},{"text":383,"config":384},"Code Suggestions (AI)",{"href":385,"dataGaName":383,"dataGaLocation":376},"/solutions/code-suggestions/",{"text":107,"config":387},{"href":109,"dataGaName":107,"dataGaLocation":376},{"text":389,"config":390},"GitLab on AWS",{"href":391,"dataGaName":389,"dataGaLocation":376},"/partners/technology-partners/aws/",{"text":393,"config":394},"GitLab on Google Cloud",{"href":395,"dataGaName":393,"dataGaLocation":376},"/partners/technology-partners/google-cloud-platform/",{"text":397,"config":398},"Why GitLab?",{"href":86,"dataGaName":397,"dataGaLocation":376},{"freeTrial":400,"mobileIcon":405,"desktopIcon":410,"secondaryButton":413},{"text":401,"config":402},"Start free trial",{"href":403,"dataGaName":49,"dataGaLocation":404},"https://gitlab.com/-/trials/new/","nav",{"altText":406,"config":407},"Gitlab Icon",{"src":408,"dataGaName":409,"dataGaLocation":404},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1758203874/jypbw1jx72aexsoohd7x.svg","gitlab icon",{"altText":406,"config":411},{"src":412,"dataGaName":409,"dataGaLocation":404},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1758203875/gs4c8p8opsgvflgkswz9.svg",{"text":414,"config":415},"Get Started",{"href":416,"dataGaName":417,"dataGaLocation":404},"https://gitlab.com/-/trial_registrations/new?glm_source=about.gitlab.com/get-started/","get started",{"freeTrial":419,"mobileIcon":424,"desktopIcon":426},{"text":420,"config":421},"Learn more about GitLab Duo",{"href":422,"dataGaName":423,"dataGaLocation":404},"/gitlab-duo/","gitlab duo",{"altText":406,"config":425},{"src":408,"dataGaName":409,"dataGaLocation":404},{"altText":406,"config":427},{"src":412,"dataGaName":409,"dataGaLocation":404},{"freeTrial":429,"mobileIcon":434,"desktopIcon":436},{"text":430,"config":431},"Back to pricing",{"href":186,"dataGaName":432,"dataGaLocation":404,"icon":433},"back to pricing","GoBack",{"altText":406,"config":435},{"src":408,"dataGaName":409,"dataGaLocation":404},{"altText":406,"config":437},{"src":412,"dataGaName":409,"dataGaLocation":404},{"title":439,"button":440,"config":445},"See how agentic AI transforms software delivery",{"text":441,"config":442},"Watch GitLab Transcend now",{"href":443,"dataGaName":444,"dataGaLocation":44},"/events/transcend/virtual/","transcend event",{"layout":446,"icon":447},"release","AiStar",{"data":449},{"text":450,"source":451,"edit":457,"contribute":462,"config":467,"items":472,"minimal":679},"Git is a trademark of Software Freedom Conservancy and our use of 'GitLab' is under license",{"text":452,"config":453},"View page source",{"href":454,"dataGaName":455,"dataGaLocation":456},"https://gitlab.com/gitlab-com/marketing/digital-experience/about-gitlab-com/","page source","footer",{"text":458,"config":459},"Edit this page",{"href":460,"dataGaName":461,"dataGaLocation":456},"https://gitlab.com/gitlab-com/marketing/digital-experience/about-gitlab-com/-/blob/main/content/","web ide",{"text":463,"config":464},"Please contribute",{"href":465,"dataGaName":466,"dataGaLocation":456},"https://gitlab.com/gitlab-com/marketing/digital-experience/about-gitlab-com/-/blob/main/CONTRIBUTING.md/","please contribute",{"twitter":468,"facebook":469,"youtube":470,"linkedin":471},"https://twitter.com/gitlab","https://www.facebook.com/gitlab","https://www.youtube.com/channel/UCnMGQ8QHMAnVIsI3xJrihhg","https://www.linkedin.com/company/gitlab-com",[473,520,574,618,645],{"title":184,"links":474,"subMenu":489},[475,479,484],{"text":476,"config":477},"View plans",{"href":186,"dataGaName":478,"dataGaLocation":456},"view plans",{"text":480,"config":481},"Why Premium?",{"href":482,"dataGaName":483,"dataGaLocation":456},"/pricing/premium/","why premium",{"text":485,"config":486},"Why Ultimate?",{"href":487,"dataGaName":488,"dataGaLocation":456},"/pricing/ultimate/","why ultimate",[490],{"title":491,"links":492},"Contact Us",[493,496,498,500,505,510,515],{"text":494,"config":495},"Contact sales",{"href":53,"dataGaName":54,"dataGaLocation":456},{"text":359,"config":497},{"href":361,"dataGaName":362,"dataGaLocation":456},{"text":364,"config":499},{"href":366,"dataGaName":367,"dataGaLocation":456},{"text":501,"config":502},"Status",{"href":503,"dataGaName":504,"dataGaLocation":456},"https://status.gitlab.com/","status",{"text":506,"config":507},"Terms of use",{"href":508,"dataGaName":509,"dataGaLocation":456},"/terms/","terms of use",{"text":511,"config":512},"Privacy statement",{"href":513,"dataGaName":514,"dataGaLocation":456},"/privacy/","privacy statement",{"text":516,"config":517},"Cookie preferences",{"dataGaName":518,"dataGaLocation":456,"id":519,"isOneTrustButton":12},"cookie preferences","ot-sdk-btn",{"title":89,"links":521,"subMenu":530},[522,526],{"text":523,"config":524},"DevSecOps platform",{"href":71,"dataGaName":525,"dataGaLocation":456},"devsecops platform",{"text":527,"config":528},"AI-Assisted Development",{"href":422,"dataGaName":529,"dataGaLocation":456},"ai-assisted development",[531],{"title":532,"links":533},"Topics",[534,539,544,549,554,559,564,569],{"text":535,"config":536},"CICD",{"href":537,"dataGaName":538,"dataGaLocation":456},"/topics/ci-cd/","cicd",{"text":540,"config":541},"GitOps",{"href":542,"dataGaName":543,"dataGaLocation":456},"/topics/gitops/","gitops",{"text":545,"config":546},"DevOps",{"href":547,"dataGaName":548,"dataGaLocation":456},"/topics/devops/","devops",{"text":550,"config":551},"Version Control",{"href":552,"dataGaName":553,"dataGaLocation":456},"/topics/version-control/","version control",{"text":555,"config":556},"DevSecOps",{"href":557,"dataGaName":558,"dataGaLocation":456},"/topics/devsecops/","devsecops",{"text":560,"config":561},"Cloud Native",{"href":562,"dataGaName":563,"dataGaLocation":456},"/topics/cloud-native/","cloud native",{"text":565,"config":566},"AI for Coding",{"href":567,"dataGaName":568,"dataGaLocation":456},"/topics/devops/ai-for-coding/","ai for coding",{"text":570,"config":571},"Agentic AI",{"href":572,"dataGaName":573,"dataGaLocation":456},"/topics/agentic-ai/","agentic ai",{"title":575,"links":576},"Solutions",[577,579,581,586,590,593,597,600,602,605,608,613],{"text":131,"config":578},{"href":126,"dataGaName":131,"dataGaLocation":456},{"text":120,"config":580},{"href":103,"dataGaName":104,"dataGaLocation":456},{"text":582,"config":583},"Agile development",{"href":584,"dataGaName":585,"dataGaLocation":456},"/solutions/agile-delivery/","agile delivery",{"text":587,"config":588},"SCM",{"href":116,"dataGaName":589,"dataGaLocation":456},"source code management",{"text":535,"config":591},{"href":109,"dataGaName":592,"dataGaLocation":456},"continuous integration & delivery",{"text":594,"config":595},"Value stream management",{"href":159,"dataGaName":596,"dataGaLocation":456},"value stream management",{"text":540,"config":598},{"href":599,"dataGaName":543,"dataGaLocation":456},"/solutions/gitops/",{"text":169,"config":601},{"href":171,"dataGaName":172,"dataGaLocation":456},{"text":603,"config":604},"Small business",{"href":176,"dataGaName":177,"dataGaLocation":456},{"text":606,"config":607},"Public sector",{"href":181,"dataGaName":182,"dataGaLocation":456},{"text":609,"config":610},"Education",{"href":611,"dataGaName":612,"dataGaLocation":456},"/solutions/education/","education",{"text":614,"config":615},"Financial services",{"href":616,"dataGaName":617,"dataGaLocation":456},"/solutions/finance/","financial services",{"title":189,"links":619},[620,622,624,626,629,631,633,635,637,639,641,643],{"text":201,"config":621},{"href":203,"dataGaName":204,"dataGaLocation":456},{"text":206,"config":623},{"href":208,"dataGaName":209,"dataGaLocation":456},{"text":211,"config":625},{"href":213,"dataGaName":214,"dataGaLocation":456},{"text":216,"config":627},{"href":218,"dataGaName":628,"dataGaLocation":456},"docs",{"text":239,"config":630},{"href":241,"dataGaName":242,"dataGaLocation":456},{"text":234,"config":632},{"href":236,"dataGaName":237,"dataGaLocation":456},{"text":244,"config":634},{"href":246,"dataGaName":247,"dataGaLocation":456},{"text":252,"config":636},{"href":254,"dataGaName":255,"dataGaLocation":456},{"text":257,"config":638},{"href":259,"dataGaName":260,"dataGaLocation":456},{"text":262,"config":640},{"href":264,"dataGaName":265,"dataGaLocation":456},{"text":267,"config":642},{"href":269,"dataGaName":270,"dataGaLocation":456},{"text":272,"config":644},{"href":274,"dataGaName":275,"dataGaLocation":456},{"title":290,"links":646},[647,649,651,653,655,657,659,663,668,670,672,674],{"text":297,"config":648},{"href":299,"dataGaName":292,"dataGaLocation":456},{"text":302,"config":650},{"href":304,"dataGaName":305,"dataGaLocation":456},{"text":310,"config":652},{"href":312,"dataGaName":313,"dataGaLocation":456},{"text":315,"config":654},{"href":317,"dataGaName":318,"dataGaLocation":456},{"text":320,"config":656},{"href":322,"dataGaName":323,"dataGaLocation":456},{"text":325,"config":658},{"href":327,"dataGaName":328,"dataGaLocation":456},{"text":660,"config":661},"Sustainability",{"href":662,"dataGaName":660,"dataGaLocation":456},"/sustainability/",{"text":664,"config":665},"Diversity, inclusion and belonging (DIB)",{"href":666,"dataGaName":667,"dataGaLocation":456},"/diversity-inclusion-belonging/","Diversity, inclusion and belonging",{"text":330,"config":669},{"href":332,"dataGaName":333,"dataGaLocation":456},{"text":340,"config":671},{"href":342,"dataGaName":343,"dataGaLocation":456},{"text":345,"config":673},{"href":347,"dataGaName":348,"dataGaLocation":456},{"text":675,"config":676},"Modern Slavery Transparency Statement",{"href":677,"dataGaName":678,"dataGaLocation":456},"https://handbook.gitlab.com/handbook/legal/modern-slavery-act-transparency-statement/","modern slavery transparency statement",{"items":680},[681,684,687],{"text":682,"config":683},"Terms",{"href":508,"dataGaName":509,"dataGaLocation":456},{"text":685,"config":686},"Cookies",{"dataGaName":518,"dataGaLocation":456,"id":519,"isOneTrustButton":12},{"text":688,"config":689},"Privacy",{"href":513,"dataGaName":514,"dataGaLocation":456},[691],{"id":692,"title":18,"body":8,"config":693,"content":695,"description":8,"extension":26,"meta":699,"navigation":12,"path":700,"seo":701,"stem":702,"__hash__":703},"blogAuthors/en-us/blog/authors/fernando-diaz.yml",{"template":694},"BlogAuthor",{"name":18,"config":696},{"headshot":697,"ctfId":698},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1749659556/Blog/Author%20Headshots/fern_diaz.png","fjdiaz",{},"/en-us/blog/authors/fernando-diaz",{},"en-us/blog/authors/fernando-diaz","lxRJIOydP4_yzYZvsPcuQevP9AYAKREF7i8QmmdnOWc",[705,716,730],{"content":706,"config":714},{"title":707,"description":708,"authors":709,"tags":710,"heroImage":711,"category":9,"date":712,"body":713},"A complete guide to GitLab Container Scanning","Explore GitLab's various container scanning methods and learn how to secure containers at every lifecycle stage.",[18],[9,24],"https://res.cloudinary.com/about-gitlab-com/image/upload/v1772721753/frfsm1qfscwrmsyzj1qn.png","2026-03-05","Container vulnerabilities don't wait for your next deployment. They can emerge at any\npoint, including when you build an image or while containers run in production.\nGitLab addresses this reality with multiple container scanning approaches, each designed\nfor different stages of your container lifecycle.\n\nIn this guide, we'll explore the different types of container scanning GitLab offers,\nhow to enable each one, and common configurations to get you started.\n\n## Why container scanning matters\n\nSecurity vulnerabilities in container images create risk throughout your application\nlifecycle. Base images, OS packages, and application dependencies can all harbor\nvulnerabilities that attackers actively exploit. Container scanning detects these risks\nearly, before they reach production, and provides remediation paths when available.\n\nContainer scanning is a critical component of Software Composition Analysis (SCA),\nhelping you understand and secure the external dependencies your containerized\napplications rely on.\n\n## The five types of GitLab Container Scanning\n\nGitLab offers five distinct container scanning approaches, each serving a specific\npurpose in your security strategy.\n\n\n### 1. Pipeline-based Container Scanning\n\n* What it does: Scans container images during your CI/CD pipeline execution,\ncatching vulnerabilities before deployment\n\n* Best for: Shift-left security, blocking vulnerable images from reaching production \n\n* Tier availability: Free, Premium, and Ultimate (with enhanced features in Ultimate)  \n\n* [Documentation](https://docs.gitlab.com/user/application_security/container_scanning/)\n\n\nGitLab uses the Trivy security scanner to analyze container images for\nknown vulnerabilities. When your pipeline runs, the scanner examines your images\nand generates a detailed report.\n\n\n#### How to enable pipeline-based Container Scanning \n\n**Option A: Preconfigured merge request**  \n\n* Navigate to **Secure > Security configuration** in your project.\n* Find the \"Container Scanning\" row.\n* Select **Configure with a merge request**.\n* This automatically creates a merge request with the necessary configuration.  \n\n**Option B: Manual configuration**  \n\n* Add the following to your `.gitlab-ci.yml`:\n\n```yaml\ninclude:\n  - template: Jobs/Container-Scanning.gitlab-ci.yml\n```  \n\n#### Common configurations\n\n**Scan a specific image:**\n\nTo scan a specific image, overwrite the `CS_IMAGE` variable in the `container_scanning` job.\n\n```yaml\ninclude:\n  - template: Jobs/Container-Scanning.gitlab-ci.yml\n\ncontainer_scanning:\n  variables:\n    CS_IMAGE: myregistry.com/myapp:latest\n```\n\n**Filter by severity threshold:**\n\nTo only find vulnerabilities with a certain severity criteria, overwrite the\n`CS_SEVERITY_THRESHOLD` variable in the `container_scanning` job. In the example\nbelow, only vulnerabilities with a severity of **High** or greater will be displayed.\n\n\n```yaml\ninclude:\n  - template: Jobs/Container-Scanning.gitlab-ci.yml\n\ncontainer_scanning:\n  variables:\n    CS_SEVERITY_THRESHOLD: \"HIGH\"\n```\n\n#### Viewing vulnerabilities in a merge request\n\nViewing Container Scanning vulnerabilities directly within merge requests makes security\nreviews seamless and efficient. Once Container Scanning is configured in your CI/CD\npipeline, GitLab automatically display detected vulnerabilities in the merge request's\n[Security widget](https://docs.gitlab.com/user/project/merge_requests/widgets/#application-security-scanning). \n\n\n![Container Scanning vulnerabilities displayed in MR](https://res.cloudinary.com/about-gitlab-com/image/upload/v1772547514/lt6elcq6jexdhqatdy8l.png \"Container Scanning vulnerabilities displayed in MR\")\n\n\n\n* Navigate to any merge request and scroll to the \"Security Scanning\" section to see a summary of\nnewly introduced and existing vulnerabilities found in your container images.\n\n* Click on a **Vulnerability** to access detailed information about the finding, including severity level,\naffected packages, and available remediation guidance.\n\n\n![GitLab Security View details in MR](https://res.cloudinary.com/about-gitlab-com/image/upload/v1772547514/hplihdlekc11uvpfih1p.png)\n\n\n\n![GitLab Security View details in MR](https://res.cloudinary.com/about-gitlab-com/image/upload/v1772547513/jnxbe7uld8wfeezboifs.png \"Container Scanning vulnerability details in MR\")\n\n\nThis visibility enables developers and security teams to catch and address container\nvulnerabilities before they reach production, making security an integral part of your\ncode review process rather than a separate gate.\n\n\n#### Viewing vulnerabilities in Vulnerability Report\n\nBeyond merge request reviews, GitLab provides a centralized\n[Vulnerability Report](https://docs.gitlab.com/user/application_security/vulnerability_report/) that gives security teams comprehensive visibility across all Container Scanning findings in your project.\n\n\n![Vulnerability Report sorted by Container Scanning](https://res.cloudinary.com/about-gitlab-com/image/upload/v1772547524/gagau279fzfgjpnvipm5.png \"Vulnerability Report sorted by Container Scanning\")\n\n\n* Access this report by navigating to **Security & Compliance > Vulnerability Report** in your\nproject sidebar.\n\n* Here you'll find an aggregated view of all container vulnerabilities detected across your branches, with powerful filtering options to sort by severity, status, scanner type, or specific container images.\n\n* You can click on a vulnerabilty to access its Vulnerablity page.\n\n\n![Vulnerability page - 1st view](https://res.cloudinary.com/about-gitlab-com/image/upload/v1772547520/e1woxupyoajhrpzrlylj.png)\n\n\n![Vulnerability page - 2nd view](https://res.cloudinary.com/about-gitlab-com/image/upload/v1772547521/idzcftcgjc8eryixnbjn.png)\n\n\n![Vulnerability page - 3rd view](https://res.cloudinary.com/about-gitlab-com/image/upload/v1772547522/mbbwbbprtf9anqqola10.png \"Vunerability Details for a Container Scanning vulnerability\")\n\n\n[Vulnerability Details](https://docs.gitlab.com/user/application_security/vulnerabilities/)\nshows exactly which container images and layers are impacted, making it easier to trace the\nvulnerability back to its source. You can assign vulnerabilities to team members, change\ntheir status (detected, confirmed, resolved, dismissed), add comments for collaboration,\nand link related issues for tracking remediation work.\n\nThis workflow transforms vulnerability management from a spreadsheet exercise into an integrated part of your development process, ensuring that container security findings are tracked, prioritized, and resolved systematically.\n\n#### View the Dependency List\n\nGitLab's [Dependency List](https://docs.gitlab.com/user/application_security/dependency_list/)\nprovides a comprehensive software bill of materials (SBOM) that catalogs every component within\nyour container images, giving you complete transparency into your software supply chain.\n\n* Navigate to **Security & Compliance > Dependency List** to access an inventory of all packages,\nlibraries, and dependencies detected by Container Scanning across your project.\n\n* This view is invaluable for understanding what's actually running inside your containers, from base OS\npackages to application-level dependencies.\n\n\n![GitLab Dependency List](https://res.cloudinary.com/about-gitlab-com/image/upload/v1772547513/vjg6dk3nhajqamplroji.png \"GitLab Dependency List (SBOM)\")\n\n\nYou can filter the list by package manager, license type, or vulnerability status to quickly\nidentify which components pose security risks or compliance concerns. Each dependency entry\nshows associated vulnerabilities, allowing you to understand security issues in the context\nof your actual software components rather than as isolated findings.\n\n\n### 2. Container Scanning for Registry\n\n* What it does: Automatically scans images pushed to your GitLab Container Registry\nwith the `latest` tag\n\n* Best for: Continuous monitoring of registry images without manual pipeline triggers  \n\n* Tier availability: Ultimate only \n\n* [Documentation](https://docs.gitlab.com/user/application_security/container_scanning/#container-scanning-for-registry) \n\n\nWhen you push a container image tagged `latest`, GitLab's security policy bot\nautomatically triggers a scan against the default branch. Unlike pipeline-based\nscanning, this approach works with Continuous Vulnerability Scanning to monitor\nfor newly published advisories.\n\n#### How to enable Container Scanning for Registry\n\n1. Navigate to **Secure > Security configuration**.\n2. Scroll to the **Container Scanning For Registry** section.\n3. Toggle the feature on.\n\n![Container Scanning for Registry](https://res.cloudinary.com/about-gitlab-com/image/upload/v1772547512/vntrlhtmsh1ecnwni5ji.png \"Toggle for Container Scanning for Registry\")\n\n#### Prerequisites\n\n- Maintainer role or higher in the project\n- Project must not be empty (requires at least one commit on the default branch)\n- Container Registry notifications must be configured\n- Package Metadata Database must be configured (enabled by default on GitLab.com)\n\nVulnerabilities appear under the **Container Registry vulnerabilities** tab in your\nVulnerability Report.\n\n\n### 3. Multi-Container Scanning\n\n* What it does: Scans multiple container images in parallel within a single pipeline \n* Best for: Microservices architectures and projects with multiple container images  \n* Tier availability: Free, Premium, and Ultimate (currently in Beta)  \n* [Documentation](https://docs.gitlab.com/user/application_security/container_scanning/multi_container_scanning/) \n\nMulti-Container Scanning uses dynamic child pipelines to run scans concurrently, significantly reducing overall pipeline execution time when you need to scan multiple images.\n\n#### How to enable Multi-Container scanning\n\n1. Create a `.gitlab-multi-image.yml` file in your repository root:\n\n```yaml\nscanTargets:\n  - name: alpine\n    tag: \"3.19\"\n  - name: python\n    tag: \"3.9-slim\"\n  - name: nginx\n    tag: \"1.25\"\n```\n\n2. Include the template in your `.gitlab-ci.yml`:\n\n```yaml\ninclude:\n  - template: Jobs/Multi-Container-Scanning.latest.gitlab-ci.yml\n```\n\n#### Advanced configuration\n\n**Scan images from private registries:**\n\n```yaml\nauths:\n  registry.gitlab.com:\n    username: ${CI_REGISTRY_USER}\n    password: ${CI_REGISTRY_PASSWORD}\n\nscanTargets:\n  - name: registry.gitlab.com/private/image\n    tag: latest\n```\n\n**Include license information:**\n\n```yaml\nincludeLicenses: true\n\nscanTargets:\n  - name: postgres\n    tag: \"15-alpine\"\n```\n\n\n### 4. Continuous Vulnerability Scanning\n\n* What it does: Automatically creates vulnerabilities when new security advisories are published, no pipeline required \n\n* Best for: Proactive security monitoring between deployments\n\n* Tier availability: Ultimate only\n\n* [Documentation](https://docs.gitlab.com/user/application_security/continuous_vulnerability_scanning/)  \n\nTraditional scanning only catches vulnerabilities at scan time. But what happens\nwhen a new CVE is published tomorrow for a package you scanned yesterday? Continuous\nVulnerability Scanning solves this by monitoring the GitLab Advisory Database and\nautomatically creating vulnerability records when new advisories affect your components.\n\n\n#### How it works\n\n1. Your Container Scanning or Dependency Scanning job generates a CycloneDX SBOM.\n\n2. GitLab registers your project's components from this SBOM.\n\n3. When new advisories are published, GitLab checks if your components are affected.\n\n4. Vulnerabilities are automatically created in your vulnerability report.\n\n\n#### Key considerations\n\n- Scans run via background jobs (Sidekiq), not CI pipelines.\n\n- Only advisories published within the last 14 days are considered for new component detection.\n\n- Vulnerabilities use \"GitLab SBoM Vulnerability Scanner\" as the scanner name.\n\n- To mark vulnerabilities as resolved, you still need to run a pipeline-based scan.\n\n\n### 5. Operational Container Scanning\n\n* What it does: Scans running containers in your Kubernetes cluster on a\nscheduled cadence\n\n* Best for: Post-deployment security monitoring and runtime vulnerability detection  \n\n* Tier availability: Ultimate only\n\n* [Documentation](https://docs.gitlab.com/user/clusters/agent/vulnerabilities/)\n\n\nOperational Container Scanning bridges the gap between build-time security and\nruntime security. Using the GitLab Agent for Kubernetes, it scans containers\nactually running in your clusters—catching vulnerabilities that emerge after\ndeployment.\n\n#### How to enable Operational Container Scanning\n\nIf you are using the [GitLab Kubernetes Agent](https://docs.gitlab.com/user/clusters/agent/install/), you can add the following to your agent configuration file:\n\n```yaml\ncontainer_scanning:\n  cadence: '0 0 * * *'  # Daily at midnight\n  vulnerability_report:\n    namespaces:\n      include:\n        - production\n        - staging\n```\n\n\nYou can also create a [scan execution policy](https://docs.gitlab.com/user/clusters/agent/vulnerabilities/#enable-via-scan-execution-policies) that enforces scanning on a schedule by the GitLab Kubernetes Agent.\n\n\n![Scan execution policy - Operational Container Scanning](https://res.cloudinary.com/about-gitlab-com/image/upload/v1772547515/gsgvjcq4sas4dfc8ciqk.png \"Scan execution policy conditions for Operational Container Scanning\")\n\n#### Viewing results\n\n* Navigate to **Operate > Kubernetes clusters**.\n\n* Select the **Agent** tab, and choose your agent.\n\n* Then select the **Security** tab to view cluster vulnerabilities.\n\n* Results also appear under the **Operational Vulnerabilities** tab in the **Vulnerability Report**.\n\n\n## Enhancing posture with GitLab Security Policies\n\nGitLab Security Policies enable you to enforce consistent security standards across your container workflows through automated, policy-driven controls. These policies shift security left by embedding requirements directly into your development pipeline, ensuring vulnerabilities are caught and addressed before code reaches production.\n\n#### Scan execution and pipeline policies\n\n[Scan execution policies](https://docs.gitlab.com/user/application_security/policies/scan_execution_policies/) automate when and how Container Scanning runs across your projects. Define policies that trigger container scans on every merge request, schedule recurring scans of your main branch, and more. These policies ensure comprehensive coverage without relying on developers to manually configure scanning in each project's CI/CD pipeline.\n\nYou can specify which scanner versions to use and configure scanning parameters centrally, maintaining consistency across your organization while adapting to new container security threats.\n\n![Scan execution policy configuration](https://res.cloudinary.com/about-gitlab-com/image/upload/v1772547517/z36dntxslqem9udrynvx.png \"Scan execution policy configuration\")\n\n\n[Pipeline execution policies](https://docs.gitlab.com/user/application_security/policies/pipeline_execution_policies/) provide flexible controls for injecting (or overriding) custom jobs into a pipeline based on your compliance needs.\n\nUse these policies to automatically inject Container Scanning jobs into your pipeline, fail builds when container vulnerabilities exceed your risk tolerance, trigger additional security checks for specific branches or tags, or enforce compliance requirements for container images destined for production environments. Pipeline execution policies act as automated guardrails, ensuring your security standards are consistently applied across all container deployments without manual intervention.\n\n![Pipeline execution policy](https://res.cloudinary.com/about-gitlab-com/image/upload/v1772547517/ddhhugzcr2swptgodof2.png \"Pipeline execution policy actions\")\n\n#### Merge request approval policies\n\n[Merge request approval policies](https://docs.gitlab.com/user/application_security/policies/merge_request_approval_policies/) enforce security gates by requiring designated approvers to review and sign off on merge requests containing container vulnerabilities.\n\nConfigure policies that block merge when critical or high-severity vulnerabilities are detected, or require security team approval for any merge request introducing new container findings. These policies prevent vulnerable container images from advancing through your pipeline while maintaining development velocity for low-risk changes.\n\n![Merge request approval policy performing block in MR](https://res.cloudinary.com/about-gitlab-com/image/upload/v1772547513/hgnbc1vl4ssqafqcyuzg.png \"Merge request approval policy performing block in MR\")\n\n\n## Choosing the right approach\n\n| Scanning Type | When to Use | Key Benefit |\n|--------------|-------------|-------------|\n| Pipeline-based | Every build | Shift-left security, blocks vulnerable builds |\n| Registry scanning | Continuous monitoring | Catches new CVEs in stored images |\n| Multi-container | Microservices | Parallel scanning, faster pipelines |\n| Continuous vulnerability | Between deployments | Proactive advisory monitoring |\n| Operational | Production monitoring | Runtime vulnerability detection |\n\n\n\nFor comprehensive security, consider combining multiple approaches. Use\npipeline-based scanning to catch issues during development, container\nscanning for registry for continuous monitoring, and operational scanning\nfor production visibility.\n\n## Get started today\n\nThe fastest path to container security is enabling pipeline-based scanning:\n\n1. Navigate to your project's **Secure > Security configuration**.\n2. Click **Configure with a merge request** for Container Scanning.\n3. Merge the resulting merge request.\n4. Your next pipeline will include vulnerability scanning.\n\nFrom there, layer in additional scanning types based on your security requirements\nand GitLab tier.\n\nContainer security isn't a one-time activity, it's an ongoing process.\nWith GitLab's comprehensive container scanning capabilities, you can detect\nvulnerabilities at every stage of your container lifecycle, from build to runtime.\n\n> For more information on how GitLab can help enhance your security posture, visit the [GitLab Security and Governance Solutions Page](https://about.gitlab.com/solutions/application-security-testing/).\n",{"slug":715,"featured":12,"template":13},"complete-guide-to-gitlab-container-scanning",{"content":717,"config":728},{"title":718,"description":719,"authors":720,"heroImage":723,"date":724,"body":725,"category":9,"tags":726},"Track vulnerability remediation with the updated GitLab Security Dashboard","Quickly prioritize remediation on high-risk projects and measure progress with vulnerability insights.",[721,722],"Alisa Ho","Mike Clausen","https://res.cloudinary.com/about-gitlab-com/image/upload/v1771438388/t6sts5qw4z8561gtlxiq.png","2026-02-19","Security teams and developers face the same frustration: thousands of vulnerabilities demanding attention, without the insights to help them prioritize remediation. Where is risk concentrated and how fast is it being remediated? Where will remediation efforts have the greatest impact? The updated GitLab Security Dashboard helps answer these questions with trend tracking, vulnerability age distribution, and risk scoring by project.\n\n## Measure remediation, not just detection\nApplication security teams don’t struggle to find vulnerabilities; they struggle to make sense of them. Most dashboards show raw counts without context, forcing teams to spend countless hours chasing remediation without understanding what vulnerabilities expose them to the greatest risks.\n\n[GitLab Security Dashboard](https://docs.gitlab.com/user/application_security/security_dashboard/#new-security-dashboards) consolidates all vulnerability data into one view that spans projects, groups, and business units.\n\nIn 18.6, we introduced the first release of the updated Security Dashboard, allowing teams to view vulnerabilities over time and filter based on project or report type. As part of the [18.9 release](https://about.gitlab.com/releases/2026/02/19/gitlab-18-9-released/), customers will be able to take advantage of new filters and charts that make it easier to slice data by severity, status, scanner, or project and visualize trends such as open vulnerabilities, remediation velocity, vulnerability age distribution, and risk score over time.\n\nRisk scores help teams prioritize remediating their most critical vulnerabilities. The risk score is calculated using factors such as vulnerability age, Exploit Prediction Scoring System (EPSS), and Known Exploited Vulnerability (KEV) scores for related repositories and their security postures. With this data, application security teams can pinpoint which areas need more attention than others. \n\nGitLab Security Dashboard helps application security and development teams:\n* **Track program effectiveness**: Monitor remediation velocity, scanner adoption, and risk posture to show measurable improvement.\n* **Focus on targeted remediation**: Fix vulnerabilities that represent the greater risk to production systems.\n* **Identify areas for remediation training**: Find which teams struggle with remediating vulnerabilities in accordance with company policy to invest in additional training. \n* **Reduce manual reporting**: Eliminate the need for external dashboards and spreadsheets by tracking everything directly within GitLab.\n\nThis update reflects GitLab’s continued commitment to making security measurable, contextual, and integrated into everyday development workflows. GitLab Security Dashboard turns raw findings into actionable insights, giving security and development teams the clarity to prioritize, reduce risk faster, and prove their progress.\n\n## See Security Dashboard in action\nAn application security leader preparing for an executive briefing can now show whether investments are reducing risk with clear trendlines: open vulnerabilities decreasing, vulnerability age decreasing, once-prevalent CWE types trending downward, and a healthy risk score. Instead of presenting raw counts, they can demonstrate how the backlog is shrinking and how risk posture is improving quarter over quarter.\n\nAt the same time, developers can see the same dashboard highlighting critical vulnerabilities in their active projects, allowing them to focus remediation efforts without exporting data or juggling multiple tools.\n\n\u003Ciframe src=\"https://player.vimeo.com/video/1166108924?badge=0&amp;autopause=0&amp;player_id=0&amp;app_id=58479\" frameborder=\"0\" allow=\"autoplay; fullscreen; picture-in-picture; clipboard-write; encrypted-media; web-share\" referrerpolicy=\"strict-origin-when-cross-origin\" style=\"position:absolute;top:0;left:0;width:100%;height:100%;\" title=\"Security-Dashboard-Demo-Final\">\u003C/iframe>\u003Cscript src=\"https://player.vimeo.com/api/player.js\">\u003C/script>\n\n> For more details on how to get started with GitLab Security Dashboard today, check out our [documentation](https://docs.gitlab.com/user/application_security/security_dashboard/).",[9,727,25],"product",{"featured":31,"template":13,"slug":729},"track-vulnerability-remediation-with-the-updated-gitlab-security-dashboard",{"content":731,"config":741},{"title":732,"description":733,"heroImage":734,"body":735,"date":736,"category":9,"authors":737,"tags":739},"How to set up GitLab SAML SSO with Google Workspace","Learn how to automate user provisioning and sync permissions with Google groups with this step-by-step guide.","https://res.cloudinary.com/about-gitlab-com/image/upload/v1759320418/xjmqcozxzt4frx0hori3.png","Single sign-on (SSO) simplifies user authentication and improves security by allowing employees to access multiple applications with one set of credentials. For organizations using both GitLab and Google Workspace, integrating SAML-based SSO streamlines access management and ensures your teams can collaborate seamlessly.\n\nIn this guide, we'll walk through configuring SAML authentication between Google Workspace and GitLab.com, including automatic group synchronization that maps Google Workspace groups to GitLab roles. By the end, your users will be able to sign in to GitLab using their Google credentials, and their permissions will automatically reflect their Google group memberships.\n\n**Note:** This guide focuses on GitLab.com (SaaS). If you're using GitLab Self-Managed, the setup process differs slightly. Refer to the [official GitLab SAML documentation for self-managed instances](https://docs.gitlab.com/integration/saml/) for detailed instructions.\n\n## What you'll need\n\nBefore getting started, make sure you have:\n- **Google Workspace** with Super Admin access\n- **GitLab.com** with a Premium or Ultimate tier subscription\n- **Owner role** on a GitLab top-level group\n- Users already existing in Google Workspace (they'll be created in GitLab automatically on first login)\n\n## Understanding the architecture\n\nWhen you configure SAML SSO with group synchronization, here's what happens:\n\n1. **Authentication flow**: Users navigate to GitLab's SSO URL and are redirected to Google Workspace to authenticate.\n2. **SAML assertion**: After successful authentication, Google sends a SAML response containing user details and group memberships.\n3. **Automatic provisioning**: GitLab creates the user account (if needed) and assigns them to groups based on their Google group memberships.\n4. **Permission sync**: Each time users sign in, GitLab updates their group memberships and roles to match their current Google groups.\n\nThis setup provides several benefits:\n\n- **Centralized access control**: You can manage user access through Google Workspace groups.\n- **Automatic provisioning**: New users gain GitLab access on their first login.\n- **Dynamic permissions**: User roles update automatically based on group membership changes.\n- **Enhanced security**: You can leverage Google's authentication security features.\n- **Reduced administrative overhead**: There is no need to manually manage GitLab group memberships.\n\n## Part 1: Get your GitLab SAML configuration values\n\nFirst, you'll need to gather some information from GitLab that you'll use when creating the SAML application in Google Workspace. Here are the steps to take:\n\n### Step 1: Navigate to your GitLab group SAML settings\n\n1. Sign in to **GitLab.com**.\n2. Navigate to your **top-level group** (Note: SAML SSO can only be configured at the top-level group, not in subgroups).\n3. In the left sidebar, select **Settings > SAML SSO**.\n\n### Step 2: Copy the required URLs\n\nOn the SAML SSO settings page, you'll see three important URLs. Copy and save these somewhere accessible — you'll need them shortly:\n\n- **Assertion consumer service URL**: This is where Google will send SAML responses.\n  - Format: `https://gitlab.com/groups/your-group/-/saml/callback`\n\n- **Identifier**: Also called the Entity ID, this uniquely identifies your GitLab group.\n  - Format: `https://gitlab.com/groups/your-group`\n\n- **GitLab SSO URL**: This is the URL your users will use to sign in.\n  - Format: `https://gitlab.com/groups/your-group/-/saml/sso`\n\n\n\u003Cfigure>\n  \u003Cimg src=\"https://res.cloudinary.com/about-gitlab-com/image/upload/v1769090029/lrw6jbn7ussjze6lxg5o.png\" alt=\"GitLab SAML single sign-on settings\">\n  \u003Cfigcaption>\u003Cem>GitLab SAML single sign-on settings\u003C/em>\u003C/figcaption>\n\u003C/figure>\n## Part 2: Create your SAML application in Google Workspace\n\nNow you'll create a custom SAML application in Google Workspace that connects to your GitLab group.\n\n### Step 3: Access the Google Admin Console\n\n1. Open a new browser tab and sign in to the [Google Admin Console](https://admin.google.com/) with a Super Administrator account.\n2. Click the **Menu** icon (☰) in the top-left.\n3. Navigate to **Apps > Web and mobile apps**.\n4. Click **Add App > Add custom SAML app**.\n\u003Cp>\u003C/p>\n\u003Cfigure>\n  \u003Cimg src=\"https://res.cloudinary.com/about-gitlab-com/image/upload/v1769090026/c2inhqzppdbszysupjcd.png\" alt=\"Google custom SAML app\">\n  \u003Cfigcaption>\u003Cem>Google custom SAML app\u003C/em>\u003C/figcaption>\n\u003C/figure>\n\n### Step 4: Configure the application name\n\n1. In the **App name** field, enter GitLab (or your preferred name).\n2. Optionally upload a **GitLab logo** as the app icon for easy recognition.\n3. Click **Continue**.\n\n### Step 5: Download Google identity provider details\n\nOn the **Google Identity Provider details** page, you'll need to capture two pieces of information:\n\n1. **SSO URL**: Copy this URL. It tells GitLab where to send authentication requests.\n   - Example format: `https://accounts.google.com/o/saml2/idp?idpid=C1234abcd`\n\n\n2. **Certificate**: Click the **Download** button to save the certificate file.\n   - The file will be named something like: `GoogleIDPCertificate-gitlab.pem`\n   - Save this file somewhere you can easily find it. You'll need it in the next section\n\n3. Click **Continue**.\n\n### Step 6: Configure service provider details\n\nThis is where you'll use the GitLab URLs you copied in Step 2. Enter the following:\n\n| **Field** | **Value** | **Description** |\n|-----------|-----------|-----------------|\n| **ACS URL** | Your GitLab Assertion consumer service URL | Where Google sends SAML responses |\n| **Entity ID** | Your GitLab Identifier | Unique identifier for your GitLab group |\n| **Start URL** | Leave blank | Not required for this setup |\n| **Name ID format** | Select **EMAIL** | The format for the user identifier |\n| **Name ID** | Select **Basic Information > Primary Email** | The user's primary email will be used as their identifier |\n| **Signed response** | Leave unchecked | GitLab doesn't require signed responses by default |\n\n\u003Cfigure style=\"margin: 24px 0;\">\n  \u003Cimg src=\"https://res.cloudinary.com/about-gitlab-com/image/upload/v1769090028/kaui5vj14gkftbfgsbnz.png\" alt=\"GitLab SAML app details\">\n  \u003Cfigcaption>\u003Cem>GitLab SAML app details\u003C/em>\u003C/figcaption>\n\u003C/figure>\n\n\nClick **Continue** when complete.\n\n### Step 7: Configure attribute mapping\n\nAttribute mapping tells Google which user information to send to GitLab. You'll configure both basic user attributes and group membership.\n\n#### Basic attributes\n\nAdd these three attribute mappings by clicking **Add mapping** for each:\n\n| **Google Directory attribute** | **App attribute** |\n|--------------------------------|-------------------|\n| Primary email | email |\n| First name | first_name |\n| Last name | last_name |\n\n#### Group membership configuration\n\nThis is the critical configuration that enables automatic group synchronization:\n\n1. Scroll down to the **\"Group membership (optional)\"** section.\n2. Under **\"Google groups\"**, click **\"Search for a group\"**.\n3. Search for and select each Google Workspace group you want to synchronize with GitLab.\n   - You can select up to 75 groups\n   - Examples: Engineering, DevOps, Platform-Team, Security-Team\n\n4. Under **\"App attribute\"**, enter exactly: `groups`.\n5. Click **Finish**.\n\n\u003Cp>\u003C/p>\n\u003Cfigure>\n  \u003Cimg src=\"https://res.cloudinary.com/about-gitlab-com/image/upload/v1769090027/ksuebt9uoe3w5cdzsjkl.png\" alt=\"GitLab SAML app attribute mapping\">\n  \u003Cfigcaption>\u003Cem>GitLab SAML app attribute mapping\u003C/em>\u003C/figcaption>\n\u003C/figure>\n\n> **Critical**: The app attribute name **MUST** be exactly `groups` (lowercase). This is what GitLab expects to receive in the SAML response. Any other value or capitalization will prevent group synchronization from working.\n\n### Step 8: Enable the application for users\n\nYour SAML app is created but not yet enabled. To make it available to users:\n\n1. In the Google Admin Console, find your **GitLab** app in the Web and mobile apps list.\n2. Click on the app to open its details.\n3. In the left sidebar, click **User access**.\n4. Select one of the following:\n   - **ON for everyone** - Enables the app for all users in your organization\n   - **ON for some organizational units** - Select specific organizational units\n\n5. Click **Save**.\n\n**Note**: Changes can take up to 24 hours to propagate, but typically take effect within a few minutes.\n\n## Part 3: Convert the certificate to SHA-1 fingerprint format\n\nGitLab requires a SHA-1 certificate fingerprint, but Google's certificate download doesn't include this format directly. You'll need to convert it.\n\n### Step 9: Convert your certificate\n\nYou have two options for converting the certificate to the required format.\n\n#### Option 1: Online conversion tool\n\nThis is a viable method if you're comfortable using a third-party tool:\n\n1. **Locate the certificate file** you downloaded in Step 5:\n   - Check your Downloads folder\n   - The file name will be something like: `GoogleIDPCertificate-gitlab.pem`\n\n2. **Open the file** in a text editor:\n   - Mac: **Right-click > Open With > TextEdit**\n   - Windows: **Right-click > Open With > Notepad**\n   - Linux: Use your preferred text editor\n\n3. **Copy ALL contents** of the file, including the header and footer:\n\n\n  ```text\n  -----BEGIN CERTIFICATE-----\n  MIIDdDCCAlygAwIBAgIGAXqD...\n  (multiple lines of encoded text)\n  ...kE7RnF6yQ==\n  -----END CERTIFICATE-----\n  ```\n\n\n4. **Navigate to**: A SHA-1 fingerprint conversion tool. [This one](https://www.samltool.com/fingerprint.php) is a good example.\n5. **Paste the certificate content** into the text box.\n6. **Select \"SHA-1\"** from the algorithm dropdown (not SHA-256!).\n7. Click **\"Calculate Fingerprint\"**.\n8. **Copy the resulting fingerprint** - it will be in the format: `XX:XX:XX:XX:XX:...`.\n\n#### Option 2: Command-line conversion\n\nIf you prefer using the command line:\n\n**For Mac, Linux, or Windows with WSL:**\n\n\n  ```bash\n  cd ~/Downloads\n  openssl x509 -noout -fingerprint -sha1 -inform pem -in \"GoogleIDPCertificate-gitlab.pem\"\n  ```\n\n\nThe output will show:\n\n\n  ```text\n  SHA1 Fingerprint=XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX:XX\n  ```\n\n\nCopy everything after `SHA1 Fingerprint=`.\n\n## Part 4: Complete your GitLab SAML configuration\n\nNow that you have the Google SSO URL and certificate fingerprint, you can complete the GitLab side of the configuration.\n\n### Step 10: Enter Google identity provider details\n\nReturn to your GitLab browser tab (**Settings > SAML SSO**) and do the following:\n\n1. **Identity provider SSO URL**:\n   - Paste the SSO URL you copied from Google in Step 5\n\n2. **Certificate fingerprint**:\n   - Paste the SHA-1 fingerprint you generated in Step 9\n   - Verify the format is correct: 59 characters with colons (XX:XX:XX:...)\n\n3. **Enable SAML authentication for this group**:\n   - Check this box to activate SAML SSO\n\n\u003Cp>\u003C/p>\n\u003Cfigure>\n  \u003Cimg src=\"https://res.cloudinary.com/about-gitlab-com/image/upload/v1769090027/ncoeqrdu7aahyuflrq7b.png\" alt=\"GitLab SAML Configuration with Google SAML values\">\n  \u003Cfigcaption>\u003Cem>GitLab SAML Configuration with Google SAML values\u003C/em>\u003C/figcaption>\n\u003C/figure>\n\n### Step 11: Configure security settings (recommended)\n\nFor enhanced security, consider enabling these additional options:\n\n- **\"Enforce SAML authentication for web activity for this group\"**\n  - Requires users to authenticate via SAML to access the GitLab web interface\n\n- **\"Enforce SAML authentication for Git and Dependency Proxy activity for this group\"**\n  - Requires SAML authentication for Git operations and dependency proxy access\n\nClick **Save changes** to apply your configuration.\n\n### Step 12: Test your SAML configuration\n\nBefore proceeding with group synchronization, verify that basic SAML authentication works:\n\n1. Open an incognito or private browsing window.\n2. Navigate to your GitLab SSO URL.\n   - Format: `https://gitlab.com/groups/your-group/-/saml/sso`\n\n3. You should be redirected to the Google sign-in page.\n4. Sign in with a Google Workspace account that has access to the GitLab app.\n5. After successful authentication, you should be redirected back to GitLab.\n\n**If the test succeeds**, you can proceed to configure group synchronization.\n\n**If the test fails**, check the following:\n\n- Verify the certificate fingerprint is SHA-1 format (not SHA-256).\n- Confirm the SSO URL is correct.\n- Ensure the user has access to the GitLab SAML app in Google Admin Console.\n- Check that the ACS URL and Entity ID match exactly.\n\n## Part 5: Set up SAML group synchronization\n\nNow it's time to map your Google Workspace groups to GitLab roles so that permissions are automatically managed based on group membership.\n\n### Step 13: Configure default membership role\n\nAs a security best practice, set a minimal default role for users who log in but don't belong to any mapped groups:\n\n1. In your GitLab group, navigate to **Settings > General**.\n2. Expand the **Permissions and group features** section.\n3. Under **Default membership role**, select **Minimal Access or Guest**.\n4. Click **Save changes**.\n\n\u003Cp>\u003C/p>\n\u003Cfigure>\n  \u003Cimg src=\"https://res.cloudinary.com/about-gitlab-com/image/upload/v1769097587/syi0jeaspzt9tki0w9nd.png\" alt=\"GitLab SAML Default membership setting\">\n  \u003Cfigcaption>\u003Cem>GitLab SAML Default membership setting\u003C/em>\u003C/figcaption>\n\u003C/figure>\n\n### Step 14: Create SAML group links\n\nSAML Group Links are the mappings between Google Workspace groups and GitLab roles. Here's how to create them:\n\n1. In your GitLab group, navigate to **Settings > SAML Group Links**.\n2. Click **\"Add new SAML Group Link\"**.\n\nFor each Google Workspace group you want to sync:\n\n**SAML Group Name**:\n\n- Enter the **exact name** of your Google Workspace group\n- This is **case-sensitive** and must match perfectly\n- Example: Engineering (not engineering)\n- To find the exact name: Google Admin Console > Directory > Groups\n\n**Access Level**: Select the appropriate GitLab role:\n\n- **Minimal Access** - Can see that the group exists\n- **Guest** - Can view issues and leave comments\n- **Reporter** - Can pull code, view issues, and create new issues\n- **Developer** - Can push code, create merge requests, and manage issues\n- **Maintainer** - Can manage project settings and members\n- **Owner** - Full administrative control over the group\n\n3. Click **Save**.\n4. **Repeat this process** for each Google Workspace group you want to map.\n\n**Note:** SAML group sync rules are enforced every time a user signs in. If a user's Google group membership matches a sync rule, their GitLab role will be automatically set to the configured access level, even if you've manually changed it to something different. For example, if you set up a sync rule that grants \"Maintainer\" access and then manually promote a user to \"Owner,\" they'll be automatically downgraded back to \"Maintainer\" on their next SAML sign-in.\n\n**Best practices:** To maintain custom access levels for specific users, do one of the following:\n\n - Use SAML group sync only on your top-level group and manually manage permissions in subgroups\n\n - Create separate Google groups for users who need elevated permissions\n \n - Avoid setting up sync rules that would conflict with manual role assignments\n\n\n\n\u003Cfigure>\n  \u003Cimg src=\"https://res.cloudinary.com/about-gitlab-com/image/upload/v1769090028/etjoaiuyhnqh4gnjqcha.png\" alt=\"GitLab SAML Group Links setup\">\n  \u003Cfigcaption>\u003Cem>GitLab SAML Group Links setup\u003C/em>\u003C/figcaption>\n\u003C/figure>\n### Example group mapping configuration\n\nHere's a practical example of how you might structure your group mappings:\n\n| **Google Workspace Group** | **GitLab Role** | **Purpose** |\n|----------------------------|-----------------|-------------|\n| GitLab-Admins | Owner | Full administrative access |\n| Engineering-Team | Maintainer | Can manage projects and settings |\n| Developer-Team | Developer | Can write and push code |\n| QA-Team | Developer | Can test and manage issues |\n| Contractors | Reporter | Read-only access to code |\n| All-Employees | Minimal Access | Basic visibility |\n\n### Step 15: Verify your group links\n\nAfter creating all your group links:\n\n1. Review the complete list of SAML Group Links in **Settings > SAML Group Links**.\n2. Verify each **SAML Group Name** exactly matches the corresponding Google Workspace group.\n3. Verify each **Access Level** is appropriate for the intended purpose.\n4. Check for any typos or extra spaces.\n\n## Part 6: Test the complete configuration\n\nNow it's time to test the entire setup including group synchronization.\n\n### Step 16: Test with a real user\n\nChoose a test user who meets these criteria:\n\n- Has a Google Workspace account\n- Is a member of at least one Google Workspace group you configured\n- Has the GitLab SAML app enabled in Google Admin Console\n- Ideally is not you (to ensure a realistic test)\n\nTo perform the test:\n\n1. **Open an incognito or private browsing window**\n2. **Navigate to your GitLab SSO URL**:\n   - `https://gitlab.com/groups/your-group/-/saml/sso`\n\n3. **Sign in** with the test user's Google Workspace credentials\n4. The user should be:\n   - Authenticated successfully\n   - Redirected to GitLab\n   - Automatically added to the GitLab group\n   - Assigned the appropriate role based on their Google group membership\n\n### Step 17: Verify group membership and role assignment\n\nUsing your GitLab administrator account:\n\n1. Navigate to your group in GitLab.\n2. Select **Manage > Members** from the left sidebar.\n3. Find the test user in the members list.\n4. Verify the following:\n   - User appears in the members list\n   - User has the correct **Max role** based on their Google group(s)\n   - **Source** column shows a SAML indicator\n\n\u003Cp>\u003C/p>\n\u003Cfigure>\n  \u003Cimg src=\"https://res.cloudinary.com/about-gitlab-com/image/upload/v1769090026/hiov7kiukidsiyscfesg.png\" alt=\"Verified SAML user added\">\n  \u003Cfigcaption>\u003Cem>Verified SAML user added\u003C/em>\u003C/figcaption>\n\u003C/figure>\n\n## Part 7: Configure subgroup access (optional)\n\nFor larger organizations, you may want to provide more granular access control using GitLab subgroups. SAML Group Links can be configured at any level of your group hierarchy, allowing you to map different Google Workspace groups to specific teams or projects.\n\n### Understanding GitLab's subgroup structure\n\nGitLab supports nested group hierarchies that can mirror your organizational structure:\n\n  ```text\n  acme-corp/                          ← Top-level group (SAML configured here)\n  ├── engineering/                    ← Subgroup\n  │   ├── backend/                   ← Nested subgroup\n  │   └── frontend/                  ← Nested subgroup\n  ├── marketing/                      ← Subgroup\n  └── operations/                     ← Subgroup\n  ```\n\n\n### Creating subgroups\n\nIf you need to create additional subgroups:\n\n1. Navigate to your **parent group** (e.g., acme-corp).\n2. Click the **New subgroup** button.\n3. Configure the subgroup:\n   - **Subgroup name**: Display name (e.g., Engineering)\n   - **Subgroup URL**: URL slug (e.g., engineering)\n   - **Visibility level**: Choose Private, Internal, or Public\n\n4. Click **Create subgroup**.\n5. Repeat for other subgroups as needed.\n\n### Configuring SAML group links for subgroups\nHere are the steps to configure SAML group links for subgroups.\n\n#### Add new Google groups to the SAML app (if needed)\n\nIf you're introducing new Google Workspace groups for subgroup access:\n\n1. Go to **Google Admin Console > Apps > Web and mobile apps > GitLab**.\n2. Click **SAML attribute mapping**.\n3. Scroll to **\"Group membership (optional)\"**.\n4. Add your new groups (e.g., Backend-Team, Frontend-Team).\n5. Verify the **\"App attribute\"** is still `groups`.\n6. Click **Save**.\n\n#### Map Google groups to subgroups\n\n1. **Navigate to the specific subgroup** in GitLab\n   - Example: acme-corp/engineering/backend\n\n2. Go to **Settings > SAML Group Links**.\n3. Click **\"Add new SAML Group Link\"**.\n4. Configure the mapping:\n   - **SAML Group Name**: Backend-Team (exact Google Workspace group name)\n   - **Access Level**: Developer (or your desired role)\n\n5. Click **Save**.\nRepeat this process for all subgroups and their corresponding Google groups.\n\n### Multi-level access example\n\nHere's how permissions might work across different levels:\n\n#### Top-level group: acme-corp\n\nSAML Group Links:\n\n- \"Company-Admins\" → Owner\n- \"All-Employees\" → Minimal Access\n\n#### Subgroup: acme-corp/engineering\n\nSAML Group Links:\n\n- \"Engineering-Leads\" → Owner\n- \"Engineering-Team\" → Maintainer\n\n#### Nested subgroup: acme-corp/engineering/backend\n\nSAML Group Links:\n\n- \"Backend-Leads\" → Maintainer\n- \"Backend-Team\" → Developer\n\n### How permissions inherit and combine\n\nUnderstanding permission behavior is important:\n\n- **Role calculation**: At each level, users receive the **highest role** from all their Google groups.\n- **Inheritance**: Higher permissions at parent levels flow down to child subgroups.\n- **Independence**: Each level calculates permissions based on its own group links plus inherited permissions.\n- **No limitation**: Lower permissions at parent levels do NOT restrict higher permissions at child levels.\n\n**Example scenarios**:\n\n**User A** (member of Backend-Team only):\n\n- acme-corp: Minimal Access (from \"All-Employees\" default)\n- acme-corp/engineering: Minimal Access (inherited from parent)\n- acme-corp/engineering/backend: Developer (from \"Backend-Team\" mapping)\n\n**User B** (member of Engineering-Leads and Backend-Team):\n\n- acme-corp: Minimal Access (from \"All-Employees\" default)\n- acme-corp/engineering: Owner (from \"Engineering-Leads\" mapping)\n- acme-corp/engineering/backend: Owner (inherited from parent, which is higher than Developer)\n\n## How the synchronization works\n\nUnderstanding the mechanics of SAML group synchronization helps you manage the system effectively.\n\n### Synchronization timing\n\n- **When sync occurs**: Group memberships update **every time** a user signs in via SAML.\n- **Frequency**: Changes are not continuous — they only happen at login.\n- **Direction**: Synchronization is **one-way** from Google Workspace to GitLab.\n- **First login**: User account is created automatically and groups are assigned.\n- **Subsequent logins**: Existing group memberships are updated to match current Google groups.\n\n### Role priority and combination\n\nWhen a user belongs to multiple Google Workspace groups:\n\n- GitLab evaluates **all** the user's groups at each level of the hierarchy.\n- The user receives the **highest role** from any of their groups.\n- This calculation happens independently at each level (top-level group, subgroups, etc.).\n\n**Example**:\n\n- User in \"Developers\" (Developer role) + \"Tech-Leads\" (Maintainer role) → Gets **Maintainer**\n\n### Automatic role changes\n\nThe system automatically handles membership changes:\n\n- **User added to a Google group**: Role upgraded on next login.\n- **User removed from a Google group**: Role recalculated based on remaining groups on next login.\n- **User removed from all mapped groups**: Reverts to default membership role on next login.\n- **User added to additional groups**: Gets highest role from all groups on next login.\n\n### Propagation timing\n\nBe aware of these timing considerations:\n\n- **Google Workspace changes**: Can take up to 24 hours to propagate, though usually take only a few minutes.\n- **GitLab sync**: Happens immediately when the user logs in after Google changes are live.\n- **Testing**: Have users log out and log back in to test permission changes.\n\n## Understanding user lifecycle and edge cases\n\n### What happens when you remove a user from GitLab?\n\n**Removing permissions only:** If you remove a user from GitLab projects but leave their account active and they're still in the authorized Google groups:\n\n- They keep their same account (same user ID and username)\n- When they log in via SAML, their group memberships are automatically restored\n- They regain permissions based on their current Google group memberships\n\n**Blocking the account:**\n\n- Account exists but is locked\n- User cannot log in even if in Google groups\n- Can be unblocked later, preserving all history\n\n**Deleting the account:**\n\n- Account is permanently removed\n- If user logs in again (while still in Google groups), GitLab creates a **completely new account**\n- New account has different user ID with no connection to the old one\n\n### Proper offboarding process\n\nTo permanently revoke access, follow this order:\n\n1. **Remove from Google Workspace groups** - Prevents authentication\n2. **Block in GitLab** - Prevents account recreation and preserves audit trails\n3. **Delete account (optional)** - Only if you're certain they won't return\n\n> **Critical**: Removing a user only from GitLab without removing them from Google groups means they can simply log back in and regain access.\n\n### Google group membership propagation\n\nAccording to [Google's documentation](https://support.google.com/a/answer/11143403), group membership changes can take up to 24 hours to propagate, though typically occur within minutes.\n\n### Account recreation scenarios\n\n| **Scenario** | **User still in Google groups?** | **What happens on login** |\n|--------------|----------------------------------|---------------------------|\n| Permissions removed | Yes | Same account, group memberships restored |\n| Account blocked | Yes | Login fails |\n| Account deleted | Yes | New account created with new user ID |\n| Removed from Google groups | No | Login fails at Google |\n\n## Troubleshooting common issues\n\nEven with careful configuration, you might encounter issues. Here are solutions to the most common problems.\n\n### Users not being added to groups\n\n**Symptom**: User successfully logs in via SAML but doesn't appear in any GitLab groups, or appears with only the default role.\n\n**Possible causes and solutions**:\n\n1. **Group names don't match exactly**\n   - Check spelling and capitalization in both Google Workspace and GitLab\n   - Look for extra spaces before or after group names\n   - Verify the exact name in Google Admin Console > Directory > Groups\n\n2. **User not actually in the Google group**\n   - Verify membership: Google Admin Console > Directory > Groups > [Group] > Members\n   - Remember that nested group membership might not be included\n\n3. **Groups not configured in SAML app**\n   - Verify the groups are selected in Google SAML attribute mapping\n   - Confirm \"App attribute\" is set to `groups` (lowercase)\n   - Use \"Test SAML Login\" to inspect the SAML response\n\n4. **Timing or cache issue**\n   - Wait 24 hours for Google changes to fully propagate\n   - Have the user log out of GitLab and Google completely\n   - Clear browser cache and try again\n   - User must log in via the SAML SSO URL, not regular GitLab login\n\n### User has incorrect role\n\n**Symptom**: User has access but with the wrong permission level.\n\n**Possible causes and solutions**:\n\n1. **User belongs to multiple groups**\n   - Remember: Users get the **highest** role from all their groups\n   - Check all Google groups the user belongs to\n   - Review all SAML Group Link configurations at all levels\n\n2. **SAML Group Link misconfigured**\n   - Verify the Access Level setting in Settings > SAML Group Links\n   - Check for duplicate group mappings that might conflict\n\n3. **User hasn't logged in since changes**\n   - Roles only update when users log in via SAML\n   - Have the user log out completely and log back in via the SSO URL\n\n4. **Inherited permissions from parent groups**\n   - Check SAML Group Links in parent groups\n   - Remember that higher roles at parent levels flow down to children\n\n### SAML authentication fails completely\n\n**Symptom**: Users cannot log in at all, or receive error messages during authentication.\n\n**Possible causes and solutions**:\n\n1. **Incorrect certificate fingerprint**\n   - Verify you used SHA-1 format, not SHA-256\n   - Check the fingerprint has the correct format with colons\n   - Regenerate using the online tool or OpenSSL command\n\n2. **Wrong SSO URL**\n   - Double-check the SSO URL copied from Google\n   - Ensure there are no extra spaces or characters\n\n3. **ACS URL or Entity ID mismatch**\n   - Verify the ACS URL in Google Admin Console matches GitLab exactly\n   - Confirm the Entity ID matches between both systems\n\n4. **User doesn't have app access**\n   - Check User Access settings in Google Admin Console\n   - Verify the user's organizational unit has the app enabled\n   - Confirm the app is \"ON\" for the appropriate users\n\n5. **Certificate expired**\n   - Check certificate validity dates\n   - Download a fresh certificate if needed\n\n### Groups attribute missing from SAML response\n\n**Symptom**: Users can log in but group synchronization doesn't work at all.\n\n**Possible causes and solutions**:\n\n1. **Groups not selected in Google configuration**\n   - Return to **Google Admin > Apps > GitLab > Attribute** mapping\n   - Verify groups are selected under \"Group membership\"\n   - Confirm \"App attribute\" is exactly `groups` (lowercase)\n\n2. **User not in any configured groups**\n   - Only groups the user belongs to are sent in the SAML response\n   - Add the user to at least one selected group to test\n\n3. **Configuration hasn't propagated**\n   - Wait up to 24 hours for changes to take effect\n   - Try logging out of Google Admin Console and back in\n\n4. **Typo in app attribute name**\n   - The attribute name must be exactly `groups` (lowercase)\n   - Even a capital letter or extra space will break functionality\n\n## Best practices for managing SAML group sync\n\nFollow these recommendations to maintain a secure and efficient setup.\n\n### Security best practices\n\n1. **Maintain emergency access**\n   - Keep at least one Owner account that uses password authentication (not SAML)\n   - This provides emergency access if SAML configuration breaks\n   - Store these credentials securely\n\n2. **Use least privilege principle**\n   - Set default membership to Minimal Access\n   - Only grant higher permissions through explicit group mappings\n   - Regularly review and audit group memberships\n\n3. **Enable enforcement options**\n   - Turn on \"Enforce SAML authentication\" options\n   - This prevents users from bypassing SSO\n   - Exceptions should be rare and well-documented\n\n4. **Regular security audits**\n   - Quarterly review of Google Workspace group memberships\n   - Annual review of SAML Group Link mappings\n   - Monitor GitLab audit logs for unusual access patterns\n\n## Summary and next steps\n\nCongratulations! You've successfully configured SAML SSO and automatic group synchronization between Google Workspace and GitLab. Your setup now provides:\n\n- **Seamless authentication** - Users sign in with their familiar Google Workspace credentials.\n- **Automatic provisioning** - User accounts are created on first login without manual intervention.\n- **Dynamic permissions** - Group memberships and roles update automatically based on Google Workspace groups.\n- **Centralized access control** - Manage all access through your existing Google Workspace groups.\n- **Enhanced security** - Leverage Google's authentication infrastructure and enforce consistent policies.\n- **Reduced administrative overhead** - Eliminate manual user and permission management in GitLab.\n\n### What happens now\n\nWhen users access GitLab:\n\n1. They navigate to your GitLab SSO URL.\n2. Authenticate using their Google Workspace credentials.\n3. Get automatically added to appropriate GitLab groups.\n4. Receive permissions based on their Google group memberships.\n5. Their permissions update every time they sign in.\n\n### Additional resources\n\n- [GitLab SAML SSO Documentation](https://docs.gitlab.com/ee/user/group/saml_sso/)\n- [GitLab SAML Group Sync Documentation](https://docs.gitlab.com/ee/user/group/saml_sso/group_sync.html)\n- [Google Workspace SAML App Setup](https://support.google.com/a/answer/6087519)\n- [SAML Certificate Fingerprint Tool](https://www.samltool.com/fingerprint.php)\n\n## Related article\n* [How-to: GitLab Single Sign-on with SAML, SCIM, and Azure's Entra ID](https://about.gitlab.com/blog/how-to-gitlab-single-sign-on-with-saml-scim-and-azures-entra-id/)","2026-01-27",[738],"Omid Khan",[9,24,727,740],"google",{"featured":12,"template":13,"slug":742},"how-to-set-up-gitlab-saml-sso-with-google-workspace",{"promotions":744},[745,759,770],{"id":746,"categories":747,"header":749,"text":750,"button":751,"image":756},"ai-modernization",[748],"ai-ml","Is AI achieving its promise at scale?","Quiz will take 5 minutes or less",{"text":752,"config":753},"Get your AI maturity score",{"href":754,"dataGaName":755,"dataGaLocation":242},"/assessments/ai-modernization-assessment/","modernization assessment",{"config":757},{"src":758},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1772138786/qix0m7kwnd8x2fh1zq49.png",{"id":760,"categories":761,"header":762,"text":750,"button":763,"image":767},"devops-modernization",[727,558],"Are you just managing tools or shipping innovation?",{"text":764,"config":765},"Get your DevOps maturity score",{"href":766,"dataGaName":755,"dataGaLocation":242},"/assessments/devops-modernization-assessment/",{"config":768},{"src":769},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1772138785/eg818fmakweyuznttgid.png",{"id":771,"categories":772,"header":773,"text":750,"button":774,"image":778},"security-modernization",[9],"Are you trading speed for security?",{"text":775,"config":776},"Get your security maturity score",{"href":777,"dataGaName":755,"dataGaLocation":242},"/assessments/security-modernization-assessment/",{"config":779},{"src":780},"https://res.cloudinary.com/about-gitlab-com/image/upload/v1772138786/p4pbqd9nnjejg5ds6mdk.png",{"header":782,"blurb":783,"button":784,"secondaryButton":789},"Start building faster today","See what your team can do with the intelligent orchestration platform for DevSecOps.\n",{"text":785,"config":786},"Get your free trial",{"href":787,"dataGaName":49,"dataGaLocation":788},"https://gitlab.com/-/trial_registrations/new?glm_content=default-saas-trial&glm_source=about.gitlab.com/","feature",{"text":494,"config":790},{"href":53,"dataGaName":54,"dataGaLocation":788},1773350831224]