fix: private repository cloning with authentication token #1268

Merged
mfenniak merged 1 commit from syncstack/runner:fix/git-credential-store-url-format into main 2026-01-12 16:30:24 +00:00
Contributor

After patch that replaced go-git with shell git commands, the runner fails to clone private repositories with error:

fatal: could not read Username for 'https://...': No such device or address

Root cause:

The credential-store helper expects credentials in URL format (https://user:token@host/path), but the current implementation writes only the raw token to the file, causing git to prompt for username interactively.

Solution:

  • Format credentials as proper URL with embedded token: https://x-access-token:TOKEN@host/path
  • Add credential.useHttpPath=true to respect repository paths in credential matching
  • Parse remote URL to extract host and construct valid credential-store entry
  • bug fixes
    • PR: fix: private repository cloning with authentication token
After patch that replaced go-git with shell git commands, the runner fails to clone private repositories with error: ``` fatal: could not read Username for 'https://...': No such device or address ``` Root cause: The credential-store helper expects credentials in URL format (`https://user:token@host/path`), but the current implementation writes only the raw token to the file, causing git to prompt for username interactively. Solution: - Format credentials as proper URL with embedded token: `https://x-access-token:TOKEN@host/path` - Add `credential.useHttpPath=true` to respect repository paths in credential matching - Parse remote URL to extract host and construct valid credential-store entry <!--start release-notes-assistant--> <!--URL:https://code.forgejo.org/forgejo/runner--> - bug fixes - [PR](https://code.forgejo.org/forgejo/runner/pulls/1268): <!--number 1268 --><!--line 0 --><!--description Zml4OiBwcml2YXRlIHJlcG9zaXRvcnkgY2xvbmluZyB3aXRoIGF1dGhlbnRpY2F0aW9uIHRva2Vu-->fix: private repository cloning with authentication token<!--description--> <!--end release-notes-assistant-->
feat(git): support token-based HTTP authentication
All checks were successful
checks / validate mocks (pull_request) Successful in 54s
checks / validate pre-commit-hooks file (pull_request) Successful in 1m1s
checks / build and test (pull_request) Successful in 1m33s
checks / runner exec tests (pull_request) Successful in 46s
checks / runner integration tests (pull_request) Successful in 5m31s
checks / integration tests (docker-latest) (pull_request) Successful in 10m22s
checks / integration tests (docker-stable) (pull_request) Successful in 12m33s
issue-labels / release-notes (pull_request_target) Successful in 5s
cascade / debug (pull_request_target) Has been skipped
cascade / end-to-end (pull_request_target) Successful in 6s
cascade / forgejo (pull_request_target) Successful in 1m9s
808b4d824d
Add support for passing Git token to HTTP remote repos.
Parse the remote URL, embed the token into the credential helper, and set `credential.useHttpPath=true` so Git sends an `Authorization` header without prompting.
Also add tests to verify the token is sent and no interactive prompt occurs.
Member

@syncstack Thanks a lot for reporting, fixing, and testing the problem. Looks great. Could you provide your reproduction steps? I'd like to check whether they match up with mine.

@mfenniak We seem to need more and better tests, ideally some with a running Forgejo instance. Does that a better fit for https://code.forgejo.org/forgejo/end-to-end/ or should we introduce such tests here, but this time in a separate directory so that we can separate them from the rest of the test suite? We already have two such tests here (internal/app/cmd/create-runner-file_test.go).

@syncstack Thanks a lot for reporting, fixing, and testing the problem. Looks great. Could you provide _your_ reproduction steps? I'd like to check whether they match up with mine. @mfenniak We seem to need more and better tests, ideally some with a running Forgejo instance. Does that a better fit for https://code.forgejo.org/forgejo/end-to-end/ or should we introduce such tests here, but this time in a separate directory so that we can separate them from the rest of the test suite? We already have two such tests here (`internal/app/cmd/create-runner-file_test.go`).
Author
Contributor

@aahlenst

We run a modified runner that enables cloning of private actions (described in this issue). Because of our modifications, we encountered this bug much earlier than typical deployments would.

I think it can be reproduced with the following scenario (though I haven't tested it personally on vanilla):

  • Use a reusable workflow (uses: owner/repo/.forgejo/workflows/workflow.yml@ref) that lives in the same private repository
  • The runner will attempt to clone that private repository using the token
  • It should fail with fatal: could not read Username for 'https://...' because credential-store doesn't find the credentials in the malformed file

Even if the bug is rare in current vanilla deployments, it will inevitably be encountered as users adopt more complex workflow patterns.

@aahlenst We run a modified runner that enables cloning of private actions (described in this [issue](https://code.forgejo.org/forgejo/forgejo-actions-feature-requests/issues/3#issuecomment-71378)). Because of our modifications, we encountered this bug much earlier than typical deployments would. I think it can be reproduced with the following scenario (though I haven't tested it personally on vanilla): - Use a reusable workflow (uses: owner/repo/.forgejo/workflows/workflow.yml@ref) that lives in the same private repository - The runner will attempt to clone that private repository using the token - It should fail with fatal: could not read Username for 'https://...' because credential-store doesn't find the credentials in the malformed file Even if the bug is rare in current vanilla deployments, it will inevitably be encountered as users adopt more complex workflow patterns.

@aahlenst I created a pull request to show how to set a context where a private repository is used for checkout. Variations can be added to verify a scenario such as the one this pull request is expected to fix.

forgejo/end-to-end@!1409 (commit b9e49cdb77)

@aahlenst I created a [pull request to show how to set a context where a private repository]( https://code.forgejo.org/forgejo/end-to-end/pulls/1409) is used for checkout. Variations can be added to verify a scenario such as the one this pull request is expected to fix. https://code.forgejo.org/forgejo/end-to-end/pulls/1409/commits/b9e49cdb7712918b6f5e63bc63daf23918ca10d6
mfenniak approved these changes 2026-01-11 20:49:21 +00:00
Dismissed
Owner

@aahlenst wrote in #1268 (comment):

@mfenniak We seem to need more and better tests, ideally some with a running Forgejo instance. Does that a better fit for https://code.forgejo.org/forgejo/end-to-end/ or should we introduce such tests here, but this time in a separate directory so that we can separate them from the rest of the test suite? We already have two such tests here (internal/app/cmd/create-runner-file_test.go).

I think this seems like a better fit for the end-to-end test repo, where we have infrastructure set up to test all the supported Forgejo versions (or selectively exclude tests)... currently the CI here only uses the last Forgejo LTS and so we couldn't do the private access test without a different Forgejo. The downside is that the end-to-end repo is difficult to work in, in my opinion... opinionated about where tools are expected, documentation is limited, and entirely bash scripting. But I think it's conceptually ideal for integration testing these systems, and just needs technical improvements over time. 🙂

@aahlenst wrote in https://code.forgejo.org/forgejo/runner/pulls/1268#issuecomment-72506: > @mfenniak We seem to need more and better tests, ideally some with a running Forgejo instance. Does that a better fit for https://code.forgejo.org/forgejo/end-to-end/ or should we introduce such tests here, but this time in a separate directory so that we can separate them from the rest of the test suite? We already have two such tests here (`internal/app/cmd/create-runner-file_test.go`). I think this seems like a better fit for the end-to-end test repo, where we have infrastructure set up to test all the supported Forgejo versions (or selectively exclude tests)... currently the CI here only uses the last Forgejo LTS and so we couldn't do the private access test without a different Forgejo. The downside is that the end-to-end repo is difficult to work in, in my opinion... opinionated about where tools are expected, documentation is limited, and entirely bash scripting. But I think it's conceptually ideal for integration testing these systems, and just needs technical improvements over time. 🙂
Contributor

cascading-pr updated at actions/setup-forgejo#814

cascading-pr updated at https://code.forgejo.org/actions/setup-forgejo/pulls/814
Owner

The change here seems to make sense, but it's failing for me in manual testing.

Minus the customization that @syncstack mentions, the only in-tree support for cloning a private repository is when using #1249's capability to use a private action... and when I attempt to do that, either with a bare action (eg. uses: mfenniak/checkout@main) or with a fully qualified address (uses: https://.../mfenniak/basic-checkout@main), it works correctly on main. (mfenniak/basic-checkout is a private repository in the same org as the running workflow).

On the other hand, when I'm running this modification, I'm getting this error from a clone w/ uses: https://.../mfenniak/basic-checkout@main

  ☁️  git clone 'https://.../mfenniak/basic-checkout' # ref=main
⚙️ [runner]: unable to clone 'https://..../mfenniak/basic-checkout' to '/home/mfenniak/.cache/act/c7/dbd88dcbb4054cc2f546393f71a207bbd98c2ceb884288f762f3c9fcaf2df8': Cloning into bare repository '/home/mfenniak/.cache/act/c7/dbd88dcbb4054cc2f546393f71a207bbd98c2ceb884288f762f3c9fcaf2df8'...\nremote: User permission denied\nfatal: unable to access 'https://.../mfenniak/basic-checkout/': The requested URL returned error: 403: exit status 128
skipping post step for 'mfenniak/basic-checkout@main'; step was not executed

I've added some diagnostic output for the contents written to the credential file, and the git CLI, and they seem to be as-expected from the patch:

remoteURL = "https://x-access-token:cf26208a35ab20517c48557bd7d6e7c98e8232cf@.../mfenniak/basic-checkout"
gitArguments = []string{"-c", "credential.helper=store --file=/tmp/nix-shell-3530017-1441160932/forgejo-runner-git-token-611348731", "-c", "credential.useHttpPath=true", "clone", "--bare", "https://.../mfenniak/basic-checkout", "/home/mfenniak/.cache/act/c7/dbd88dcbb4054cc2f546393f71a207bbd98c2ceb884288f762f3c9fcaf2df8"}

(Note: I've removed ~/.cache/act between manual testing steps to ensure a clone is required)

The change here seems to make sense, but it's failing for me in manual testing. Minus the customization that @syncstack mentions, the only in-tree support for cloning a private repository is when using #1249's capability to use a private *action*... and when I attempt to do that, either with a bare action (eg. `uses: mfenniak/checkout@main`) or with a fully qualified address (`uses: https://.../mfenniak/basic-checkout@main`), it works correctly on `main`. (mfenniak/basic-checkout is a private repository in the same org as the running workflow). On the other hand, when I'm running this modification, I'm getting this error from a clone w/ `uses: https://.../mfenniak/basic-checkout@main` ``` ☁️ git clone 'https://.../mfenniak/basic-checkout' # ref=main ⚙️ [runner]: unable to clone 'https://..../mfenniak/basic-checkout' to '/home/mfenniak/.cache/act/c7/dbd88dcbb4054cc2f546393f71a207bbd98c2ceb884288f762f3c9fcaf2df8': Cloning into bare repository '/home/mfenniak/.cache/act/c7/dbd88dcbb4054cc2f546393f71a207bbd98c2ceb884288f762f3c9fcaf2df8'...\nremote: User permission denied\nfatal: unable to access 'https://.../mfenniak/basic-checkout/': The requested URL returned error: 403: exit status 128 skipping post step for 'mfenniak/basic-checkout@main'; step was not executed ``` I've added some diagnostic output for the contents written to the credential file, and the git CLI, and they seem to be as-expected from the patch: ``` remoteURL = "https://x-access-token:cf26208a35ab20517c48557bd7d6e7c98e8232cf@.../mfenniak/basic-checkout" gitArguments = []string{"-c", "credential.helper=store --file=/tmp/nix-shell-3530017-1441160932/forgejo-runner-git-token-611348731", "-c", "credential.useHttpPath=true", "clone", "--bare", "https://.../mfenniak/basic-checkout", "/home/mfenniak/.cache/act/c7/dbd88dcbb4054cc2f546393f71a207bbd98c2ceb884288f762f3c9fcaf2df8"} ``` (Note: I've removed `~/.cache/act` between manual testing steps to ensure a clone is required)
mfenniak dismissed mfenniak's review 2026-01-11 21:28:43 +00:00
Reason:

manual testing failures

Author
Contributor

I’ll bring up a test environment with a vanilla Forgejo setup and investigate this.

I’d appreciate it if @mfenniak could share the exact workflow file they’re using, and clarify whether the relevant repositories/actions are private or public (and in which org/owner), so I can reproduce the issue accurately.

I’ll bring up a test environment with a vanilla Forgejo setup and investigate this. I’d appreciate it if @mfenniak could share the exact workflow file they’re using, and clarify whether the relevant repositories/actions are private or public (and in which org/owner), so I can reproduce the issue accurately.
syncstack changed title from fix: private repository cloning with authentication token to WIP: fix: private repository cloning with authentication token 2026-01-11 21:44:35 +00:00
Owner

Absolutely. I have a repo mfenniak/test which is a private repo, and has this workflow in .forgejo/workflows/main.yml:

on:
  pull_request:

jobs:
  build-docker:
    runs-on: docker
    container: alpine:3.22

    steps:
      - run: apk add --no-cache git
      - uses: mfenniak/basic-checkout@main
      - run: find .

Forgejo is current dev branch (d9de2833), and has DEFAULT_ACTIONS_URL configured:

[actions]
DEFAULT_ACTIONS_URL = "https://example.com"

basic-checkout is a repo with the attached content, which is a private repository at mfenniak/basic-checkout. Local git CLI is git version 2.51.2.

Absolutely. I have a repo `mfenniak/test` which is a private repo, and has this workflow in `.forgejo/workflows/main.yml`: ``` on: pull_request: jobs: build-docker: runs-on: docker container: alpine:3.22 steps: - run: apk add --no-cache git - uses: mfenniak/basic-checkout@main - run: find . ``` Forgejo is current dev branch (d9de2833), and has DEFAULT_ACTIONS_URL configured: ``` [actions] DEFAULT_ACTIONS_URL = "https://example.com" ``` basic-checkout is a repo with the attached content, which is a private repository at `mfenniak/basic-checkout`. Local git CLI is `git version 2.51.2`.
Author
Contributor

@mfenniak Thanks for the detailed reproduction steps.


Wait a minute — you mentioned that both mfenniak/test and mfenniak/basic-checkout are private repositories.

As far as I know, this scenario (calling a private action/workflow from a different private repository) cannot work at all in vanilla Forgejo v13.0.4 + forgejo-runner v12.5.0.

The maximum that currently works in vanilla is a composite workflow within the same private repository calling itself.

So the described use case doesn't work for me as-is in vanilla builds.

@mfenniak Thanks for the detailed reproduction steps. --- Wait a minute — you mentioned that both `mfenniak/test` and `mfenniak/basic-checkout` are private repositories. As far as I know, this scenario (calling a private action/workflow from a different private repository) cannot work at all in vanilla Forgejo v13.0.4 + forgejo-runner v12.5.0. The maximum that currently works in vanilla is a composite workflow within the same private repository calling itself. So the described use case doesn't work for me as-is in vanilla builds.
Owner

Ah! Great insight. I've added some debugging code into my Forgejo instance to figure out why this is being permitted, and it appears Forgejo is receiving basic auth with my user's username & password. My guess is that running git subprocesses, and running the runner as my development user, it's accessing my session's credential store to login. I think that credential.useHttpPath interrupts that because whatever creds I have stored for a different repo aren't accessible for the different target action repo.

I'll run this through another set of manual tests on an environment where these factors won't have any influence, tomorrow, and verify that those guesses.

There's probably nothing wrong in this patch, but it would be nice if we could isolate the git commands from the development session. (and also, I noted, in some cases when the cred store fails it's accessing the runner's stdin to prompt). Unrelated to this work, though.

Ah! Great insight. I've added some debugging code into my Forgejo instance to figure out why this is being permitted, and it appears Forgejo is receiving basic auth with my user's username & password. My guess is that running `git` subprocesses, and running the runner as my development user, it's accessing my session's credential store to login. I think that `credential.useHttpPath` interrupts that because whatever creds I have stored for a different repo aren't accessible for the different target action repo. I'll run this through another set of manual tests on an environment where these factors won't have any influence, tomorrow, and verify that those guesses. There's probably nothing wrong in this patch, but it would be nice if we could isolate the git commands from the development session. (and also, I noted, in some cases when the cred store fails it's accessing the runner's stdin to prompt). Unrelated to this work, though.
syncstack changed title from WIP: fix: private repository cloning with authentication token to fix: private repository cloning with authentication token 2026-01-12 07:48:56 +00:00
Author
Contributor

By the way, you're right - if you run the runner on a machine where there are already some git credentials stored, the behavior can indeed be unpredictable.

Although, on the other hand, this is exactly what we were missing at the beginning of our experience with Forgejo, so it's not a bug but a feature ))

By the way, you're right - if you run the runner on a machine where there are already some git credentials stored, the behavior can indeed be unpredictable. Although, on the other hand, this is exactly what we were missing at the beginning of our experience with Forgejo, so it's not a bug but a feature ))
mfenniak approved these changes 2026-01-12 16:30:05 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
5 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
forgejo/runner!1268
No description provided.