Sign inSign up

socketdev/gitlab

By socketdev

Updated 14 days ago

Socket on-prem GitLab app

Image
0

1.5K

socketdev/gitlab repository overview

@socketsecurity/socket-gitlab

Socket's on-prem GitLab integration. Scans your GitLab projects for dependency security issues and surfaces alerts in the Socket dashboard and on merge requests.

What It Does

  • Syncs GitLab projects in a group to Socket repositories automatically
  • Creates full scans on every push (visible in the Socket dashboard)
  • Comments on merge requests with dependency changes and alerts
  • Can block merges: a "Socket Security" commit status with the "Pipelines must succeed" merge check (ENABLE_COMMIT_STATUS + ENFORCE_MERGE_CHECKS, see Merge enforcement) and, on GitLab Ultimate, an external status check (ENABLE_EXTERNAL_STATUS_CHECKS, see External status checks)
  • Honors per-repository socket.yml overrides (Per-repository configuration)
  • Picks up newly created projects without restarts

Requirements

  • Any GitLab edition
  • PostgreSQL >= 15
  • Docker
  • Network path for GitLab webhook delivery to the container on TCP port 5050

Setup

1. Create a PostgreSQL Database

Create a Postgres 15+ database for the app. Example config used throughout this guide:

SettingValue
Usernameuser
Passwordpasswd
Hostdbhost
Databasesocket-gitlab
2. Create a Socket Organization

Go to https://socket.dev/dashboard/create-organization and create a new org.

Important: This org should be dedicated to the GitLab integration. The app manages Socket repositories to mirror your GitLab projects, and will add/delete repos as projects are created or removed.

3. Create a Socket API Token

Go to Settings > Integrations > API Tokens in your Socket org.

Required scopes:

  • full-scans (all)
  • diff-scans (all)
  • repo (all)
4. Identify Your GitLab Group

Decide which GitLab group (top-level or subgroup) to scan. Find the group ID:

  1. Go to the group page in GitLab
  2. Click the ... menu
  3. Select "Copy group ID"
5. Create a GitLab Access Token

GitLab Premium/Ultimate: Create a group access token from the group page:

  • Go to Settings > Access Tokens > Add new token
  • Note that when the app comments on merge requests the token's name will appear as the comment's author. We suggest putting 'socket' in the name for clarity
  • Role: Owner for the default setup (the app manages the group webhook) — Minimal role by configuration works out the floor for every combination of features
  • Required scopes: api, read_repository

GitLab Free tier: Group access tokens are disabled. Create a personal access token instead:

  • Go to User Settings > Access Tokens > Add new token
  • Role: Maintainer on each project for the default setup (the app manages the per-project webhooks) — see Minimal role by configuration
  • Required scopes: api, read_repository
6. Configure Webhook Delivery

The app needs to receive HTTP(S) webhooks from GitLab at the path /webhook/gitlab on port 5050.

Production: Configure your network/load balancer to route traffic from GitLab to the container.

Local testing: Use a tunnel service:

# cloudflared (recommended, no signup required)
brew install cloudflared
cloudflared tunnel --url http://localhost:5050

# ngrok (requires free account)
ngrok http 5050

Unless DISABLE_WEBHOOK_AUTO_SETUP is set, the app creates and maintains its own webhook during the initial project sync — a group-level hook on Premium/Ultimate, one per project on Free. To register the webhook yourself instead, see Self-registering webhooks.

7. Configure Environment

Create a .env file with your configuration:

# Required
DATABASE_URL=postgres://user:passwd@dbhost/socket-gitlab
SOCKET_ORG=your-socket-org
SOCKET_API_KEY=sktsec_yourtoken_api
GITLAB_GROUP_ID=123456
GITLAB_TOKEN=glpat-yourtoken
EXTERNAL_WEBHOOK_URL=https://your-external-url/webhook/gitlab

# Optional
# GITLAB_INSTANCE=https://gitlab.example.com    # defaults to https://gitlab.com
# SERVICE_ENV=prod                              # dev, ci, staging, prod
# ENABLE_DEPENDENCY_OVERVIEW_COMMENTS=true      # defaults to true (overview comments enabled)
# ENABLE_ALERTS_COMMENTS=true                   # defaults to true (alerts comments enabled)
8. Run the Container
docker run -d -p 5050:5050 --env-file .env --name socket-gitlab socketdev/gitlab:latest

