SCM Connection Reference
This page is the single source of truth for connecting a source-control provider to a self-hosted Plexicus deployment. For each provider it gives the four things you need and cannot guess:
- which authentication flow it uses (OAuth App vs GitHub App vs personal access token),
- the exact callback URL(s) to register with the provider,
- the exact scopes or permissions to grant,
- which chart values, Secret keys, or in-app fields carry the credentials.
Throughout, <your-domain> is the value of global.domain in your Helm
overlay (PLEXICUS_DOMAIN on the evaluator) — for example plexicus.acme.com,
or plexicus.local on a default evaluator install.
Plexicus installs and runs its first scan with no source-control provider connected at all — the Sandbox onboarding flow scans a pre-configured repository through a synthetic connector. Everything on this page is needed only when you want to scan your own repositories. See Evaluator Installation and Self-Hosted Installation.
Supported providers
| Provider | Deployment | Flow | Credentials live in |
|---|---|---|---|
| GitHub (github.com) | SaaS | GitHub App (recommended) or OAuth App — browser redirect | Chart values + Secret |
| GitHub Enterprise Server | Self-managed | Personal access token, or OAuth App, or GitHub App | In-app connector form, per connection |
| GitLab (gitlab.com) | SaaS | OAuth application — browser redirect | Chart values + Secret |
| GitLab self-managed | Self-managed | OAuth application registered on your instance — browser redirect | In-app connector form, per connection |
| Bitbucket Cloud | SaaS only | OAuth consumer — browser redirect | Chart values + Secret |
| Gitea | Self-managed | Personal access token only | In-app connector form, per connection |
| Forgejo | Self-managed | Personal access token only | In-app connector form, per connection |
| Azure DevOps | Cloud and Server/TFS | Personal access token | In-app connector form, per connection |
| TFVC | On-premises | Shares the Azure DevOps connection and PAT | In-app connector form, per connection |
Plexicus supports Bitbucket Cloud only. There is no Bitbucket Server /
Data Center provider in the platform: no OAuth handler, no repository plugin,
no client for Bitbucket Server's /rest/api/1.0 API, and no entry in the
connector catalogue. The Bitbucket connector has no instance-URL field and
every call it makes is hard-coded to api.bitbucket.org, so it cannot be
pointed at a self-managed Bitbucket. If you run Bitbucket Data Center, mirror
the repositories to a supported forge or contact
engineering@plexicus.ai.
Providers whose credentials live in the in-app connector form need nothing in your Helm overlay: each connection carries its own token or client credentials, entered under Connectors → SCM after the platform is running. Only GitHub, GitLab and Bitbucket Cloud have platform-level (chart-level) credentials, and only for their SaaS instances.
Callback URLs
Every browser OAuth redirect lands on the frontend origin — the same
hostname users log in on, with no api. prefix. The chart derives these
URLs from global.domain automatically; you only have to register the matching
value with the provider.
| Registered with | Callback URL to register | Used by |
|---|---|---|
| GitHub App / OAuth App | https://<your-domain>/api/callback/github | Both "Sign in with GitHub" and the repository connector |
| GitHub App (second URL) | https://api.<your-domain>/vulnerability-tool/callback/github | The standalone vulnerability-tool OAuth flow and the App-installation hand-off |
| GitLab application | https://<your-domain>/api/callback/gitlab | Repository connector (gitlab.com and self-managed) |
| Bitbucket Cloud consumer | https://<your-domain>/api/callback/bitbucket_cloud | Repository connector |
| Google OAuth client (SSO) | https://<your-domain>/api/callback/google | "Sign in with Google" |
| Gitea, Forgejo, Azure DevOps, TFVC | (none) | Token-based — no redirect, nothing to register |
This is the single most common self-hosted misconfiguration. Plexicus has two distinct GitHub OAuth surfaces:
- Sign-in and the repository connector both redirect to
https://<your-domain>/api/callback/github— the frontend origin, noapi.prefix. - The vulnerability-tool flow and the App-installation hand-off use
https://api.<your-domain>/vulnerability-tool/callback/github— served by the API, so it does carry theapi.prefix. Note the hyphen invulnerability-tool.
A GitHub App accepts multiple callback URLs (Add Callback URL on the app
settings page). Register both. Registering only the first leaves the App
installation hand-off dead-ending; registering only the second breaks sign-in
and the connector with redirect_uri_mismatch.
GitLab and Bitbucket Cloud have only one callback each — the frontend one.
The browser is what follows these redirects, so a hostname that only resolves
on your own machine (a .local name from /etc/hosts, for instance) works
fine for OAuth. Inbound webhooks are a different matter — see
Webhooks.
GitHub
github.com — register a GitHub App
A GitHub App is preferred over a plain OAuth App: it supports webhooks and per-repository install/uninstall, which an OAuth App cannot do.
On github.com go to Settings → Developer settings → GitHub Apps → New GitHub App (register it under the organisation's settings for an organisation-wide install):
-
Homepage URL:
https://<your-domain> -
Callback URLs — add both, using Add Callback URL for the second:
https://<your-domain>/api/callback/githubhttps://api.<your-domain>/vulnerability-tool/callback/github
-
Request user authorization (OAuth) during installation: checked.
-
Webhook → Active: check it only if your
<your-domain>is resolvable from GitHub's network — see Webhooks. -
Repository permissions:
Permission Level Needed for Metadata Read-only Repository discovery (mandatory) Contents Read-only Cloning and scanning Pull requests Read & write Remediation pull requests Webhooks Read & write Push-triggered rescans
Then: note the App ID and the app slug (from its URL), generate a
client secret, and generate a private key (a .pem file downloads).
Finally install the app on your account or organisation — the installation URL
is https://github.com/apps/<slug>/installations/new, and Plexicus needs that
URL too.
Plexicus sends no scope parameter on the GitHub authorize URL. A GitHub
App's access is defined entirely by the repository permissions above and by
which repositories the App is installed on. This is also why a plain OAuth App
grants less than you probably expect — another reason to prefer the App flow.
GitHub Enterprise Server
GHES is connected per connection in the UI, not through the chart. Under Connectors → SCM → GitHub, choose GitHub Enterprise Server and fill in:
- GitHub Enterprise URL — e.g.
https://ghe.corp.local - Then exactly one credential set; Plexicus auto-detects which mode you meant:
- a Personal Access Token (
ghp_…orgithub_pat_…), or - an OAuth App Client ID + Client Secret, or
- a GitHub App ID + private key (PEM)
- a Personal Access Token (
Only the OAuth App mode performs a browser redirect. It sends the user to
https://<your-ghes-host>/login/oauth/authorize and comes back to
https://<your-domain>/api/callback/github — so that is the Authorization
callback URL to register on the OAuth App you create inside GHES. The PAT and
GitHub App modes complete without leaving Plexicus and need no callback URL at
all.
Plexicus reaches a GHES instance's REST API at <your-ghes-url>/api/v3 and its
GraphQL API at <your-ghes-url>/api/graphql; both must be reachable from the
cluster.
Chart values and Secret keys (github.com only)
Public identifiers go in your values overlay:
global:
required:
oauth:
github:
clientId: "Iv23li…" # → GITHUB_OAUTH_CLIENT_ID
appId: "1234567" # → GITHUB_APP_ID — quote it, see below
appInstallationUrl: "https://github.com/apps/<slug>/installations/new"
appId — an unquoted 7-digit number renders as 3.004825e+06Helm round-trips values.yaml through JSON, where every number becomes a Go
float64, and Go's shortest-form float printer switches to scientific notation
at 1,000,000. An unquoted appId: 1234567 therefore reaches the container as
GITHUB_APP_ID=1.234567e+06, which no GitHub API accepts — and nothing in the
render fails, so the only symptom is that App-authenticated calls quietly stop
working. Write it as a string: appId: "1234567". The chart's own default
is a quoted empty string, and the production and staging overlays quote theirs.
Leave freeSastToolUrl unset. Despite its name it is what the backend
receives as GITHUB_OAUTH_REDIRECT_URI, and when it is empty the chart derives
<scheme>://<domain>/api/callback/github — exactly the callback the SPA sends
at /authorize. Set it only if you are running the Plexicus free-scan
marketing funnel, in which case it must be one of the callback URLs registered
on the App.
Private values go in Kubernetes Secrets:
| Secret | Key | Value |
|---|---|---|
plexicus-fastapi | GITHUB_OAUTH_CLIENT_SECRET | The App's client secret |
plexicus-fastapi | GITHUB_APP_PRIVATE_KEY | The .pem, base64-encoded |
plexicus-worker | GITHUB_APP_PRIVATE_KEY | Same value — the worker needs it to open pull requests |
plexicus-frontend | NUXT_GITHUB_SECRET_KEY | The App's client secret |
clientId, appId and appInstallationUrl all default to "". Empty means
"this provider is not configured" — the connector is hidden rather than shown
behind a button that cannot work.
The chart's values.schema.json additionally rejects two specific literals
by value: Plexicus's own GitHub App id, and Plexicus's own OAuth client id. Both
once shipped as chart defaults, which pointed every self-hosted install's GitHub
traffic at an App that Plexicus owns. An overlay that reintroduces either now
fails at render time instead.
So there is nothing to pin off. Set clientId only when you have registered
your own App, and leave it empty otherwise.
Do not write appId: 0. The schema types appId as a string, so a bare
0 fails validation, and "" — not 0 — is the value that means "no GitHub
App".
Let Plexicus create the App for you
You do not have to register the App by hand. When a deployment has no GitHub App configured, the product offers to create one through GitHub's App Manifest flow: go to Settings → Integrations → GitHub, where a panel headed No GitHub App is configured on this deployment offers Create GitHub App automatically.
The button appears only when both are true: the deployment has no working App,
and your account holds the backoffice-settings admin permission. Clicking it
POSTs a prepared manifest to https://github.com/settings/apps/new; you review
and confirm on GitHub's own page, GitHub creates the App and redirects back,
and Plexicus stores the credentials and puts them straight into use — the
scanning worker picks them up on its own refresh loop, so nothing needs a
restart.
The generated App requests contents: read, metadata: read,
pull_requests: write, issues: write and statuses: write, subscribes to
push, pull_request and pull_request_review, and is created private.
Two limits are worth knowing before you choose this path over registering by hand:
- It registers one callback URL,
https://<your-domain>/api/callback/github— not the second,api.-prefixed one described in Callback URLs. Add that one by hand afterwards if you need the App-installation hand-off. - Webhooks are included only when your domain is publicly resolvable.
Against a private or
.localhostname the manifest omits them and the callback page says so — see Webhooks.
There is also no in-product way to replace an App once one is stored; the button is offered only while the deployment has none.
A stored App takes precedence over the chart values: once one exists, both the
backend and the Nuxt login proxy prefer it over clientId/clientSecret from
the environment — but only when the stored record carries both halves. A
half-stored App never displaces a working environment pair.
GitLab
Scopes
Plexicus requests the same eight scopes for gitlab.com and for self-managed instances:
api, read_api, read_user, read_repository, write_repository,
openid, profile, email
openid, profile and email identify the connecting user; read_repository
and read_api cover discovery and scanning; write_repository and api cover
remediation merge requests, webhooks and CI-variable reads. Granting fewer than
these makes the authorization succeed and the later repository calls fail.
gitlab.com
Register the application once, at the platform level: User Settings → Applications on gitlab.com (or Admin Area → Applications for an instance-wide registration).
- Redirect URI:
https://<your-domain>/api/callback/gitlab - Confidential: keep checked
- Scopes: the eight above
Then wire the Application ID and Secret in:
global:
required:
oauth:
gitlab:
clientId: "<application-id>" # → GITLAB_OAUTH_CLIENT_ID
| Secret | Key |
|---|---|
plexicus-fastapi | GITLAB_OAUTH_CLIENT_SECRET |
plexicus-frontend | NUXT_GITLAB_SECRET_KEY |
Self-managed GitLab
A self-managed instance is connected per connection, in the UI — it does not use the chart-level client id or secret. Each connection carries the client credentials of an application registered on your own GitLab.
See GitLab → Self-Hosted for the full
walkthrough. In short: register an application on your instance with redirect
URI https://<your-domain>/api/callback/gitlab and the eight scopes above,
then in Plexicus open Connectors → SCM → GitLab, choose GitLab
Self-Hosted, and enter the instance URL, the Application ID and the Secret.
Because the redirect URI is the same for both, one Plexicus deployment can serve a gitlab.com connection and any number of self-managed connections at once.
Bitbucket Cloud
Bitbucket Cloud only — see the note in Supported providers.
In your workspace: Settings → OAuth consumers → Add consumer.
-
URL (homepage):
https://<your-domain> -
Callback URL:
https://<your-domain>/api/callback/bitbucket_cloud -
This is a private consumer: keep checked
-
Permissions:
Permission Level Needed for Account Read Identifying the connecting user Repositories Read Discovery and scanning Pull requests Write Remediation pull requests Issues Write Issue creation from findings
Plexicus sends no scope parameter to Bitbucket; the consumer's permission
checkboxes are what the token ends up carrying, so set them before you connect.
Expand the saved consumer to reveal its Key and Secret:
global:
required:
oauth:
bitbucket:
key: "<consumer-key>" # → BITBUCKET_OAUTH_CLIENT_ID
| Secret | Key |
|---|---|
plexicus-fastapi | BITBUCKET_OAUTH_CLIENT_SECRET |
plexicus-frontend | NUXT_BITBUCKET_CLOUD_SECRET |
Gitea and Forgejo
Both are personal-access-token connectors. Neither uses OAuth, and neither needs any chart value, Secret key or callback URL. Attempting an OAuth redirect for Gitea or Forgejo is rejected by the platform with an explicit error message pointing you back at the token form.
Generate the token on your instance under Settings → Applications → Generate New Token, granting at minimum:
- repository: read — discovery, cloning and scanning
- user: read — Plexicus verifies the token against
/api/v1/user - repository: write — additionally required for remediation pull requests
Then in Plexicus open Connectors → SCM → Gitea (or Forgejo) and fill in:
- Gitea URL / Forgejo URL — origin only, e.g.
https://gitea.corp.localorhttps://gitea.corp.local:3000. No path, no trailing slash. - Token — the personal access token
Click Connect; Plexicus validates the token against your instance before saving it.
GITEA_CLIENT_ID, GITEA_CLIENT_SECRET and GITEA_REDIRECT_URI exist in the
platform's configuration surface, but the Helm chart sets none of them and the
connector UI does not use them. Configure Gitea and Forgejo with a personal
access token, as above.
Webhooks
Push-triggered rescans require your provider to make an inbound HTTP request to your Plexicus deployment. That is the opposite direction from the OAuth redirects, and it is where self-hosted topologies commonly fall short.
Webhooks cannot work when:
<your-domain>is a.localname, an/etc/hostsentry, or any hostname that does not resolve on the public internet — github.com, gitlab.com and bitbucket.org cannot look it up;- the deployment sits behind a firewall or on a private network with no inbound path from the provider;
- you use a self-signed or private-CA certificate that the provider will not trust.
None of this affects scanning. Manual scans and scheduled scans work normally;
only push-triggered rescans are unavailable. To enable them on an evaluator,
use a publicly resolvable domain — plexicus.<vm-ip>.nip.io avoids editing
/etc/hosts entirely — and leave Webhook → Active checked on the GitHub
App.
A self-managed provider (GitHub Enterprise Server, self-managed GitLab, Gitea, Forgejo, Azure DevOps Server) only needs to reach Plexicus across your own network, so webhooks from those are usually fine even on a private hostname.
Troubleshooting
redirect_uri_mismatch (GitHub) / invalid_redirect_uri (GitLab, Bitbucket)
right after clicking Authorize
The callback URL registered with the provider is not what Plexicus sent.
Compare them character by character: same scheme (https vs http), same
hostname (no api. prefix for the sign-in and connector callbacks), no
trailing slash, no query string. For GitHub, confirm that both callback URLs
from Callback URLs are registered.
400 Bad Request at token exchange, with no provider error page
The client id is configured but its secret is not — a half-configured
connector renders a working Connect button that can only fail at the
exchange step. Check that the matching *_OAUTH_CLIENT_SECRET key exists in
the plexicus-fastapi Secret and is non-empty:
kubectl -n plexicus get secret plexicus-fastapi \
-o jsonpath='{.data.GITHUB_OAUTH_CLIENT_SECRET}' | base64 -d | wc -c
0 means it was never set. On GitHub specifically, confirm the clientId in
your overlay is the one from your own App — see
Chart values and Secret keys.
The connector says Connected but no repositories appear
The token authorized successfully but lacks read access. Re-check the scopes or permissions table for your provider above; for GitHub, confirm the App is actually installed on the account or organisation that owns the repositories, not merely authorized.
Scans run but no remediation pull requests are created
Write access is missing: GitHub Pull requests: Read & write, GitLab
write_repository and api, Bitbucket Pull requests: Write, Gitea/Forgejo
repository write. On TFVC, pull requests are not supported at all.
Next steps
- Evaluator Installation — one-command evaluation install
- Self-Hosted Installation — production Helm deployment
- Configuration Reference — every provider integration, not just SCM
- Per-provider walkthroughs: GitHub, GitLab, Bitbucket Cloud, Gitea, Forgejo, Azure DevOps