From cf3bb7c3c11d616bc7042bfb9c9b1abdfbd8d641 Mon Sep 17 00:00:00 2001 From: Gregory Hunt Date: Sat, 5 Sep 2026 11:27:05 -0400 Subject: [PATCH] =?UTF-8?q?=F0=9F=90=9B=20fix:=20silence=20the=20credentia?= =?UTF-8?q?l=20helper's=20audit-log=20append=20and=20pre-create=20the=20lo?= =?UTF-8?q?g=20so=20it=20records?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Every agent clone, fetch, and push printed git-credential-hive.sh: line 157: /var/run/hive-metrics/token-access.jsonl: Permission denied into the agent's pane. The helper appends an audit line with `>> "$LOG" 2>/dev/null || true`, but `2>/dev/null` only mutes the printf: when the redirection itself fails the shell reports it on its own stderr before the command runs. gh-wrapper.sh had this exact bug and fixed it by wrapping the append in a group (#4043); the credential helper never got the same treatment. The redirection always failed on a per-UID hive: /var/run/hive-metrics is 0755 dev:node by design (#4044, protecting the bot-identity file under agent-tokens/) and nothing ever created token-access.jsonl, so no agent UID could open it. That also means the audit log both writers feed — and GET /api/token-access reads — has been empty on every per-UID hive. - bin/git-credential-hive.sh: wrap the append in `{ ...; } 2>/dev/null` so a failed redirection is silent, matching gh-wrapper.sh. - src/deploy/entrypoint.sh: pre-create the log as dev:node 0664 next to the existing hive-metrics setup so appends from both writers land. The directory stays 0755; only this one file is group-writable. - bin/test_git_credential_hive.sh: assert a `get` never emits token-access.jsonl / Permission denied noise. The harness has no /var/run/hive-metrics either, so it reproduces the leak exactly; the assertion fails on the old helper and passes on the new one. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01CoYvUkv8Gp4AHGWLo97nqb Signed-off-by: Gregory Hunt --- bin/git-credential-hive.sh | 11 +++++++++-- bin/test_git_credential_hive.sh | 19 +++++++++++++++++++ .../fixed-credential-helper-audit-log.md | 1 + src/deploy/entrypoint.sh | 12 ++++++++++++ 4 files changed, 41 insertions(+), 2 deletions(-) create mode 100644 changelog.d/fixed-credential-helper-audit-log.md 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)