The jumphost clone could not git push: every attempt died with could not read Username for 'https://git.baldurkeep.com'. The branch sat 12 commits ahead of origin for two days as a result -- a real data-loss exposure, since all of the D-112 / OPNsense edge work existed only on this box. Fixed by giving git a stored credential. Operator ruled the posture; recorded as SEC-005.
git config --global credential.helper store # -> ~/.git-credentials, mode 600
The token itself was written by the OPERATOR via a hidden prompt (getpass + /dev/tty), URL-encoded, file created 0600 from os.open so it is never briefly world-readable. The token was never read into agent context -- verification is by using the credential (push succeeds / file survives), never by printing it. Per the CLAUDE.md secrets rule.
Result: f5fd4f5..425d434 pushed; git ls-remote confirms origin HEAD == local HEAD.
1. The git username is NOT the login you type into the web UI. The operator signs in to GitBucket with jesse.austin@neumatrix.com. That address is rejected for git transport. Measured against the real endpoints:
| username | /api/v3/user |
info/refs?service=git-receive-pack |
verdict |
|---|---|---|---|
jesse.austin@neumatrix.com |
401 | 401 | rejected |
jesse.austin |
401 | 200 | CAN PUSH |
JANeumatrix (the git user.name) |
401 | 401 | rejected |
Note the whole API column is 401: GitBucket does not accept PAT basic-auth on /api/v3/, but does accept it for git transport. So an API probe is the WRONG way to validate this credential -- it produces a false negative. Validate against info/refs?service=git-receive-pack, which is what push actually uses.
2. credential.helper store DELETES the credential when the server rejects it. git calls credential reject on a 401, and the store helper erases the matching line. The first (bad-username) push therefore emptied ~/.git-credentials to 0 bytes, and the follow-up diagnostics were all authenticating with an empty string -- which looked like a corrupt/unparseable file and sent the investigation the wrong way for a beat. If the file mysteriously empties, the credential was rejected; it did not fail to write.
Consequence for tooling: validate before writing. The helper script now confirms push rights against GitBucket and only then persists the token, so a bad entry cannot silently wipe the store.
A long-lived, account-wide PAT now sits in plaintext on the jumphost, readable by anything running as jessea123. This is the accepted cost of unattended push, ruled by the operator. Mitigations in place: dedicated token (not a reused personal one), mode 600, and a recorded revoke path. GitBucket has no per-repo token scoping, so least-privilege is not achievable here -- the control is rotation, not scope.
Revoke/rotate: GitBucket -> Account Settings -> Applications. Do it at v1 close, or immediately if the jumphost is shared, rebuilt, or its disk leaves the premises.
git config --global --unset credential.helper rm -f ~/.git-credentials
Then revoke the token in GitBucket. Pushes go back to requiring interactive auth.