Enable secret push protection for all GitLab owned projects
### Background
To prevent secrets from being pushed to GitLab-owned projects, we should enable [Secret Push Protection](https://docs.gitlab.com/ee/user/application_security/secret_detection/secret_push_protection/) for all the projects under GitLab Umbrella.
We can initially focus on active and more important namespaces: [**gitlab-org**](https://gitlab.com/gitlab-org) & [**gitlab-com**](https://gitlab.com/gitlab-com). Other namespaces such as, `components`, `gitlab-community`, `gitlab-data`, `gitlab-examples`, `security-products`, etc. can be tackled in the next iteration.
**Note: This issue is being addressed via https://gitlab.com/groups/gitlab-org/-/epics/16361+. Please review that epic for additional information on how we are rolling this feature out.**
### Proposal
Run a migration in production to enable this setting.
This can be done by running a script that iterates through all the projects under the GitLab namespaces and calls the API to enable the feature for each project. The script must be run by the instance owner, requiring collaboration with the [Infra team](https://handbook.gitlab.com/handbook/engineering/infrastructure/).
<details>
<summary>
Implementation Details
</summary>
1. Retrieve all project paths from the API endpoint:
https://gitlab.com/api/v4/groups/gitlab-org/projects?include_subgroups=true&archived=false
1. Iterate through all the pages
2. Iterate through other namespace (gitlab-com)
2. Execute GraphQL mutation to enable the setting
https://docs.gitlab.com/ee/api/graphql/reference/#mutationsetprereceivesecretdetection
```graphql
mutation {
setPreReceiveSecretDetection(input: {namespacePath: $fullPath, enable: true}) {
preReceiveSecretDetectionEnabled
errors
}
}
```
For context, there are around 11K projects within the GitLab-owned namespaces, around **4458** projects in `gitlab-org`and **6984** projects in the `gitlab-com` namespace.
</details>
<details>
<summary>
Node.js script for reference
</summary>
```javascript
const HOST = "https://gitlab.com";
const GROUP_PATH = "gitlab-org";
const TOKEN = "glpat-xxxxxxxxxxxxxxxxxxxx";
const GRAPHQL_URL = `${HOST}/api/graphql`;
const fetchProjects = async () => {
const projectIds = [];
const BASE_URL = `${HOST}/api/v4/groups/${GROUP_PATH}/projects?include_subgroups=true&per_page=100`;
try {
for (let page = 1; ; page++) {
const response = await fetch(`${BASE_URL}&page=${page}`, {
headers: { "PRIVATE-TOKEN": TOKEN },
});
if (!response.ok) throw new Error(`HTTP error! Status: ${response.status}`);
const projects = await response.json();
if (!projects.length) break;
projectIds.push(...projects.map(({ path_with_namespace }) => path_with_namespace));
}
} catch (error) {
console.error("Error fetching projects:", error);
}
return projectIds;
};
const enableSecretPushProtection = async (path) => {
const query = JSON.stringify({
query: `mutation {
setPreReceiveSecretDetection(input: {namespacePath: "${path}", enable: true}) {
preReceiveSecretDetectionEnabled
errors
}
}`,
});
try {
const response = await fetch(GRAPHQL_URL, {
headers: {
"content-type": "application/json",
Authorization: `Bearer ${TOKEN}`,
},
method: "POST",
body: query,
});
const { data, errors } = await response.json();
if (!response.ok) {
console.error(errors?.[0]?.message || "Unknown error");
} else if (data?.setPreReceiveSecretDetection?.errors?.length) {
console.error(data.setPreReceiveSecretDetection.errors[0].message);
} else {
console.log(`${path} : ${data.setPreReceiveSecretDetection.preReceiveSecretDetectionEnabled}`);
}
} catch (error) {
console.error(`Error enabling secret push protection for ${path}:`, error);
}
};
const main = async () => {
const projectPaths = await fetchProjects();
console.log(`Total projects under ${GROUP_PATH}: ${projectPaths.length}`)
projectPaths?.forEach(enableSecretPushProtection);
};
main();
```
</details>
### Long term solution
The secret detection team is working towards [introducing group/namespace-level settings for Secret Push Protection](https://gitlab.com/groups/gitlab-org/-/epics/14307 "[Post-GA] Introduce group/namespace-level setting for Secret Push Protection"). Once it's implemented and rolled out, we should be able to enable and enforce the feature by default for all groups and namespaces.
issue