Tensionix ENDERU
GitHub28 RepositoriesDaily Tech3 Abonnentenaudion.devdas ProduktregalRSSVeröffentlichungen
← Alle NotizenProjects

GIT_AUTH_STRATEGY — GitHub/GitLab/Forgejo login policy

Decision

Audion Hub Manager must not become a password/token vault.

The application is portable, but Git credentials are not stored inside the portable project. Authentication is delegated to the normal Git ecosystem:

Hub Manager executes real git commands and displays them in the terminal dock. If the user is already authenticated through Git, VS Code, Git Credential Manager, SSH agent, GitKraken, GitHub CLI or GitLab CLI, push/pull/fetch should work without Hub Manager knowing any secret.

Current UI policy:

Remote pane. The Quick pane keeps local/frequent repository operations.

intent.

Check Auth, paired SSH probes for GitHub/GitLab, paired gh auth login / glab auth login, Windows Credential Manager, VS Code and GitKraken folder.

Remote mechanics

A Git remote is only a named URL. Typical examples:

git remote add github git@github.com:audion/Audion_Hub.git
git remote add gitlab git@gitlab.com:audion/Audion_Hub.git
git remote add local_nas file:///Z:/git-mirrors/Audion_Hub.git

Push/pull/fetch authenticate according to the URL type:

SSH URL    -> SSH key / ssh-agent
HTTPS URL  -> Git Credential Manager / token / CLI OAuth helper
file://    -> local filesystem permissions

Hub Manager can build remote URLs from platform/login/repository fields and save enabled records to config/remotes.json. Field history lives in config/remote_field_cache.json and is applied only by an explicit use button, never by automatic overlay into the typed field.

For ordinary publishing, push with annotated checkpoint tags:

git push --follow-tags origin main

push all remotes should enumerate Git remote names in Python and run git push --follow-tags <remote> <branch> sequentially. Do not use a PowerShell-only pipeline for GUI actions.

Use SSH for GitHub/GitLab remotes:

git@github.com:OWNER/REPO.git
git@gitlab.com:OWNER/REPO.git

Reasons:

For a single-button GitHub+GitLab publish path, configure origin with multiple push URLs while keeping pull/fetch anchored to one canonical fetch URL. Pulling from multiple platforms at once is not a normal sync operation; divergent platform histories should be resolved deliberately.

HTTPS fallback

HTTPS is acceptable, especially behind restrictive networks, but Hub Manager must still avoid storing tokens.

For GitHub, use Git Credential Manager or GitHub CLI. For GitLab, use a credential helper or a token stored outside this project.