On startup the app will:

  1. Run database migrations
  2. Create/update the GitLab group webhook
  3. Sync all projects in the group to Socket repositories
  4. Begin processing webhook events
9. Verify Your Setup

Check the container is healthy:

curl http://localhost:5050/health
# Expected: {"statusCode":200,"status":"ok"}

Check the logs for successful startup:

docker logs socket-gitlab

Look for these messages in order:

  • pg-boss started (database connection working)
  • GitLab webhook configured (webhook created/verified on GitLab)
  • Server listening at ... (HTTP server ready)
  • SYNCED PROJECTS (GitLab projects mirrored to Socket)

Trigger a test scan:

Push a commit to any project in the configured GitLab group. Within a few seconds, the logs should show Found N manifest files and the scan will appear in your Socket dashboard under Scans.

If no scan appears after pushing, check the Troubleshooting section.

Best Practices

  • Commit lockfiles (package-lock.json, yarn.lock, pnpm-lock.yaml). Without a lockfile, Socket only sees direct dependencies. With a lockfile, the full transitive dependency tree is scanned, which catches significantly more issues.
  • Dedicated Socket org: Use a separate Socket org for this integration since the app manages repository lifecycle.
  • Token expiration: Set calendar reminders for access token expiry dates.

Development — running the tests

npm test is eslint → tsc → node --test. Some tests need PostgreSQL, and they are not read-only: they DELETE rows and install pg-boss's schema. So the suite refuses to run against anything but a disposable database whose name ends in _test:

createdb socket-gitlab_test
DATABASE_URL=postgres://postgres@localhost/socket-gitlab_test npm run migrate
npm test   # unset DATABASE_URL resolves to socket-gitlab_test automatically

Point DATABASE_URL at any other database and the run aborts before it opens a connection (test/db-guard.ts). Without a *_test database the pure-logic tests still run; the PostgreSQL-dependent ones fail on the connection.

No Postgres at all? A throwaway container does the job — and on a custom port, so it cannot collide with a developer Postgres already listening on 5432. The container tool ships with the AI workflow harness (CLAUDE.md → The AI workflow harness): without an ai-harness/ folder in the checkout, use the plain createdb path above; with one, the commands are:

node ai-harness/scripts/pg-container.ts start    # postgres:17 on localhost:5433, DB socket-gitlab_test, migrations applied
DATABASE_URL=postgres://postgres:postgres@localhost:5433/socket-gitlab_test npm test
node ai-harness/scripts/pg-container.ts stop     # removes the container; its database existed only inside it

start reuses a running container (prints is already up …); stop is safe to run twice (prints was not running; nothing to stop). The URL is what test/db-guard.ts requires — the database name ends in _test — and it is the one to use, because start runs the migrations into it.

What the container does and does not unblock. Of the five allowlisted failures (ai-harness/scripts/verify_session.shEXPECTED_FAILURES), the container fixes one locally: ensureWebhook recreates a project hook deleted from GitLab in a single pass, which needs only the database. The other four — plugins/health.test.ts, plugins/pg.test.ts, plugins/pgboss.test.ts and routes/get-root.test.ts — build the full app through test/helper.ts, and plugins/gitlab.ts calls Users.showCurrentUser() at boot; without a valid GITLAB_TOKEN they fail with 401 Unauthorized regardless of the database. CI supplies both a Postgres service and a real token, which is why the suite is green there and the allowlist stays at five.

Environment Variable Reference

