feat(gitlab): support self-managed GitLab host across tools, block, triggers, webhook, and connector#5200
Conversation
…riggers, webhook, and connector Add an optional `host` so the GitLab integration can target a self-managed instance (e.g. gitlab.example.com) instead of gitlab.com. Defaults to gitlab.com everywhere, so existing workflows, blocks, triggers, and stored webhooks are unchanged. - Shared host helper (normalizeGitLabHost/getGitLabApiBase) used by all 19 tools, the block, triggers, the webhook provider, and the connector - SSRF hardening: reject structurally unsafe hosts (userinfo `@`, whitespace, control chars, embedded path/query, empty labels) before the token-bearing request is built; allow self-managed hosts, ports, and IDN punycode - Route the webhook provider's previously-raw fetches through secureFetchWithValidation (DNS + private-IP rejection + IP pinning), matching the tool and connector paths - Add regression tests for the host validator
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
PR SummaryMedium Risk Overview GitLab: New shared helpers Slack (unrelated): Temporarily drops the Reviewed by Cursor Bugbot for commit d3f4062. Configure here. |
|
@greptile |
|
@cursor review |
Greptile SummaryThis PR adds optional self-managed GitLab host support across all 19 tools, the block, triggers, the webhook provider, and the knowledge-base connector via a shared
Confidence Score: 5/5Safe to merge — the self-managed host path is additive and every surface defaults to gitlab.com, leaving existing workflows byte-identical. The shared normalizeGitLabHost helper correctly strips protocol and trailing slashes, rejects authority-confusion characters (userinfo @, embedded path/query, whitespace, consecutive dots), and delegates IP-literal SSRF to the existing secureFetchWithValidation chokepoint. Both subscribe and unsubscribe in the webhook provider now have explicit UnsafeGitLabHostError try/catch blocks. All 19 tools, the block, the triggers, and the connector are updated consistently from a single helper. The test suite is comprehensive and documents the layered-defense boundary explicitly. No logic changes affect the default gitlab.com path. No files require special attention. Important Files Changed
Reviews (4): Last reviewed commit: "fix(slack): drop assistant:write scope p..." | Re-trigger Greptile |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit bfca84c. Configure here.
Greptile SummaryThis PR adds an optional
Confidence Score: 4/5Safe to merge; all changed paths have backward-compatible defaults and the SSRF guards are layered (structural validator + DNS-layer fetch) across every surface. The two findings are both non-blocking style/documentation issues. The test gap (no IPv4 loopback case) leaves the structural-vs-DNS-layer design boundary undocumented rather than unprotected. The cleanupGitLabHookByUrl gap is unreachable in the current call graph but could surprise future maintainers. No correctness or security regression is introduced. apps/sim/lib/webhooks/providers/gitlab.ts (cleanupGitLabHookByUrl error-swallowing contract) and apps/sim/tools/gitlab/utils.test.ts (IPv4 coverage gap) are the two files worth a second look before merging. Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart TD
U["User-supplied host\n(optional)"] --> N["normalizeGitLabHost(rawHost)\n• trim / strip protocol & trailing slashes\n• assertSafeGitLabHostString\n – reject @, whitespace, /, ?, #, [, ]\n – reject empty labels, leading/trailing dot"]
N -->|empty / blank| D["gitlab.com (default)"]
N -->|unsafe chars| E["UnsafeGitLabHostError thrown"]
N -->|safe host| B["getGitLabApiBase\nhttps://<host>/api/v4"]
B --> T["GitLab Tools\n(19 tools via executeToolRequest)"]
B --> W["Webhook Provider\n(createSubscription / deleteSubscription)"]
B --> C["KB Connector\n(buildApiBase)"]
T --> DNS["validateUrlWithDNS\n+ secureFetchWithPinnedIP"]
W --> SV["secureFetchWithValidation"]
C --> SV2["secureFetchWithValidation"]
DNS -->|private / loopback IP| BLOCK["Request blocked"]
SV -->|private / loopback IP| BLOCK
SV2 -->|private / loopback IP| BLOCK
DNS -->|safe IP| GL["GitLab API"]
SV --> GL
SV2 --> GL
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
U["User-supplied host\n(optional)"] --> N["normalizeGitLabHost(rawHost)\n• trim / strip protocol & trailing slashes\n• assertSafeGitLabHostString\n – reject @, whitespace, /, ?, #, [, ]\n – reject empty labels, leading/trailing dot"]
N -->|empty / blank| D["gitlab.com (default)"]
N -->|unsafe chars| E["UnsafeGitLabHostError thrown"]
N -->|safe host| B["getGitLabApiBase\nhttps://<host>/api/v4"]
B --> T["GitLab Tools\n(19 tools via executeToolRequest)"]
B --> W["Webhook Provider\n(createSubscription / deleteSubscription)"]
B --> C["KB Connector\n(buildApiBase)"]
T --> DNS["validateUrlWithDNS\n+ secureFetchWithPinnedIP"]
W --> SV["secureFetchWithValidation"]
C --> SV2["secureFetchWithValidation"]
DNS -->|private / loopback IP| BLOCK["Request blocked"]
SV -->|private / loopback IP| BLOCK
SV2 -->|private / loopback IP| BLOCK
DNS -->|safe IP| GL["GitLab API"]
SV --> GL
SV2 --> GL
|
Address review feedback: - Validate the optional self-managed host up front in createSubscription and deleteSubscription so a structurally unsafe value surfaces as a clear error (create) or a graceful non-strict skip (delete) instead of an unhandled UnsafeGitLabHostError, mirroring the connector's handling - Document the layered SSRF defense: bare IP literals pass the structural host guard by design and are rejected at the fetch layer (validateUrlWithDNS); add a confirming test group making that intent explicit
|
Thanks for the review. Addressed in
|
|
@greptile |
|
@cursor review |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit 9b4b6e0. Configure here.
Requesting assistant:write before Slack approved it fails the OAuth/install flow for users. Remove it from both request paths until approval lands: - Remove from the user OAuth scope list (oauth.ts), matching the existing users:read.email TODO pattern - Remove the action_assistant capability from the bot manifest generator (capabilities.ts), leaving a TODO to restore it after approval The set_status/set_title/set_suggested_prompts tools remain and surface their existing graceful "reconnect with assistant:write" message until re-enabled.
|
@greptile |
|
@cursor review |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit d3f4062. Configure here.
Summary
gitlab.example.com) instead ofgitlab.comnormalizeGitLabHost/getGitLabApiBase)gitlab.comeverywhere — existing workflows, blocks, triggers, and already-deployed webhooks are byte-identical (fully backwards compatible)@, whitespace, control chars, embedded path/query, empty labels) are rejected before any token-bearing request is built; self-managed hosts, ports, and IDN punycode still workfetchcalls throughsecureFetchWithValidation(DNS resolution + private/loopback/metadata-IP rejection + IP pinning), matching the tool and connector pathsType of Change
Testing
apps/sim/tools/gitlab/utils.test.ts(host validator: defaults, self-managed hosts/ports/IDN, and the SSRF rejection matrix) — passing/path,?query,[::1], empty/leading/trailing-dot labels all rejected;gitlab.com, custom ports, punycode allowed)Checklist