Repository connections
for SAST and SCA
Connect GitHub, GitLab, or Bitbucket through OAuth so SecPortal can read the repositories your team chooses for code scanning. Tokens are encrypted at rest, scoped to the workspace, gated by RBAC, and recorded in the activity log on connect, configure, and disconnect.
No credit card required. Free plan available forever.
OAuth into your source control without handing the scanner a personal access token
Static analysis and dependency auditing only matter if the scanner can reach the source. For most teams that means giving a security platform read access to the repositories that back the production estate. The default path is a personal access token, pasted into a config field, owned by whichever individual happened to set it up. The token outlives the person, the scope is wider than it needs to be, and revocation is an open question when a team member leaves. SecPortal replaces that pattern with OAuth connections that belong to the workspace, hold encrypted tokens, and record every lifecycle event on the same audit trail that the rest of the platform uses.
The connection layer is the bridge between the OAuth grant and code scanning. A workspace configures a per-tenant OAuth app, an operator with the right RBAC permission completes the OAuth handshake, and the platform writes an encrypted git_connections row scoped to that workspace. From there the team adds specific repositories to SAST and SCA explicitly. The scanner only runs on repositories the workspace has chosen, and every step flows through the activity log rather than a separate integrations dashboard.
Three providers, one connection model
Source control fragments across vendors. SecPortal connects to GitHub, GitLab, and Bitbucket through one set of endpoints (configure, connect, callback, list, delete) and normalises the repository metadata so a mixed estate looks consistent inside the workspace.
GitHub
OAuth connection against github.com using the repo scope. SecPortal reads the repositories the connected user has push or admin access to and lists them in the workspace repository picker. The platform paginates up to five hundred repositories per connection so larger organisations are visible without manual selection.
GitLab
OAuth connection against gitlab.com with the read_repository and read_user scopes. SecPortal lists projects where the connected user holds at least Developer access, sorted by recent activity, so the picker shows the projects the team is actually working on rather than every project they can read.
Bitbucket
OAuth connection against bitbucket.org for Bitbucket Cloud workspaces. SecPortal reads the connected user identity and exposes the workspace repositories through the same picker as the other providers, so teams running mixed estates do not learn three different selection flows.
How OAuth tokens are stored
OAuth access tokens unlock everything downstream. SecPortal stores them with the same cryptographic primitive that protects authenticated scan credentials in encrypted credential storage. The properties below describe what the code actually enforces, not aspirational claims.
Encrypted access tokens
OAuth access tokens are encrypted with AES-256-GCM before being written to the git_connections table. The 12-byte initialisation vector and 16-byte authentication tag are stored alongside the ciphertext, so a database leak does not yield a usable token without the platform encryption key.
Encrypted refresh tokens when issued
Providers that return a refresh token on the OAuth grant have it stored under the same AES-256-GCM scheme as the access token, in distinct refresh_token, refresh_iv, and refresh_tag columns. Token refresh runs server-side; the browser never holds either secret.
Tenant-scoped storage with row-level security
Every git_connections row carries the workspace_id of the tenant that connected the provider. Postgres row-level security restricts SELECT and UPDATE to operators on that workspace, so a query without a workspace filter still cannot leak connections across tenants.
Per-workspace OAuth client credentials
OAuth client_id and client_secret values for each provider are stored in the git_provider_configs table, encrypted with the same AES-256-GCM scheme, and scoped to the workspace. Each tenant brings its own OAuth app rather than sharing a platform-wide client.
Server-only encryption boundary
Encrypt and decrypt run in server-side route handlers and the scan worker. The browser never sees the encryption key and never receives the plaintext token after the OAuth callback, so a compromised browser session cannot exfiltrate stored OAuth secrets.
Token expiry recorded on the row
The token_expires_at column records the absolute expiry of the access token when the provider returns one. Operations that require a non-expired token can check the column rather than relying on a probe request that would otherwise leak validity to the provider.
The OAuth flow, step by step
The flow is small enough to inspect end to end. Each step has a defined boundary between browser, application server, provider, and database, so an architecture review can confirm where the token lives at every moment.
- A workspace operator with the manage_repos permission opens settings, picks a provider, and pastes the OAuth client_id and client_secret of the workspace OAuth app. The values are encrypted server-side and stored on the git_provider_configs row for the (workspace, provider) pair.
- The operator clicks Connect. The platform builds the authorization URL with the workspace OAuth app credentials, the configured redirect URI, the requested scopes (repo on GitHub, read_repository plus read_user on GitLab, default scopes on Bitbucket), and a signed state parameter, and redirects the browser to the provider.
- The provider authenticates the operator, asks for consent against the requested scopes, and redirects back to /api/git/callback/[provider] with an authorization code and the original state.
- The callback validates the state, exchanges the code for an access token via exchangeCodeForToken, fetches the connected user identity from the provider through getAuthenticatedUser, and upserts the git_connections row with the encrypted access token, optional refresh token, scopes, and token expiry.
- The platform writes a git_provider_connected event to the activity log with the provider and the connected username in metadata, and redirects the browser back to settings with a success flag.
Who can connect, configure, and revoke
Repository connections sit on the same RBAC model as the rest of the platform through team management, gated by the AAL2 session promotion that multi-factor authentication enforces. The platform does not invent a parallel permission model for integrations.
- The manage_repos permission is held by owner, admin, and member team roles. Viewers and the billing role do not hold manage_repos, so read-only stakeholders cannot connect, configure, or disconnect a git provider.
- Every endpoint that touches git connections (provider config GET and POST, connect, callback, connections list, connections delete, repos list) checks manage_repos through requirePermission before any state change.
- OAuth client_id and client_secret values are never returned through the GET endpoints. The provider config list endpoint returns only the metadata (provider, configured_by, created_at, updated_at) rather than the encrypted credential fields.
- The connect endpoint and provider-config endpoints are rate-limited at the API layer. The provider-config POST is capped at ten saves per fifteen minutes per user, so a stolen session cannot bulk-stuff a workspace with provider apps.
- The platform AAL2 session promotion gates the dashboard reach, so connecting a provider inherits the same multi-factor authentication requirement as the rest of the workspace.
Repositories enabled for scanning are an explicit list, not an implicit grant
OAuth gives the platform read access to the repositories the connected user can reach. It does not give the platform permission to scan all of them. The connected_repos table records the explicit decision to scan each repository, with per-repo SAST and SCA flags and a default branch.
connected_repos as the explicit allow-list
A git_connections row gives SecPortal the OAuth permission to read repositories. A connected_repos row records the explicit decision to enable a specific repository for SAST and SCA. The repos picker reads from the live OAuth API; the scanner only runs on repositories that the workspace has chosen to add to connected_repos.
Per-repo SAST and SCA toggles
Each connected_repos row carries sast_enabled and sca_enabled boolean columns, so a workspace can enable static analysis on a backend service while skipping dependency auditing on a build artefact, or vice versa. The toggles run at the API layer rather than as a UI hint.
Default branch and language captured at add-time
The default_branch and language columns are populated when a repository is added, so subsequent scans run against the correct branch by default and the dashboard groups results by language without recomputing the heuristic on every render.
Cascade on connection removal
Removing a git_connections row cascades through connected_repos for that connection. Disconnecting a provider therefore also cleans up the per-repo enablement, so a workspace cannot leave orphan rows pointing at a connection that no longer holds an OAuth token.
last_scan_at on every repository row
The last_scan_at column records the timestamp of the most recent scan run against each repository. Operators can see at a glance which connected repositories are receiving regular coverage and which have drifted, without joining against a separate scan history table.
Scope filtering keeps the picker honest
Listing every repository the connected user can read is rarely the right behaviour. The repos endpoint applies provider-specific filters at the listing step so the workspace does not see repositories where the connected identity could not legitimately authorise a scan.
- GitHub repos are filtered to the user write-access set on the platform side. SecPortal exposes only repositories where the connected user holds push or admin permissions, so a user with read-only access to thousands of organisation repos is not exposed to a repo picker that pretends scanning is possible.
- GitLab projects are filtered server-side using min_access_level=30, the GitLab access level for Developer or above. A user who is a Reporter on hundreds of projects sees only the projects where they actually have the seat to authorise a scan against.
- Bitbucket connections list the workspaces the connected user belongs to, with repository visibility computed against the Bitbucket access model. The scanner does not act on a repository the connected user does not actually have permission to clone.
- The repos endpoint runs the listings of all configured providers in parallel through Promise.allSettled, so one slow or temporarily failing provider does not block the picker from rendering the rest.
- The repos endpoint never returns the access token or refresh token. The browser receives only the public repository metadata (full_name, default_branch, language, is_private, html_url, has_write_access, connection_id, provider) needed for the picker UI.
Lifecycle events on the activity log
Every meaningful change to a repository connection writes an event into the same activity feed that records findings, engagements, scans, and team changes. The audit answers the provenance questions a programme leader and an external assessor both ask.
- A git_provider_configured event is written when a workspace operator saves a provider OAuth app for the first time or updates an existing one. The event metadata records the provider name and the actor identity.
- A git_provider_connected event is written when the OAuth callback succeeds and a git_connections row is upserted. The event metadata records the provider and the provider_username so the audit answers which identity holds the workspace connection.
- A git_provider_disconnected event is written when an operator removes a single git_connections row through DELETE /api/git/connections/[id]. The event metadata records the provider and the provider_username of the removed connection.
- A git_provider_config_removed event is written when the entire provider OAuth app is deleted through DELETE /api/git/provider-config. The cascade also removes git_connections rows for that provider in the same workspace, so the audit shows the rollback of provider trust as one operator action.
- All four events flow into the same activity feed that records findings, engagements, scans, credentials, documents, and team changes. Programme leadership reviews repository-connection lifecycle from one place rather than from a separate integrations console.
Questions an enterprise reviewer should be able to answer in five minutes
Procurement, AppSec architecture, and audit each ask the same questions about any tool that holds OAuth tokens against source control. The connection layer is built so that the answers come from the dashboard or from the activity log rather than reconstructed from inference.
- Which providers are configured for this workspace today, who configured each one, and when?
- Which user accounts on which providers hold the active OAuth connections, and which scopes were granted?
- When was provider X connected, when was it last refreshed, and when is the access token due to expire?
- Which repositories are explicitly enabled for SAST or SCA, what is the default branch on each, and when was each one last scanned?
- When a workspace operator disconnected provider Y, did the cascade remove the connected_repos rows that depended on it?
- Are the OAuth access tokens or client secrets ever returned to the browser, and what is the boundary that prevents that?
Boundaries: what repository connections are and what they are not
Naming the boundary up front keeps the connection layer focused on its job and points teams at the right tool when they reach the edge.
Do
Use OAuth rather than personal access tokens. The connection lives on the workspace, not on an individual operator, so a team member leaving does not silently break code scanning the next time their token would have refreshed.
Do
Audit the connections list when a team member departs. A git_connections row carries the provider_username of the operator who completed the OAuth flow; revoke the row and have a current member reconnect rather than leaving stale identity attached to scans.
Do not
Treat repository connections as a CI plugin. SecPortal pulls source on demand for SAST and SCA runs and does not post status checks back to pull requests. For build-pipeline policy, run a pipeline scanner alongside SecPortal rather than wiring SecPortal as a PR gate.
Do not
Rely on a single OAuth app for cross-tenant scanning. Each workspace configures its own OAuth client; sharing a platform-wide app would conflate audit attribution and concentrate revocation risk. The per-workspace shape exists so revocation, rotation, and audit each stay tenant-scoped.
Where repository connections fit in the rest of the platform
Repository connections are an enabler, not a standalone product. They let code scanning run against the source the team actually ships, sit alongside encrypted credential storage for the runtime side of authenticated testing, and feed scan results into the same findings management record that drives remediation. The audit story for the connection layer flows through the activity log; the live signal flows through notifications and alerts.
Operationally, connecting repositories sits inside the workflow that runs through DevSecOps scanning and security testing program management. The cadence side of when scans run lives on the scan scheduling and baseline cadence guide.
For the buyer-side framing of who consumes this discipline, see the audience pages for AppSec teams, DevSecOps teams, security engineering teams, platform engineering teams, and product security teams.
For the comparable evaluation against code-security platforms that also touch source control, see SecPortal vs GitHub Advanced Security, SecPortal vs Snyk, and SecPortal vs Semgrep.
Stop pasting personal access tokens into security tools
One OAuth flow per provider. The team picks repos. The scanner reads them on demand. Every step lands on the activity log.
No credit card required. Free plan available forever.