VariableRequiredDefaultDescription
DATABASE_URLNopostgres://postgres@localhost/socket-gitlabPostgreSQL connection string
SOCKET_ORGYesSocket organization slug
SOCKET_API_KEYYesSocket API token
GITLAB_GROUP_IDYesGitLab group ID to scan
GITLAB_TOKENYesGitLab access token with api scope, used for notes, commit statuses, webhooks and project sync — and for cloning too, unless GITLAB_READ_TOKEN or GITLAB_TOKENS is set
GITLAB_TOKENSNoJSON object mapping GitLab group, subgroup or project paths to access tokens used when cloning those repositories; the longest matching path prefix wins and anything unmatched falls back to GITLAB_READ_TOKEN, then to GITLAB_TOKEN
GITLAB_READ_TOKENNoRead-only token (scope read_repository) used as the default clone credential, so the api-scoped GITLAB_TOKEN is never handed to a clone; falls back to GITLAB_TOKEN when unset
EXTERNAL_WEBHOOK_URLYesPublicly reachable webhook URL
ENABLE_DEPENDENCY_OVERVIEW_COMMENTSNotrueAdd Socket dependency overview comment/note on merge requests
ENABLE_ALERTS_COMMENTSNotrueAdd Socket alert comment/note on merge requests
ENABLE_COMMIT_STATUSNofalsePost a "Socket Security" commit status (running/success/failed) on merge request head commits
ENFORCE_MERGE_CHECKSNofalseAutomatically enable each project's "Pipelines must succeed" merge check so a failed Socket commit status blocks merging (requires ENABLE_COMMIT_STATUS=true)
ENABLE_EXTERNAL_STATUS_CHECKSNofalse(GitLab Ultimate only) Register a "Socket Security" external status check on each project and report the scan verdict to it
GITLAB_INSTANCENohttps://gitlab.comGitLab instance URL (for self-hosted)
SERVICE_ENVNodevEnvironment: dev, ci, staging, prod
SOCKET_BASE_URLNohttps://api.socket.dev/v0/Socket API base URL
DEBUG_PROXY_URLNoHTTP proxy for debugging requests
DISABLE_WEBHOOK_AUTO_SETUPNofalseSkip all webhook creation and management in GitLab; requires GITLAB_WEBHOOK_SECRET for incoming deliveries to validate
GITLAB_WEBHOOK_SECRETNoSecret token of the webhook the operator created by hand; only read when DISABLE_WEBHOOK_AUTO_SETUP is true

Token setup (least privilege)

The app uses its tokens for two different jobs, and they do not need the same power. Writes — merge request notes, commit statuses, webhook management, project sync — go through GITLAB_TOKEN and need the api scope. Clones need only read_repository, and that is what GITLAB_READ_TOKEN (one token for everything) and GITLAB_TOKENS (one token per path prefix) are for. Set at least GITLAB_READ_TOKEN and the api-scoped token is never handed to a git clone.

JobVariableMinimal scopeMinimal role
API writes — notes, commit statuses, webhooks, syncGITLAB_TOKENapiFrom Guest to OwnerMinimal role by configuration works out the floor
Cloning, defaultGITLAB_READ_TOKENread_repositoryReporter
Cloning, per path prefixGITLAB_TOKENS valuesread_repositoryReporter on that group, subgroup or project

Leave GITLAB_READ_TOKEN unset and GITLAB_TOKEN does the cloning as well, so it then needs read_repository on top of api — which is the configuration the startup audit below stops warning about.

Minimal role by configuration

The role the api-scoped write token (GITLAB_TOKEN) needs is set by the features it must use: every enabled feature has a floor, and the token needs the highest floor of the features you turn on.

Feature the token must doWhen it appliesRole floor
MR comments, project sync, socket.yml reads (reporting only)every deploymentGuest
Post commit statusesENABLE_COMMIT_STATUS=trueDeveloper
Register and report external status checksENABLE_EXTERNAL_STATUS_CHECKS=true (Ultimate)Developer
Enable the "Pipelines must succeed" merge checkENFORCE_MERGE_CHECKS=trueMaintainer on each project
Create and maintain the app's own webhooksdefault — DISABLE_WEBHOOK_AUTO_SETUP unset or falseOwner on the group; Maintainer per project on the Free per-project fallback

In practice:

  • No commit statuses, and you register the webhooks yourself (see Self-registering webhooks) — a Guest token is the minimum role.
  • Commit statuses or external status checks — the floor rises to Developer.
  • The app registers the webhooks (the default) — the floor rises to Owner on the group, or Maintainer per project on the Free per-project fallback.
  • ENFORCE_MERGE_CHECKS implies ENABLE_COMMIT_STATUS, so its Maintainer floor subsumes the Developer one.

