https://git.your-app.com/team/project-alpha.git. Your server authenticates
each request with your own system and forwards it to Code Storage with a short-lived token.
Your users never receive a Code Storage token or your private key. You control:
- Authentication. Use your own sign-in, access tokens, or SSO.
- Authorization. Decide which user can read or write each repository, and set ref policies for each user.
- Revocation. When you remove access in your system, the next request fails.
- Access logs. Record the user, IP address, repository, and operation of each request.
Choose an approach
A proxy gives you the most control, but it is also the most work: you run a server in the path of every clone and push. Two lighter options do not need a proxy:- Short-lived remote URLs. Your backend gives each user a remote URL with a short-lived token. See Git remote URLs on the SDK page.
- A Git credential helper. You ship a credential helper that gets a short-lived token from your API each time Git needs credentials.
How it works
- Forward only the Git endpoints below, and return
404for every other path. - Return
401when the user’s credentials are missing or incorrect, and404when the user cannot access the repository. - Sign a new token for each request, with the repository, the scopes that the request needs, your
user ID as
sub, and your ref policies. - Never forward the user’s
Authorizationheader to Code Storage. - Stream the request body and the response body.
The endpoints that Git uses
Git over HTTPS uses four requests for each repository, under/{repo}.git/:
The example proxy does not support Git LFS. It returns
404 for LFS requests.
To add LFS, see Support Git LFS.Build the proxy
The proxy is a Fetch API handler: it takes aRequest and returns a Response. This example runs it on Node.js with
srvx and signs tokens with jose. Install
the packages:
PIERRE_ORG and PIERRE_PRIVATE_KEY as shown in Authentication.
Each step below adds code to proxy.ts, in order. The complete file is at
the end of this section.
Set up
proxy.ts
Match the Git endpoints
Each route sets the access that its endpoint needs. A request that matches no route gets404.
proxy.ts
Check the user
The handler authenticates the user, checks access to the repository, and writes one log line for each request. The line has the user, IP address, repository, endpoint, and response status.proxy.ts
Connect your user system
Put your own logic inauth.ts. It exports two functions:
authenticate(request)returns the user, as an object with anid, ornull. Git sends the user’s credentials as HTTP Basic auth in theAuthorizationheader. Fornull, the proxy returns401, and Git asks the user or its credential helper for credentials.authorize(user, repo, access)returnstrueif the user can access the repository.accessis'read'for a clone or fetch, and'write'for a push. Forfalse, the proxy returns404, so a user cannot find out which repositories exist.
Sign a token
The token covers one repository and the scopes that the request needs. It setssub to your user
ID, which Code Storage shows in its logs. The token expires after 300 seconds. It only needs to be
valid when Code Storage receives the request, so a long push still completes.
proxy.ts
If you use a restricted key, its allowlist must
include
git:read and git:write. A restricted key also limits the token lifetime to 3,600
seconds.Forward the request
The proxy forwards only the headers that Git needs. It does not forward the user’sAuthorization
header. A clone or push can transfer a large pack, so the proxy streams both bodies and does not
buffer them.
proxy.ts
Serve the proxy
proxy.ts
handleGit() uses only the Fetch API. To run it on another server, pass it each Request and the
client IP address, and return its Response.
Run the proxy with Node.js 24 or later. Node.js runs TypeScript files directly. Set
"type": "module" in package.json.
The complete proxy
proxy.ts
proxy.ts
proxy.ts
Use the remote
Give your users the URL of your proxy. Git sends their credentials with HTTP Basic auth, so they can use a Git credential helper as they do with any other host:authenticate() accepts,
for example a username and an access token from your product. The proxy matches repository paths
that end in .git, so give your users URLs in this form.
Attribute pushes
Push webhooks identify the repository and ref, but not the user who pushed. Your proxy log identifies the user for eachgit-receive-pack request. Use your proxy log as the record
of who pushed.
Test the proxy
Run these checks before you give the remote to users:- Clone a repository through the proxy with a valid user.
- Push to a branch that the user can write.
- Clone with an incorrect token. The proxy returns
401. - Clone a repository that the user cannot access. The proxy returns
404. - Push to a ref that a ref policy blocks. Git shows
push denied by policy.
Support Git LFS
The example proxy returns404 for Git LFS requests. To support LFS, add the
steps in this section to proxy.ts.
The endpoints that Git LFS uses
The Git LFS client finds the LFS server at your remote URL, under/{repo}.git/info/lfs/. It uses
these requests:
A batch request is a
POST for downloads and for uploads. The operation field in the request body
tells which. A download batch needs git:read. An upload batch needs git:read and
git:write.
Match the LFS endpoints
SetPROXY_URL to the public URL of your proxy, for example https://git.your-app.com. The proxy
uses it in the batch response.
proxy.ts
Check the user for LFS
The LFS handler is the same ashandleGit(), with two changes. It reads the access of a batch
request from the body. It also rewrites the batch response.
proxy.ts
Rewrite the batch response
Each action in the batch response has anhref and a header. An action that contains the token of
your proxy points at Code Storage. The proxy changes that action:
- The
hrefgoes to your proxy, with the same path. - The
Authorizationheader is the user’s own header from the batch request. The LFS client sends it with the transfer, so your proxy can authenticate the user again.
proxy.ts
Pass redirects to the client
A download can get a307 redirect to a signed object storage URL. Return the redirect to the LFS
client, so that the client downloads the object directly. In forward(), add location to the
response headers, and set redirect: 'manual':
proxy.ts
Serve LFS requests
Send LFS requests tohandleLFS(), and all other requests to handleGit():
proxy.ts
Test LFS
Run these checks with a repository that tracks a file with LFS:- Push a commit with an LFS file through the proxy.
- Clone the repository through the proxy. The LFS file has its full content, not a pointer.
- Clone with a user that can read but not write. The clone succeeds, and a push of an LFS file
returns
404. - Run
GIT_TRACE=1 git lfs pull. No LFS request goes directly toyour-org.code.storage.