feat: use terminal Git credentials with a one-year memory cache
This commit is contained in:
@@ -20,7 +20,7 @@ Flat, explicit NixOS modules with locked inputs. Required setup belongs here—n
|
||||
| `neovim.nix`, `neovim-test.lua` | Unmodified upstream editor deployment and opt-in native runtime audit |
|
||||
| `updates.nix`, `update-system.sh`, `update-test.py` | Shared dev-owned checkouts, daily boot-staged updates and failure/concurrency regression tests |
|
||||
| `switch-system.sh`, `switch-test.py` | Same installed manual apply/preview command on both hosts, with host identity supplied by Nix |
|
||||
| `physical-test.nix`, `tools-test.nix` | Physical/AWS safety, shared-policy assertions and bounded offline tool/help smoke tests |
|
||||
| `physical-test.nix`, `tools-test.nix`, `git-credentials-test.nix` | Physical/AWS safety, shared-policy assertions, offline tool/help smoke tests and disposable Git credential-cache checks |
|
||||
| `desktop-test.nix`, `desktop-test.py`, `audit-desktop.sh` | Disposable graphical/PAM/audio/scaling audit |
|
||||
| `workstation.nix`, `nvidia.nix` | Shared local hardware/SDDM integration for laptop and VM; separate opt-in NVIDIA support |
|
||||
| [DESKTOP.md](DESKTOP.md) | Live audit, wallpaper provenance, JaKooLit comparison and explicit feature-completion plan |
|
||||
@@ -91,6 +91,28 @@ See [DESKTOP.md](DESKTOP.md) for the screenshot-led audit, functional coverage,
|
||||
|
||||
These are system-owned executables from Nix, not unmanaged `npm -g`, `pip install --user` or `cargo install` bootstraps. Project dependencies may still be downloaded by their ordinary package managers. `nix develop` / `.envrc` remain appropriate for project-specific versions; this is not a promise that every language project uses the same global toolchain.
|
||||
|
||||
### Git credentials: terminal, not a GUI
|
||||
|
||||
Git HTTPS authentication uses terminal username/token prompts and Git's native
|
||||
**in-memory cache with a 365-day timeout** (`31536000` seconds). Inherited helpers
|
||||
are reset, and Git/SSH graphical askpass fallback is disabled for normal terminal
|
||||
Git invocations. No credential-manager GUI or plaintext `credential-store` is used.
|
||||
Credentials are scoped to the repository path as well as the host.
|
||||
|
||||
This is cache retention, **not a new token expiry**: rebooting, stopping the cache
|
||||
daemon or rejecting a credential clears it, and the provider can expire/revoke a
|
||||
token sooner. A successful re-approval refreshes its cache timeout. Choose a
|
||||
one-year token expiry at your Git provider if it supports it; no real credentials
|
||||
or provider settings are changed by this configuration. SSH keys/agents and
|
||||
KeePassXC's storage for other applications remain unchanged. IDEs or repositories
|
||||
that explicitly override Git helpers/askpass can override these user defaults.
|
||||
|
||||
To forget all cached Git HTTPS credentials immediately:
|
||||
|
||||
```sh
|
||||
git credential-cache exit
|
||||
```
|
||||
|
||||
### Per-project DeepSeek Harness
|
||||
|
||||
[AGENT-VM.md](AGENT-VM.md) documents the project template and `nix run .#agent`.
|
||||
|
||||
@@ -63,6 +63,10 @@
|
||||
in
|
||||
{
|
||||
desktop = import ./desktop-test.nix { inherit inputs pkgs; };
|
||||
git-credentials = import ./git-credentials-test.nix {
|
||||
inherit pkgs;
|
||||
config = inputs.self.nixosConfigurations.nixos.config;
|
||||
};
|
||||
tools = import ./tools-test.nix {
|
||||
inherit pkgs;
|
||||
config = inputs.self.nixosConfigurations.nixos.config;
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
# Offline protocol checks using dummy credentials in a disposable HOME only.
|
||||
{ config, pkgs }:
|
||||
let
|
||||
hm = config.home-manager.users.dev;
|
||||
in
|
||||
assert
|
||||
hm.programs.git.settings.credential.helper == [
|
||||
""
|
||||
"cache --timeout=31536000"
|
||||
];
|
||||
assert hm.programs.git.settings.credential.useHttpPath;
|
||||
assert hm.programs.git.settings.core.askPass == "";
|
||||
assert hm.home.sessionVariables.GIT_ASKPASS == "";
|
||||
assert hm.home.sessionVariables.GIT_TERMINAL_PROMPT == "1";
|
||||
pkgs.runCommand "git-terminal-credentials-check"
|
||||
{
|
||||
nativeBuildInputs = [
|
||||
config.programs.git.package
|
||||
pkgs.coreutils
|
||||
pkgs.gnugrep
|
||||
pkgs.expect
|
||||
];
|
||||
}
|
||||
''
|
||||
export HOME="$TMPDIR/home" XDG_CONFIG_HOME="$TMPDIR/home/.config" XDG_CACHE_HOME="$TMPDIR/cache"
|
||||
export GIT_CONFIG_NOSYSTEM=1 GIT_ASKPASS="" GIT_TERMINAL_PROMPT=0
|
||||
mkdir -p "$XDG_CONFIG_HOME/git"
|
||||
cp ${hm.xdg.configFile."git/config".source} "$XDG_CONFIG_HOME/git/config"
|
||||
test "$(git config --get core.askPass)" = ""
|
||||
git config --get-all credential.helper | grep -qx 'cache --timeout=31536000'
|
||||
trap 'git credential-cache exit' EXIT
|
||||
|
||||
printf 'protocol=https\nhost=git.example.invalid\npath=project.git\nusername=test\npassword=offline-test-token\n\n' |
|
||||
git credential approve
|
||||
printf 'protocol=https\nhost=git.example.invalid\npath=project.git\n\n' |
|
||||
git credential fill > "$TMPDIR/retrieved"
|
||||
grep -qx 'password=offline-test-token' "$TMPDIR/retrieved"
|
||||
test -S "$XDG_CACHE_HOME/git/credential/socket"
|
||||
test ! -e "$HOME/.git-credentials"
|
||||
|
||||
# Even with a GUI fallback in the environment, a cache miss must not invoke it.
|
||||
printf '#!${pkgs.runtimeShell}\ntouch "$TMPDIR/gui-was-used"\necho unwanted\n' > "$TMPDIR/gui-askpass"
|
||||
chmod +x "$TMPDIR/gui-askpass"
|
||||
export SSH_ASKPASS="$TMPDIR/gui-askpass"
|
||||
if printf 'protocol=https\nhost=git.example.invalid\npath=other.git\n\n' | git credential fill; then
|
||||
echo 'Credentials leaked across repository paths' >&2; exit 1
|
||||
fi
|
||||
test ! -e "$TMPDIR/gui-was-used"
|
||||
printf 'protocol=https\nhost=git.example.invalid\npath=project.git\n\n' | git credential reject
|
||||
if printf 'protocol=https\nhost=git.example.invalid\npath=project.git\n\n' | git credential fill; then
|
||||
echo 'Rejected credentials remained cached' >&2; exit 1
|
||||
fi
|
||||
# Exercise genuine /dev/tty entry too, without contacting a Git server.
|
||||
export GIT_TERMINAL_PROMPT=1
|
||||
expect <<'EXPECT'
|
||||
set timeout 10
|
||||
spawn -noecho git credential fill
|
||||
send -- "protocol=https\rhost=terminal.example.invalid\rpath=project.git\r\r"
|
||||
expect {
|
||||
-exact "Username for 'https://terminal.example.invalid/project.git': " { send -- "terminal-user\r" }
|
||||
timeout { exit 1 }
|
||||
eof { exit 1 }
|
||||
}
|
||||
expect {
|
||||
-exact "Password for 'https://terminal-user@terminal.example.invalid/project.git': " { send -- "offline-tty-token\r" }
|
||||
timeout { exit 1 }
|
||||
eof { exit 1 }
|
||||
}
|
||||
expect {
|
||||
-exact "password=offline-tty-token" { }
|
||||
timeout { exit 1 }
|
||||
eof { exit 1 }
|
||||
}
|
||||
expect eof
|
||||
lassign [wait] pid spawnid os_error status
|
||||
exit $status
|
||||
EXPECT
|
||||
test ! -e "$TMPDIR/gui-was-used"
|
||||
touch "$out"
|
||||
''
|
||||
@@ -235,7 +235,22 @@ in
|
||||
programs.git = {
|
||||
enable = true;
|
||||
package = null; # The system module supplies Git.
|
||||
settings.user.useConfigOnly = true;
|
||||
settings = {
|
||||
user.useConfigOnly = true;
|
||||
core.askPass = ""; # Use /dev/tty, never fall back to SSH's GUI askpass.
|
||||
credential = {
|
||||
# Reset inherited helpers; keep secrets in memory, never plaintext files.
|
||||
helper = [
|
||||
""
|
||||
"cache --timeout=31536000"
|
||||
]; # 365 days; cleared on reboot.
|
||||
useHttpPath = true; # Don't reuse a repository token for unrelated paths.
|
||||
};
|
||||
};
|
||||
};
|
||||
home.sessionVariables = {
|
||||
GIT_ASKPASS = "";
|
||||
GIT_TERMINAL_PROMPT = "1";
|
||||
};
|
||||
|
||||
programs.delta = {
|
||||
|
||||
Reference in New Issue
Block a user