Skip to content

Store the GitLab token instead of requiring an exported env var #6

Description

@chris-peterson

Context

git-fi's branch listing has two modes. Without a GitLab token it prints branch names. With one it prints each branch's pipeline status, author, and date, plus clickable links into GitLab, and it reports the fi pipeline a merge just triggered. Today the only way to enable that is exporting GITLAB_ACCESS_TOKEN in your shell.

An environment variable rewards the expedient choice over the secure one. The cheapest way to satisfy it is reusing a token that is already exported for something else, which is typically full api scope, when git-fi only needs read_api. Nothing in the current path asks for a narrower token or tells you when you have supplied a broad one.

The consumer is any developer who wants pipeline status in git fi, which is the whole audience for the feature.

Proposed approach

Add stored credentials, and keep the environment variable working.

Resolution order: stored config first, then GITLAB_ACCESS_TOKEN, except in CI where the variable wins. The principle is to prefer the deliberate credential over the ambient one. On a developer machine that is the stored token, which should win over whatever happens to be exported. In CI it is the job's variable, so when CI is set that comes first, and a config file baked into a container image cannot silently shadow it. Either way an existing export keeps working, so this lands as an addition rather than a migration.

Surface: an --auth[=login|status|logout] action flag, not an auth subcommand.

Why not git fi auth login, the shape gh and glab use? Positional arguments to git fi are branch names, and a lone positional becomes the list filter, so auth would have to become a reserved word and git fi auth would stop filtering for auth-related branches. The existing help and install-completions subcommands get away with it because nobody filters on those words. Carrying the verb as a flag value keeps every positional meaning "branch".

Bare --auth reports status, --auth=login prompts and stores, --auth=logout removes. This would be the first long-only flag in a table where everything has a short form, which suits a command run once per machine.

login inspects the token it was given. After reading the token it calls GET /personal_access_tokens/self, which returns the token's scopes and expires_at, and warns when the token carries more than read_api. This is what addresses the problem rather than restating it in documentation: the tool tells you when you have just handed it a broadly-scoped token.

The prompt makes the narrow token the low-effort path by linking the prefilled token form, which GitLab populates from query parameters:

https://<host>/-/user_settings/personal_access_tokens?name=git-fi&scopes=read_api

Requirements land in SPEC.md as AUTH-* ids before any implementation, since language ports are being explored and each one needs the same resolution order, file permissions, and redaction rules.

Acceptance criteria

  • SPEC.md carries AUTH-* requirements for resolution order, storage location and permissions, the stdin-only rule, scope validation, and status redaction
  • A stored token takes precedence over GITLAB_ACCESS_TOKEN, and an existing export keeps working when nothing is stored
  • When CI is set, GITLAB_ACCESS_TOKEN takes precedence over any stored token
  • Bare --auth names which source the live token came from, and says when a stored token is shadowing an exported one
  • --auth=login reads the token from stdin only, never from an argv value, since argv is visible in ps and lands in shell history
  • The token is stored at $XDG_CONFIG_HOME/git-fi/config.json with the directory at 0700 and the file at 0600
  • git-fi refuses to read that file when it is group- or world-readable, and says why
  • config.json carries a schemaVersion
  • --auth=login validates the token against GET /personal_access_tokens/self and warns when its scopes exceed read_api
  • The login prompt links the prefilled token form for the detected host
  • Bare --auth prints host, scopes, expiry, and the last 4 characters of the token, and never the value
  • --auth=logout removes the stored token
  • Completion offers login, status, and logout as values for --auth

Considerations

The permission check is the point. A 0600 file is only an improvement over an export because fewer processes can read it. Without the refuse-on-loose-permissions check it is just a differently-shaped plaintext secret, so that check is load-bearing rather than a nicety.

An OS keyring is out of scope. macOS security, libsecret, and Windows DPAPI are three platform paths and a real burden for every port to reimplement, which is too much for a read-only token. The schemaVersion field exists so a later move to a keyring can discard the plaintext generation rather than leave it behind.

Worth weighing: store nothing at all. When glab is installed, git-fi could shell out to glab api and hold no credential of its own, which is the only option where git-fi never touches a secret. The cost is depending on another tool being present for a feature that currently works standalone. Deciding this before writing the AUTH-* requirements would avoid specifying storage twice.

No existing leak path to fix first. The token already travels as a PRIVATE-TOKEN header (src/gitlab.ts:56) rather than in a URL, and --debug prints only git commands, so this work adds a storage mechanism without having to clean up the transport.

Metadata

Metadata

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions