Skip to content

Commit fb456f3

Browse files
Jonathan Muchaclaude
andcommitted
Add enable_merge_pipeline_check() to enforce pipelines-must-succeed via API
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent d0c568a commit fb456f3

File tree

4 files changed

+92
-6
lines changed

4 files changed

+92
-6
lines changed

instructions/gitlab-commit-status/uat.md

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,16 @@
3939
1. Run: `socketcli --scm github --enable-commit-status`
4040
2. **Expected**: Flag is accepted but commit status is not posted (GitHub not yet supported)
4141

42-
## Configuring Protected Branch Requirement
43-
1. Go to **Settings > Repository > Protected branches**
44-
2. Edit the target branch
45-
3. Under **Status checks**, add `socket-security` as a required external status check
46-
4. MRs targeting that branch will now require Socket's `success` status to merge
42+
## Blocking Merges on Failure
43+
44+
### Option A: Pipelines must succeed (all GitLab tiers)
45+
Since `socketcli` exits with code 1 when blocking alerts are found, the pipeline fails automatically.
46+
1. Go to **Settings > General > Merge requests**
47+
2. Under **Merge checks**, enable **"Pipelines must succeed"**
48+
3. Save — GitLab will now prevent merging when the pipeline fails
49+
50+
### Option B: External status checks (GitLab Ultimate only)
51+
Use the `socket-security` commit status as a required external check.
52+
1. Go to **Settings > General > Merge requests > Status checks**
53+
2. Add an external status check with name `socket-security`
54+
3. MRs will require Socket's `success` status to merge

socketsecurity/core/scm/gitlab.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,32 @@ def add_socket_comments(
267267
log.debug("No Previous version of Security Issue comment, posting")
268268
self.post_comment(security_comment)
269269

270+
def enable_merge_pipeline_check(self) -> None:
271+
"""Enable 'only_allow_merge_if_pipeline_succeeds' on the MR target project."""
272+
if not self.config.mr_project_id:
273+
return
274+
url = f"{self.config.api_url}/projects/{self.config.mr_project_id}"
275+
try:
276+
resp = requests.put(
277+
url,
278+
json={"only_allow_merge_if_pipeline_succeeds": True},
279+
headers=self.config.headers,
280+
)
281+
if resp.status_code == 401:
282+
fallback = self._get_fallback_headers(self.config.headers)
283+
if fallback:
284+
resp = requests.put(
285+
url,
286+
json={"only_allow_merge_if_pipeline_succeeds": True},
287+
headers=fallback,
288+
)
289+
if resp.status_code >= 400:
290+
log.error(f"GitLab enable merge check API {resp.status_code}: {resp.text}")
291+
else:
292+
log.info("Enabled 'pipelines must succeed' merge check on project")
293+
except Exception as e:
294+
log.error(f"Failed to enable merge pipeline check: {e}")
295+
270296
def set_commit_status(self, state: str, description: str, target_url: str = '') -> None:
271297
"""Post a commit status to GitLab. state should be 'success' or 'failed'.
272298

socketsecurity/socketcli.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -645,6 +645,7 @@ def main_code():
645645
if config.enable_commit_status and scm is not None:
646646
from socketsecurity.core.scm.gitlab import Gitlab
647647
if isinstance(scm, Gitlab) and scm.config.mr_project_id:
648+
scm.enable_merge_pipeline_check()
648649
passed = output_handler.report_pass(diff)
649650
state = "success" if passed else "failed"
650651
blocking_count = sum(1 for a in diff.new_alerts if a.error)

tests/unit/test_gitlab_commit_status.py

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ def test_calls_correct_url_and_json_payload(self, mock_post):
4343
"https://gitlab.example.com/api/v4/projects/99/statuses/abc123def456",
4444
json={
4545
"state": "success",
46-
"context": "socket-security",
46+
"context": "socket-security-commit-status",
4747
"description": "No blocking issues",
4848
"ref": "feature",
4949
"target_url": "https://app.socket.dev/report/123",
@@ -111,6 +111,57 @@ def test_auth_fallback_on_401(self, mock_post):
111111
assert "PRIVATE-TOKEN" in fallback_headers
112112

113113

114+
class TestEnableMergePipelineCheck:
115+
"""Test Gitlab.enable_merge_pipeline_check()"""
116+
117+
@patch("socketsecurity.core.scm.gitlab.requests.put")
118+
def test_calls_correct_url_and_payload(self, mock_put):
119+
mock_put.return_value = MagicMock(status_code=200)
120+
config = _make_gitlab_config()
121+
gl = Gitlab(client=MagicMock(), config=config)
122+
123+
gl.enable_merge_pipeline_check()
124+
125+
mock_put.assert_called_once_with(
126+
"https://gitlab.example.com/api/v4/projects/99",
127+
json={"only_allow_merge_if_pipeline_succeeds": True},
128+
headers=config.headers,
129+
)
130+
131+
@patch("socketsecurity.core.scm.gitlab.requests.put")
132+
def test_skipped_when_no_mr_project_id(self, mock_put):
133+
config = _make_gitlab_config(mr_project_id=None)
134+
gl = Gitlab(client=MagicMock(), config=config)
135+
136+
gl.enable_merge_pipeline_check()
137+
138+
mock_put.assert_not_called()
139+
140+
@patch("socketsecurity.core.scm.gitlab.requests.put")
141+
def test_auth_fallback_on_401(self, mock_put):
142+
resp_401 = MagicMock(status_code=401)
143+
resp_200 = MagicMock(status_code=200)
144+
mock_put.side_effect = [resp_401, resp_200]
145+
146+
config = _make_gitlab_config()
147+
gl = Gitlab(client=MagicMock(), config=config)
148+
149+
gl.enable_merge_pipeline_check()
150+
151+
assert mock_put.call_count == 2
152+
fallback_headers = mock_put.call_args_list[1].kwargs["headers"]
153+
assert "PRIVATE-TOKEN" in fallback_headers
154+
155+
@patch("socketsecurity.core.scm.gitlab.requests.put")
156+
def test_graceful_error_handling(self, mock_put):
157+
mock_put.side_effect = Exception("connection error")
158+
config = _make_gitlab_config()
159+
gl = Gitlab(client=MagicMock(), config=config)
160+
161+
# Should not raise
162+
gl.enable_merge_pipeline_check()
163+
164+
114165
class TestEnableCommitStatusCliArg:
115166
"""Test --enable-commit-status CLI argument parsing"""
116167

0 commit comments

Comments
 (0)