The clone credentials (GITLAB_READ_TOKEN, GITLAB_TOKENS) are unaffected: read_repository scope and a Reporter role.

Which kinds of token you can create depends on the tier:

Token kindFreePremiumUltimate
Personal access token
Group access token
Project access token✅ self-managed only

On GitLab Free, where group access tokens are unavailable, use a personal access token for GITLAB_TOKEN and personal or project access tokens for the clone credentials.

Startup scope audit. At boot the app asks GitLab which scopes each configured token actually carries (GET /api/v4/personal_access_tokens/self) and logs one line per token: a warn naming the extra scopes if a token is broader than its job needs, an info otherwise. It is advisory — an over-privileged token, an unreachable instance, or an endpoint that does not answer for a given token kind all leave the app running. Log lines name the variable and the scope names, never a token value.

Self-registering webhooks (lower-privilege deployments)

By default the app creates and manages its own GitLab webhook — the most demanding of the role floors: Owner for a group webhook, Maintainer per project on the Free per-project fallback. Security teams that will not grant webhook-write permission can turn that off and register the hook themselves: set DISABLE_WEBHOOK_AUTO_SETUP=true and give the app the secret via GITLAB_WEBHOOK_SECRET. With auto-setup off the app makes no webhook API call of any kind during sync -- no plan probe, no role probe, no per-project hook writes.

Setting it up
  1. In GitLab, go to Settings > Webhooks and add a hook:
    • URL -- the same value you configured as EXTERNAL_WEBHOOK_URL.
    • Secret token -- any random string; you will pass this to the app.
    • Enable Push events and Merge request events.
  2. Set both variables on the app: DISABLE_WEBHOOK_AUTO_SETUP=true and GITLAB_WEBHOOK_SECRET=<the same secret>. If the flag is set and the secret is not, sync logs a warning and continues, and every incoming delivery is rejected.
  3. The hook does not need a custom header. The app validates on the secret alone, through the same path it already uses for hooks created before custom headers existed.
What this does not change

Merge request comments, commit statuses and merge-check enforcement still need an api-scoped token. This only removes the webhook-write requirement.

Tier note

Group webhooks are a Premium/Ultimate feature. On Free you must create the hook on each project instead -- and every one of them must use the same secret, because the app stores exactly one.

Switching modes

Neither direction cleans up after the other, because automatic cleanup only removes hooks carrying the app's x-socketdev-webhook-id header:

  • Turning self-registration on leaves the app-created hook in place. Delete it by hand or every event is delivered twice.
  • Turning it off again makes the app create and manage its own hook, and leaves your manual hook in place. Delete that one by hand.

Per-repository configuration (socket.yml)

A repository can override the instance-wide defaults with a socket.yml (or socket.yaml) file in its root:

projectIgnorePaths:
  - dist
  - "vendor/**"

gitlabApp:
  enabled: true
  dependencyOverviewEnabled: true
  pullRequestAlertsEnabled: true
  commitStatusEnabled: false
