@agent-core

Git remotes

Connect a git remote so agents can push and pull: one-click OAuth on GitHub, GitLab, Bitbucket and Codeberg, per-user push tokens, and the OAuth App credential.

A git remote connection gives an agent (and you) the credentials to authenticate push / pull / fetch against a hosted git provider. Just like channels and external MCP servers, connections are personal: the git.connect feature is granted to members by default, every user connects their own remote, and the resulting push token is stored encrypted in your credential scope — other members never see it.

Two ideas define the design:

  • Connections are personal, HTTPS-only. Git remotes authenticate over HTTPS using a per-user token as the password — there is no SSH key path. Your token lives in your own credential scope and is never echoed back to the browser after it is stored.
  • Connecting is not running. Connecting a remote only stores the credential and a small non-secret binding (host, account, auth kind). The actual git operations run separately and require the usual filesystem permissions.

There are two ways to connect the same per-user push token: paste a Personal Access Token (works with any host), or run the one-click OAuth flow. Both land the same token in your user scope; OAuth just avoids ever handling the token by hand.

OAuth is available for the hosts whose authorization endpoints are fixed and public: github.com, gitlab.com, bitbucket.org and codeberg.org. A self-hosted Gitea or Forgejo instance, a generic remote, and Azure DevOps are refused by name — use a PAT there. The reason is deliberate: the token exchange POSTs your OAuth App's client secret to the host, so Neuralis will only send it to an endpoint it knows at build time, never to an address an operator typed in. (In this release the one-click button in the Git remotes group starts the GitHub flow; the other three hosts are reachable through the API.)

Where you connect

Open the Connections section of the chat client-config panel and find the Git remotes group (the same section holds MCP servers, your credentials and the connectors). The group is only visible to a caller who holds git.connect; the server re-checks the feature on every request regardless of what the UI shows.

The group offers:

  • a write-only PAT form (host, account label, and the token — stored in your own scope, shown once and never again), and
  • a Connect with GitHub button that starts the OAuth flow, plus
  • a list of your connected hosts with a disconnect action.

One-click OAuth

When you start the flow, Neuralis:

  1. builds the host's authorization URL with whatever scope that host takes on an individual grant — repo on GitHub, read_repository write_repository on GitLab, and none at all on Bitbucket or Codeberg (see below) — plus a PKCE challenge, then redirects your browser there to approve access;
  2. receives the host's redirect back at your Neuralis origin (/api/oauth/callback), verifies it matches the flow you started, and exchanges the code for a token server-side over an SSRF-guarded, IP-pinned HTTPS request — the same guarded exchange every outbound OAuth flow in Neuralis now shares;
  3. stores the resulting per-user push token (and refresh token, when returned) in your own user credential scope, and writes a non-secret binding recording that this project/host is connected via OAuth.

The token is exchanged and stored entirely server-side — it is never returned to the page. Disconnecting a host removes every stored git token for that host and the binding in one idempotent step.

What a grant is allowed to do differs per host, and it is worth knowing before you connect:

  • GitHub — repo, the scope a private-repository push needs.
  • GitLab — read_repository write_repository, repository access only.
  • Bitbucket — no scope is sent. Bitbucket Cloud defines scopes on the OAuth consumer and rejects a grant that asks for more than the consumer carries, so the permissions come from how you registered the consumer.
  • Codeberg — no scope is sent, because Forgejo does not implement OAuth2 scopes: the issued token carries your full account rights, not repository-only access. A Codeberg connection is all-or-nothing; connect with a scoped personal access token instead if that matters to you.

Pending OAuth flows are single-use and short-lived, and a callback that does not match the user who started the flow is rejected.

Registering an OAuth App

The one-click flow needs an OAuth App on the host you are connecting — its confidential-client credential (a client_id + client_secret pair). Register one per host you want to use, once:

  1. On GitHub: Settings → Developer settings → OAuth Apps → New OAuth App (a personal account or an organization both work). GitLab, Bitbucket and Codeberg have the equivalent surface under their own application settings. On Bitbucket, grant the consumer repository read + write while you register it — Bitbucket takes the permissions from the consumer, and no scope is sent per connection.

  2. Authorization callback URL — set it to your Neuralis origin plus the callback path:

    https://<your-neuralis-origin>/api/oauth/callback

    Use the exact public origin your users reach Neuralis at (for a local dev instance that is typically http://localhost:3100). This must match the redirect Neuralis sends, or the host refuses the exchange.

  3. The host issues a Client ID and a Client Secret. You store both in Neuralis in the next step.

Only these two values are needed. Neuralis derives the redirect URL from its own configured origin, so you do not configure it anywhere else.

The OAuth App credential

Each host's client_id / client_secret are stored as its own pair of canonical Neuralis credentials:

Credential idSecret?Holds
github.oauth.clientId / github.oauth.clientSecretid no, secret yesthe GitHub OAuth App pair
gitlab.oauth.clientId / gitlab.oauth.clientSecretid no, secret yesthe GitLab application pair
bitbucket.oauth.clientId / bitbucket.oauth.clientSecretid no, secret yesthe Bitbucket OAuth consumer pair
codeberg.oauth.clientId / codeberg.oauth.clientSecretid no, secret yesthe Codeberg OAuth App pair

Neuralis picks the pair from the host you are connecting — the id is never taken from a request, so a connect flow can only ever read its own host's App. The Client ID identifies the App at the authorize step, and the GitHub pair also drives the GitHub MCP connect — one registered App serves both the git-remote flow and the MCP one. The Client Secret is the confidential-client secret sent at the token exchange, read at the connecting member's scope; the connect flow cannot complete without it.

Like every credential, they are resolved most-specific-wins across scopes (agent → project → user → global), which gives you two ways to run the flow:

  • Owner-provisioned org App (recommended). An owner/admin registers one organization OAuth App and stores the Client ID/Secret at the platform (or project) scope from the admin Credentials surface. Every member's one-click connect then uses that shared App with no per-member setup.
  • Member self-seeded App. A member can register their own OAuth App and set the same two credential ids in their own user scope from the Credentials group (the credentials.self feature). Their personal App overrides the shared one for their own flows.

Either way, the credential is resolved at the same scope at both the start and the completion of the flow, so a member's own App is used consistently end to end. If no Client ID is configured for the caller, the OAuth flow fails cleanly and the PAT path remains available.

Per-user push tokens vs. the App credential

Keep the two credential kinds distinct:

  • The OAuth App credential (<host>.oauth.clientId / clientSecret) is the application identity — one App per host, shared or personal, that drives the connect handshake.
  • The per-user push token (obtained by OAuth, or pasted as a PAT) is the user's credential that actually authenticates git operations. It always lives in that user's own scope and is never shared or echoed.

Project- or agent-scoped git tokens (a shared service account, for example) are managed by owners/admins on the admin Credentials surface, not through the personal Git remotes group.

Feature and scope

git.connect is a member-default feature: connecting your own remote does not require owner/admin. Connecting a token is separate from running git: the actual fetch / pull / push run from the Git Control panel in the Files UI — or, for an agent, through the brain-core git skill, which calls the same routes with the same rights — and additionally require your filesystem write feature and the source's exec path policy — without exec, the panel shows a "grant exec" affordance instead of the controls. Each operation uses your own connected token, released only to the exact connected host over HTTPS. See features and access and the security model. Owner/admin management of shared, project-scoped git credentials lives on the admin Credentials surface.

On this page