Do not put credentials in config/*.json and do not embed tokens in remote URLs.

Bad:

https://username:TOKEN@gitlab.com/user/repo.git

Good:

https://gitlab.com/user/repo.git

A credential-bearing URL is worse than it looks, and not only because it may be pasted into a config or a chat. Cloning with one makes Git write the token, in the clear, into .git/config of the working copy — where it then survives every backup, mirror and archive of that folder. Observed directly while testing this feature. Going through the credential helper avoids that entirely: .git/config keeps a clean URL and the secret stays in the OS store. It is the same family of mistake as putting a secret in a URL query parameter.

Credentials belong to the OS credential store, Git Credential Manager, SSH agent, GitHub CLI, GitLab CLI, or another external credential manager.

Hub Manager Auth Doctor

The app should provide a non-destructive Auth Doctor panel:

Git installed?
Git config user.name/user.email?
Remotes configured?
SSH to GitHub works?
SSH to GitLab works?
SSH to each configured Forgejo host and port works?
gh installed / authenticated?
glab installed / authenticated?
git ls-remote works for selected remotes?
Which credential helpers are configured?
For each Forgejo host: server reachable, token stored, token still valid?

Auth Doctor should use non-interactive probes by default. For background probes use fail-fast settings so the GUI does not hang waiting for a password prompt.

Example policy:

background checks: non-interactive, timeout, no credential prompts
user-triggered push/pull: visible command, clear progress, cancellable
setup/login: open external terminal or external app

fetch --all --prune is safe as a frequent remote-status command: it updates remote tracking refs and prunes deleted remote refs without modifying working tree files. pull --ff-only is the deliberate branch-advance operation.

Forgejo and Gitea instances

Forgejo and Gitea are self-hosted, so the server address is part of the configuration rather than a fixed constant. config/forgejo_hosts.json lists the instances: id, title, URL, SSH user and port, preferred URL type and a cached account login. It holds no secrets and is safe to publish.

The account contract is the ordinary one every Forgejo client uses:

personal access token  -> Settings -> Applications -> Generate New Token
API authentication     -> Authorization: token <TOKEN>
token storage          -> the Git credential helper the user already has
git push / git pull    -> SSH key, or the same token over HTTPS

Token scopes

Verified empirically against Forgejo 15.0.6, one isolated token per operation, because the documentation does not spell this out:

OperationScope
GET /api/v1/versionnone, no token needed
GET /api/v1/user — who am Iread:user
GET /api/v1/user/repos — listread:user
POST /api/v1/user/repos — create a repositorywrite:user
git clone / git push over HTTPSa repository scope

Two things here contradict the obvious guess:

/api/v1/user/*, so it needs write:user. A token holding write:repository gets 403 ... required scope(s): [write:user].

read:user,write:user still fails at git clone with remote: Forbidden, so covering the API says nothing about covering git.

Forgejo also collapses scopes on save: a token created with read:user,write:user,write:repository is stored as write:repository,write:user, because the write scope implies the read one. There is no need to list both.

Because of that, a 403 is treated separately from a 401 everywhere in the app: a 401 means the token is wrong, expired or revoked, while a 403 means the token is genuine but was issued without the scope the route needs. Forgejo names the missing scope in the response body, and parse_missing_scopes lifts it out so the UI can show it instead of a bare status code.

Why a token and not "Sign in with Forgejo"

Forgejo does implement OAuth2 / OpenID Connect, and an Authorization Code + PKCE flow would work. It was considered and deliberately not chosen:

implemented and that a third-party application obtaining a token this way gets administrative rights over the account. A manually created personal access token, by contrast, is issued with exactly the scopes the user picks — read:repository alone is enough to list repositories.

would need a dedicated credential helper feeding it a fresh token on every operation. A personal access token placed in the credential store is picked up by plain git with no help from Hub Manager at all.

A token needs no server-side preparation; an OAuth flow would require every user to register an OAuth2 application on their instance first.

A browser sign-in can still be added later as a second, optional path. It would not replace the token contract, only sit next to it.

What Hub Manager does with the token

The token is typed into the Remote pane, verified against /api/v1/user, and only then handed to git credential approve. Hub Manager keeps no copy: the field is cleared, nothing is written to config/*.json, and later reads go back through git credential fill. forget token runs git credential reject; revoking the token on the server stays a deliberate action by the user.

Background probes disable prompting (GIT_TERMINAL_PROMPT=0), so a missing credential is reported as "nothing stored" instead of blocking the GUI.

Server error text is redacted before it is shown. Forgejo answers an unknown token with access token does not exist [sha: <TOKEN>], quoting the value back; a revoked or expired token would otherwise appear verbatim in the terminal dock, and from there in screenshots, copied output and bug reports. redact_token strips the echoed value and any literal occurrence of the token in use. The dock itself keeps its lines in memory only and does not write them to logs/.

Untrusted config input

config/forgejo_hosts.json and config/remotes.json are ordinary project files and can arrive with a cloned repository, so their contents are treated as untrusted input rather than as settings the user personally typed:

hostname   must match a DNS name or an IP literal — `urlparse` keeps `;`,
           `&&` and `$(...)`, and the value reaches `ssh -T <user>@<host>`,
           which runs through a shell
ssh_user   [A-Za-z0-9._-] only
ssh_port   1..65535
URLs       no embedded credentials, no `ext::` or `fd::` (both make Git run a
           command), no leading `-` (Git would read it as an option), no
           control characters

Records that fail validation are dropped when the config is loaded, and the Remote pane refuses to save such a value in the first place.

Remote URLs for self-hosted instances

port 22        -> git@host:owner/repo.git
other SSH port -> ssh://git@host:PORT/owner/repo.git
HTTPS          -> https://host/owner/repo.git

HTTPS URLs never carry embedded credentials; the stored token supplies them.

External tools

VS Code and GitKraken are useful, but they should not become hard dependencies.

Hub Manager should be able to:

The user can still solve complex auth/conflict problems in VS Code or GitKraken, then return to Hub Manager.

Forbidden by design

Hub Manager must not:

Optional advanced mode: portable SSH

A portable SSH profile can be added later, but must be opt-in and heavily warned.

Possible config shape:

{
  "strategy": "portable_ssh",
  "private_key_path": "secrets/ssh/id_ed25519",
  "require_passphrase": true,
  "enabled": false
}

This is risky because a portable app archive can accidentally leak the key. Default policy remains external SSH agent / OS credential store.

Official references

Bearbeitet 28.08.2026