KeyApplies toEffect
projectIgnorePathsfull scans (pushes and the MR flow's before/after scans)List of glob patterns; matching paths — and everything under them — are left out of the scan's manifest report
gitlabApp.enabledmerge request scansfalse skips the whole merge request scan: no commit status, no comments, no external status check
gitlabApp.dependencyOverviewEnabledmerge request scansOverrides ENABLE_DEPENDENCY_OVERVIEW_COMMENTS for this repository
gitlabApp.pullRequestAlertsEnabledmerge request scansOverrides ENABLE_ALERTS_COMMENTS for this repository
gitlabApp.commitStatusEnabledmerge request scansOverrides ENABLE_COMMIT_STATUS for this repository
  • The gitlabApp keys only govern merge request scans: a push still creates a full scan, which honors projectIgnorePaths only.
  • Merge request scans read the file at the MR's head commit through the GitLab Repository Files API; full scans read it from the tree at the scanned commit. When both names exist, socket.yml wins.
  • The file is capped at 1 MiB. An oversized, unparseable or unreadable file is logged as a warning and treated as absent — a bad socket.yml never fails a scan. Unknown keys are ignored.
  • There is no key for the external status check: with ENABLE_EXTERNAL_STATUS_CHECKS=true, every repository the app scans reports it. To exempt a repository entirely, set gitlabApp.enabled: false.

Merge enforcement

With ENABLE_COMMIT_STATUS=true and ENFORCE_MERGE_CHECKS=true, the app automatically enables each project's "Pipelines must succeed" merge check during project sync. Combined with commit statuses, a failed Socket status then blocks the merge.

What counts as blocking

A scan verdict is failed when the diff scan's alerts carry at least one artifact whose org security policy action is block or error — the action Socket's dashboard renders as Block (the raw diff-scan JSON spells it error). Alerts whose action is warn, monitor or ignore never block.

Requirements
  • The GitLab token needs Maintainer+ role on each project for the setting to be applied.
  • This merge check is available on all GitLab tiers (Free, Premium, Ultimate).
Manual alternative

The same setting can be enabled manually per project: GitLab UI → SettingsMerge requests → check "Pipelines must succeed".

Warning

The "Pipelines must succeed" check applies to all pipelines — not just Socket. Merge requests whose Socket status is canceled (scan could not be created) or that have no pipeline will stay unmergeable until a rescan succeeds. Roll out ENFORCE_MERGE_CHECKS=true per your tolerance for this behavior.

External status checks (GitLab Ultimate)

On Ultimate, Socket can additionally report its verdict as a first-class external status check rather than only as a commit status. Enable it with:

ENABLE_EXTERNAL_STATUS_CHECKS=true

Then, per project, turn on GitLab UI → SettingsMerge requests"Status checks must succeed".

How it behaves
  • On a project's first merge request scan the app registers a check named "Socket Security", unless one by that name already exists. Registration is find-or-create by that exact name, so rescans update the existing check rather than adding duplicates.
  • After each scan the app posts passed or failed, matching the commit status verdict: failed when the diff scan found blocking alerts — at least one alert whose action is block or error, as defined under What counts as blockingpassed otherwise.
  • A scan that cannot complete posts nothing — the check stays pending in GitLab until a rescan succeeds. An infrastructure failure is deliberately not a pass.
  • On Free and Premium, and whenever the plan cannot be detected, the flag is ignored: the app logs the reason and makes no external status check API calls at all.
  • Errors talking to GitLab about the check are logged and swallowed. They never fail the scan and never change the commit status that was already posted.
Relationship to the commit status

The two channels are independent: ENABLE_COMMIT_STATUS governs the commit status, ENABLE_EXTERNAL_STATUS_CHECKS governs the external status check, and either may run without the other. A repository's socket.yml can override the first per repo (gitlabApp.commitStatusEnabled) but has no key for the second — an Ultimate deployment that opted in reports the external check for every repository it scans. To exempt one repository from Socket entirely, set gitlabApp.enabled: false, which skips its merge request scans and therefore posts neither.

The check's callback URL

GitLab requires every external status check to carry an external_url and POSTs merge request payloads to it. The app registers EXTERNAL_WEBHOOK_URL — the same URL your webhooks already use — because it reports proactively from the scan flow and needs no callback. Those POSTs are rejected by the webhook route before anything is enqueued: it requires an idempotency-key header and a valid x-gitlab-token, and a status check callback carries neither.

Requirements
  • GitLab Ultimate. The external status checks API is unavailable on Free and Premium.
  • The GitLab token needs at least the Developer role on each project — the role floor for this feature; see Minimal role by configuration.

Troubleshooting

App starts but no scans appear after pushing

  • Check that webhooks are being delivered.
  • Verify the EXTERNAL_WEBHOOK_URL is reachable from GitLab. Test with: curl -X POST <your-url>

403 "No valid x-gitlab-token" in logs

  • The webhook secret doesn't match. This can happen if the webhook was modified outside the app. Restart the app or delete the webhook from GitLab and let the app recreate it.

Scan has fewer alerts than expected

  • Check if the repo has a lockfile committed. Without one, only direct dependencies are scanned.

"Push to unknown project id" warning

  • The project was created after the last sync. Restart the app to pick up new projects.

Tag summary

Content type

Image

Digest

sha256:7e80396c2

Size

123 MB

Last updated

14 days ago

docker pull socketdev/gitlab