diff --git a/bin/git-credential-hive.sh b/bin/git-credential-hive.sh index 5bd29cbf2..1c2b842d4 100644 --- a/bin/git-credential-hive.sh +++ b/bin/git-credential-hive.sh @@ -154,9 +154,16 @@ case "${1:-}" in if [ -n "$REQUEST_PROTOCOL" ] && [ "$REQUEST_PROTOCOL" != "https" ]; then exit 0 fi - printf '{"ts":"%s","agent":"%s","uid":%d,"op":"git-credential","host":"%s"}\n' \ + # The group wraps the append so a failed REDIRECTION is silenced too, the + # same shape gh-wrapper.sh uses (#4043): `>> f 2>/dev/null` only mutes the + # printf, and when the log cannot be opened by the agent UID — its + # directory is 0755 dev:node by design (#4044) and the file does not + # exist until the entrypoint pre-creates it — the shell's own + # "line N: .../token-access.jsonl: Permission denied" leaked into the + # agent's pane on every clone, fetch, and push. + { printf '{"ts":"%s","agent":"%s","uid":%d,"op":"git-credential","host":"%s"}\n' \ "$(date -u +%Y-%m-%dT%H:%M:%SZ)" "${AGENT:-unknown}" "$(id -u)" "${REQUESTED_HOST:-unknown}" \ - >> "$TOKEN_ACCESS_LOG" 2>/dev/null || true + >> "$TOKEN_ACCESS_LOG"; } 2>/dev/null || true # Echo back the SAME host git asked about (github.com, github.ibm.com, or # any other configured GitHub Enterprise host) rather than a hardcoded # "github.com" — see the file header. entrypoint.sh only registers this diff --git a/bin/test_git_credential_hive.sh b/bin/test_git_credential_hive.sh index 3bc995f7e..a75f213ce 100644 --- a/bin/test_git_credential_hive.sh +++ b/bin/test_git_credential_hive.sh @@ -105,6 +105,25 @@ else echo "PASS: push-capable mode has no read-only notice text" fi +# ── the audit-log append must never leak into the agent's transcript ── +# The helper appends one line to /var/run/hive-metrics/token-access.jsonl on +# every `get`. On a per-UID hive that directory is 0755 dev:node by design +# (#4044) and the file is not pre-created, so the append's REDIRECTION fails. +# `>> f 2>/dev/null` only mutes the printf — the shell reports a failed +# redirection on its own stderr — so every clone/fetch/push showed +# "git-credential-hive.sh: line N: .../token-access.jsonl: Permission denied" +# in the agent's pane. The harness has no /var/run/hive-metrics either, so the +# same redirection fails here and this assertion reproduces the leak exactly. +LEAK_OUT="$(run_helper CONTRIBUTOR 4 "$TOKEN_CACHE" "$GET_STDIN" -- get | tail -n +2)" +if [[ "$LEAK_OUT" == *"token-access.jsonl"* ]] || [[ "$LEAK_OUT" == *"Permission denied"* ]] || [[ "$LEAK_OUT" == *"No such file"* ]]; then + FAIL=$((FAIL + 1)) + echo "FAIL: audit-log append failure leaked into helper output" + echo " got: $LEAK_OUT" +else + PASS=$((PASS + 1)) + echo "PASS: audit-log append failure is silent (no token-access.jsonl noise)" +fi + # ── refusals that MUST stay fail-closed (audit H3) ── assert "missing per-agent token file refuses loudly (H3: no shared-cache fallback)" \ "$(run_helper ADVISORY 2 "${WORK}/no-such-token" "$GET_STDIN" -- get)" \ diff --git a/changelog.d/fixed-credential-helper-audit-log.md b/changelog.d/fixed-credential-helper-audit-log.md new file mode 100644 index 000000000..dd3133da3 --- /dev/null +++ b/changelog.d/fixed-credential-helper-audit-log.md @@ -0,0 +1 @@ +- Agents no longer see `git-credential-hive.sh: line N: /var/run/hive-metrics/token-access.jsonl: Permission denied` on every clone, fetch, and push. The credential helper appended to the token-access audit log with `>> f 2>/dev/null`, which only mutes the command, not a failed redirection — and the redirection always failed for per-UID agents because the metrics directory is deliberately not agent-writable and the log file was never created. The helper now wraps the append the way `gh-wrapper.sh` already does (#4043) so a failure is silent, and the entrypoint pre-creates the log as `dev:node` mode 0664 so the appends from both writers actually land and `GET /api/token-access` stops reporting an empty audit log on per-UID hives. diff --git a/src/deploy/entrypoint.sh b/src/deploy/entrypoint.sh index 38ce837f3..ab00665ab 100644 --- a/src/deploy/entrypoint.sh +++ b/src/deploy/entrypoint.sh @@ -708,6 +708,18 @@ if [ "$(id -u)" = "0" ]; then # group-writable mode would let any agent swap that file and spoof the # identity the gate validates against. Re-asserted on every boot. chmod 755 /var/run/hive-metrics/agent-tokens 2>/dev/null || true + # The token-access audit log (GET /api/token-access) is APPENDED by the + # per-UID agent processes — gh-wrapper.sh on every gh call and + # git-credential-hive.sh on every credential lookup — but the directory + # above is deliberately not agent-writable, so an agent can never create + # the file and every append failed silently: the audit endpoint on a + # per-UID hive stayed empty forever. Pre-create it here, owned by dev with + # group "node" (every agent UID) writable, so the appends land. The + # directory itself stays 0755: only this one file opens up, the + # bot-identity file the gh-wrapper author gate trusts is untouched. + touch /var/run/hive-metrics/token-access.jsonl 2>/dev/null || true + chown dev:node /var/run/hive-metrics/token-access.jsonl 2>/dev/null || true + chmod 664 /var/run/hive-metrics/token-access.jsonl 2>/dev/null || true # Fix permissions on bind-mounted secret files (host may own them as # a different UID with mode 600, making them unreadable by dev/UID 1001)