Set SPP as on by default for newly created public projects
Problem to Solve
Currently, newly created public projects do not have Secret Push Protection (SPP) enabled by default, requiring users to manually enable it through security configuration. We need to enable SPP by default for all newly created public projects.
Proposal
As a first step, newly created public projects will have SPP enabled by default on the backend only. Front-end defaults/toggles can follow separately. At the time of this writing (Aug 28th, 2025) Secret push protection is on by default for new... (&18400) will come independently in FY27Q1.
🆕 public project?
How do users create a There might be a place in the backend workflow where you can set the database value for secret push protection to true
when the project is public, but it might be more complex. If the logic is more complex, here are the different ways to create a public project in GitLab:
- Create via the UI
- Create via the API
- Import via UI
- Import via API Script
- Import from GitHub
- Direct Transfer Migration
- Professional Services Migration
How will users disable secret push protection?
Maintainers and Owners will continue to be able to toggle on/off secret push protection via the REST API endpoint or the GraphQL mutation) on a per project level. We will also rework the existing Secret Push Protection configuration UI to be toggleable by public projects only: #567659
Implementation Plan
Below is the initial investigation and context regarding project_security_settings
. Through further investigation, we have found that project_security_settings
are already created for projects across all tiers during project creation, so no model move is required.
Click to expand initial context
Before we jump into the implementation plan, there's some important context to understand below.
EE vs. CE
SPP was built as an Ultimate-only feature, which essentially means it's built to only work when it's licensed to an ultimate project/group, etc. That also means most of the related code resides in EE (under the /ee
namespace). In addition to that, with most of the security-related features being ultimate-based too, the associated project_security_settings
table which is used to control the enablement status of the feature was also built to solely work with EE projects in mind. That creates some challenges as we plan make the feature work for all public projects regardless of the product tier.
New Projects
As explained in the proposal, new projects gets created in so many different ways across the codebase. Fortunately for us, those all lead to the Projects::CreateService
(and EE::Projects::CreateService
, if it's an EE-based project) as can be seen below:
-
Create via UI
- Uses
ProjectsController#create
→Projects::CreateService
- Uses
-
Create via API
- Uses
API::Projects#post
→Projects::CreateService
- Uses
-
Import via UI
- Uses
various import controllers
→Projects::CreateService
- Uses
-
Import via API Script
- Uses
API endpoints
→Projects::CreateService
- Uses
-
Import from GitHub
- Uses
Import::GithubService
→Gitlab::LegacyGithubImport::ProjectCreator
→Projects::CreateService
- Uses
-
Direct Transfer Migration
- Uses
BulkImports::Projects::Pipelines::ProjectPipeline
→Projects::CreateService
- Uses
-
Professional Services Migration
- Uses
GitLab project import
→Projects::GitlabProjectsImportService
→Projects::CreateService
- Uses
So that gives us one option:
- To inject the code necessary to create the associated
project_security_setting
record. - To set
secret_push_protection_enabled
totrue
when the project is created.
Another option (which I personally prefer) is:
- To use lifecycle hooks, e.g.
after_create
, which we do inEE::Project
to create the associatedproject_security_setting
if it doesn't exist. In this case, we could update the lifecycle method to check if the project is public, and depending on this, whether to setsecret_push_protection_enabled
totrue
or not.
Based on the two topics above, we should likely do the following list of tasks to implement this:
1️⃣ Update lifecycle hook to enable SPP if project is public
-
In the project creation lifecycle hook that builds the project_security_setting, update create_security_setting
to setsecret_push_protection_enabled
totrue
if the project is public (see example).
Example Code
# In app/models/ee/project.rb
private
def create_security_setting
return if security_setting # maybe not return early to also confirm we have this enabled for public projects?
# Default SPP to enabled for public projects, disabled for others
public_project = public?
build_security_setting(
secret_push_protection_enabled: public_project
).save!
end
2️⃣ Update license checks across the codebase, and EligibilityChecker
-
Update license checks across the codebase to only check for ultimate if it's a private project. -
Update EligibilityChecker
to only check forsecret_push_protection_enabled?
for public projects (see diagram below).
Example Diagram
flowchart LR
Start([Eligibility Checker]) --> Decision{Public?}
Decision -->|Yes| CheckFlagPublic{SPP Setting Check}
CheckFlagPublic -->|Yes| EnablePublic[Project can run SPP scan]
CheckFlagPublic -->|No| DisablePublic[Project not eligible for SPP scan]
Decision -->|No| CheckLicense{Ultimate?}
CheckLicense -->|Yes| CheckFlagPrivate{SPP Setting Check}
CheckLicense -->|No| DisableLicense[Project not eligible for SPP scan]
CheckFlagPrivate -->|Yes| EnablePrivate[Project can run SPP scan]
CheckFlagPrivate -->|No| DisablePrivate[Project not eligible for SPP scan]
Note: for brevity only certain parts of the eligibility checker are represented here.