Ctadel

Set up the Ctadel GitLab OAuth Application

GitLab doesn't have a true equivalent of GitHub Apps (where the bot is its own identity). The closest practice is an OAuth Application: each customer authorizes Ctadel and we hold a refreshable access token. The trade-off versus GitHub Apps: the token authorizes as the authorizing user, but unlike a PAT it can be refreshed and doesn't require the user to copy-paste anything.

Step 1, create the OAuth Application

  1. Go to gitlab.com → User Settings → Applications → Add new application. (For a group-scoped app, use Group → Settings → Applications instead — but a user-scoped app works fine for SaaS where each customer authorizes individually.)

  2. Fill in:

    • Name: e.g. Ctadel Scanner.
    • Redirect URI: https://<your-ctadel-host>/api/integrations/gitlab/callback. Must match exactly — GitLab refuses the callback otherwise.
    • Confidential: ✅ checked (we hold the client secret server-side).
    • Scopes: api, read_repository. Don't grant sudo or admin scopes.
  3. Click Save application. GitLab shows you the Application ID and Secret once. Copy both — the Secret is shown only at creation time.

Step 2, set the env vars

In the deployment env file:

CTADEL_GITLAB_OAUTH_CLIENT_ID=<application id>
CTADEL_GITLAB_OAUTH_CLIENT_SECRET=<application secret>
NEXT_PUBLIC_CTADEL_GITLAB_OAUTH_CLIENT_ID=<application id>

The redirect URI is automatically derived from APP_BASE_URL + /api/integrations/gitlab/callback, so make sure APP_BASE_URL is set to your Ctadel host (e.g. https://app.acme.ctadel.eu).

Step 3, restart the stack

docker compose up -d --force-recreate csm-orchestrator csm-frontend

Look for the log line:

GitLab OAuth initialized (redirect=https://.../api/integrations/gitlab/callback)

Step 4, smoke-test

  1. In Ctadel Settings → Integrations, click Connect Ctadel to GitLab.
  2. GitLab prompts to authorize the Ctadel app on your account. Approve.
  3. Back in Ctadel you'll see a "GitLab connected — finish the setup" banner with a one-time webhook secret. Copy it.
  4. On each GitLab project you want to scan, go to Settings → Webhooks → Add new webhook:
    • URL: https://<your-ctadel-host>/webhook/git
    • Secret token: paste the webhook secret from Step 3.
    • Triggers: ✅ Push events, ✅ Merge request events.
  5. Push a trivial commit. The scan should fire.

Why webhooks are still per-project on GitLab

GitLab OAuth doesn't give us a global webhook stream like GitHub Apps do. Each project keeps its own webhook configuration. The webhook secret we generate is the same for all of a customer's projects (it's stored once per integration) so the customer can copy-paste the same value in each project.

Permissions surface

ScopeWhy
apiPost MR comments + commit statuses.
read_repositoryClone the repo at the scanned commit.

We do not request sudo, admin_mode, read_user, or any write scopes outside MR/status.

Token rotation

GitLab access tokens are short-lived (2h by default) and refreshed automatically. GitLab rotates the refresh token on every refresh, so Ctadel persists the new pair via the orchestrator's UpdateGitLabOAuthToken path. If a customer revokes the app from their account, the next refresh fails and the integration is marked last_error="oauth refresh failed".

Troubleshooting

  • redirect_uri_mismatch at the authorize step — your APP_BASE_URL differs from the redirect URI registered on the OAuth Application. They must match byte-for-byte (scheme, host, port, path).
  • invalid_grant at refresh — the refresh token was already rotated by a concurrent call. Mark the integration disabled and have the customer re-authorize.

What's next