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.
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)socket.yml overrides (Per-repository configuration)Create a Postgres 15+ database for the app. Example config used throughout this guide:
| Setting | Value |
|---|---|
| Username | user |
| Password | passwd |
| Host | dbhost |
| Database | socket-gitlab |
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.
Go to Settings > Integrations > API Tokens in your Socket org.
Required scopes:
full-scans (all)diff-scans (all)repo (all)Decide which GitLab group (top-level or subgroup) to scan. Find the group ID:
... menuGitLab Premium/Ultimate: Create a group access token from the group page:
api, read_repositoryGitLab Free tier: Group access tokens are disabled. Create a personal access token instead:
api, read_repositoryThe 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.
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)
docker run -d -p 5050:5050 --env-file .env --name socket-gitlab socketdev/gitlab:latest
On startup the app will:
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.
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.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.sh → EXPECTED_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.
| Variable | Required | Default | Description |
|---|---|---|---|
DATABASE_URL | No | postgres://postgres@localhost/socket-gitlab | PostgreSQL connection string |
SOCKET_ORG | Yes | Socket organization slug | |
SOCKET_API_KEY | Yes | Socket API token | |
GITLAB_GROUP_ID | Yes | GitLab group ID to scan | |
GITLAB_TOKEN | Yes | GitLab 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_TOKENS | No | JSON 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_TOKEN | No | Read-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_URL | Yes | Publicly reachable webhook URL | |
ENABLE_DEPENDENCY_OVERVIEW_COMMENTS | No | true | Add Socket dependency overview comment/note on merge requests |
ENABLE_ALERTS_COMMENTS | No | true | Add Socket alert comment/note on merge requests |
ENABLE_COMMIT_STATUS | No | false | Post a "Socket Security" commit status (running/success/failed) on merge request head commits |
ENFORCE_MERGE_CHECKS | No | false | Automatically 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_CHECKS | No | false | (GitLab Ultimate only) Register a "Socket Security" external status check on each project and report the scan verdict to it |
GITLAB_INSTANCE | No | https://gitlab.com | GitLab instance URL (for self-hosted) |
SERVICE_ENV | No | dev | Environment: dev, ci, staging, prod |
SOCKET_BASE_URL | No | https://api.socket.dev/v0/ | Socket API base URL |
DEBUG_PROXY_URL | No | HTTP proxy for debugging requests | |
DISABLE_WEBHOOK_AUTO_SETUP | No | false | Skip all webhook creation and management in GitLab; requires GITLAB_WEBHOOK_SECRET for incoming deliveries to validate |
GITLAB_WEBHOOK_SECRET | No | Secret token of the webhook the operator created by hand; only read when DISABLE_WEBHOOK_AUTO_SETUP is true |
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.
| Job | Variable | Minimal scope | Minimal role |
|---|---|---|---|
| API writes — notes, commit statuses, webhooks, sync | GITLAB_TOKEN | api | From Guest to Owner — Minimal role by configuration works out the floor |
| Cloning, default | GITLAB_READ_TOKEN | read_repository | Reporter |
| Cloning, per path prefix | GITLAB_TOKENS values | read_repository | Reporter 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.
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 do | When it applies | Role floor |
|---|---|---|
MR comments, project sync, socket.yml reads (reporting only) | every deployment | Guest |
| Post commit statuses | ENABLE_COMMIT_STATUS=true | Developer |
| Register and report external status checks | ENABLE_EXTERNAL_STATUS_CHECKS=true (Ultimate) | Developer |
| Enable the "Pipelines must succeed" merge check | ENFORCE_MERGE_CHECKS=true | Maintainer on each project |
| Create and maintain the app's own webhooks | default — DISABLE_WEBHOOK_AUTO_SETUP unset or false | Owner on the group; Maintainer per project on the Free per-project fallback |
In practice:
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 kind | Free | Premium | Ultimate |
|---|---|---|---|
| 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.
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.
EXTERNAL_WEBHOOK_URL.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.Merge request comments, commit statuses and merge-check enforcement still need an api-scoped
token. This only removes the webhook-write requirement.
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.
Neither direction cleans up after the other, because automatic cleanup only removes hooks
carrying the app's x-socketdev-webhook-id header:
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
| Key | Applies to | Effect |
|---|---|---|
projectIgnorePaths | full 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.enabled | merge request scans | false skips the whole merge request scan: no commit status, no comments, no external status check |
gitlabApp.dependencyOverviewEnabled | merge request scans | Overrides ENABLE_DEPENDENCY_OVERVIEW_COMMENTS for this repository |
gitlabApp.pullRequestAlertsEnabled | merge request scans | Overrides ENABLE_ALERTS_COMMENTS for this repository |
gitlabApp.commitStatusEnabled | merge request scans | Overrides ENABLE_COMMIT_STATUS for this repository |
gitlabApp keys only govern merge request scans: a push still creates a full scan,
which honors projectIgnorePaths only.socket.yml wins.socket.yml never fails a scan. Unknown keys are
ignored.ENABLE_EXTERNAL_STATUS_CHECKS=true,
every repository the app scans reports it. To exempt a repository entirely, set
gitlabApp.enabled: false.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.
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.
The same setting can be enabled manually per project: GitLab UI → Settings → Merge requests → check "Pipelines must succeed".
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.
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 → Settings → Merge requests → "Status checks must succeed".
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 blocking —
passed otherwise.pending in GitLab until a
rescan succeeds. An infrastructure failure is deliberately not a pass.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.
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.
App starts but no scans appear after pushing
EXTERNAL_WEBHOOK_URL is reachable from GitLab. Test with: curl -X POST <your-url>403 "No valid x-gitlab-token" in logs
Scan has fewer alerts than expected
"Push to unknown project id" warning
Content type
Image
Digest
sha256:7e80396c2…
Size
123 MB
Last updated
14 days ago
docker pull socketdev/gitlab