Quick Commands

# edit + deploy
git status
git add -A
git commit -m "docs: update"
git push

# rebuild static blog output (local)
cd site
npm ci --no-audit --no-fund
npm run build

# VPS: pull only
# (on server)
git pull --ff-only

GitHub SSH Agent — Final Architecture & Runbook (from your chat dump)

Final model (verified): One host user is the Git identity. A single shared agent socket at **/run/gitshared/ssh-agent.sock** is used by other local users via the **gitshare** group and two wrappers. Keys live under the host user; no per‑user key sprawl.


1) Goals & Design

  • One identity, many users: Centralize GitHub auth on the host user.
  • Shared, not copied: Expose the agent socket (not private keys) to other users.
  • Strict SSH posture: Enforce IdentitiesOnly, IdentityAgent, and StrictHostKeyChecking.
  • Auditability: System‑wide known_hosts, predictable socket path, consistent wrappers.

Trust boundary

[Other local users] --SSH_AUTH_SOCK--> [/run/gitshared/ssh-agent.sock] --keys--> [Host user's keys]


2) Prerequisites

  • Linux host with ssh, git, sudo.
  • A host user (the Git identity) with a private key for GitHub.
  • A local group **gitshare** used to authorize socket access for other users.

3) Key Setup (host user)

# Create an Ed25519 key if you don’t have one
ssh-keygen -t ed25519 -C "your_email@example.com" -f ~/.ssh/id_ed25519
chmod 600 ~/.ssh/id_ed25519
chmod 644 ~/.ssh/id_ed25519.pub

# Add PUBLIC key to GitHub → Settings → SSH and GPG keys → New SSH key
cat ~/.ssh/id_ed25519.pub

# Trust GitHub host key system‑wide (non‑interactive jobs won’t stall)
ssh-keyscan github.com | sudo tee -a /etc/ssh/ssh_known_hosts > /dev/null


4) Shared Socket & Group

# Create group (once)
sudo groupadd -f gitshare

# Create shared run dir with restrictive perms
sudo install -d -m 0770 -o "$USER" -g gitshare /run/gitshared

# Add any collaborating users to the group (re‑login required)
sudo usermod -aG gitshare <otheruser>

Why: Permissions on /run/gitshared are what gate access; only group members can talk to the agent.


5) Start the Agent (host user)

# Bind the agent to a predictable, shared socket
export SSH_AUTH_SOCK=/run/gitshared/ssh-agent.sock
rm -f "$SSH_AUTH_SOCK"
eval "$(ssh-agent -a "$SSH_AUTH_SOCK")"

# Load your key into the agent
ssh-add ~/.ssh/id_ed25519
ssh-add -l   # sanity check

The agent runs in the current session. To persist across logouts, see §10 (optional systemd).


6) Zsh functions (final, enforced usage)

Final state uses zsh functions in your shell config, not standalone scripts. Both functions enforce the shared agent and strict SSH options.

6.1 run_as — run any command as another user via the shared agent

# Any command as another user with your SSH agent
run_as() {
  local user="$1"; shift
  sudo -u "$user" env SSH_AUTH_SOCK="$GIT_SHARED_SOCK" /bin/bash -lc "$*"
}

Usage:

export GIT_SHARED_SOCK=/run/gitshared/ssh-agent.sock
run_as build 'ssh -T git@github.com'

6.2 git_as — run git as another user in a target directory (with path rewriting)

# git_as <user> <dir> <git-args...>
git_as() {
  local user="$1"; local dir_in="$2"; shift 2

  # Home directories
  local host_home="$HOME"
  local target_home
  target_home="$(getent passwd "$user" | cut -d: -f6)"

  # Rewrite input path into target user's path
  local cd_cmd
  case "$dir_in" in
    "~") cd_cmd='cd ~' ;;
    "~/"*) cd_cmd="cd ~ && cd \"${dir_in#~/}\"" ;;
    "$host_home"/*) cd_cmd="cd \"$target_home\" && cd \"${dir_in#${host_home}/}\"" ;;
    /*) cd_cmd="cd \"$dir_in\"" ;;
    *) cd_cmd="cd ~ && cd \"$dir_in\"" ;;
  esac

  # One-line, zsh-friendly GIT_SSH_COMMAND
  local gsc="ssh -o IdentitiesOnly=yes -o IdentityAgent=$GIT_SHARED_SOCK -o StrictHostKeyChecking=yes -o UserKnownHostsFile=/etc/ssh/ssh_known_hosts"

  sudo -u "$user" env \
    SSH_AUTH_SOCK="$GIT_SHARED_SOCK" \
    GIT_SSH_COMMAND="$gsc" \
    /bin/bash -lc "$cd_cmd && git $*"
}

Usage:

export GIT_SHARED_SOCK=/run/gitshared/ssh-agent.sock
git_as build ~/repo 'fetch --all --prune'

Why functions? They ensure everyone uses the same agent/socket and strict SSH flags, while keeping configs in one place (~/.zshrc).


7) Verifications

# Host user → GitHub (uses your agent)
ssh -T git@github.com

# Other user via shared agent (simple)
run_as build 'ssh -T git@github.com'

# Git read-only test as other user inside a repo
git_as build ~/repo 'ls-remote git@github.com:org/repo.git | head'

If any step prompts for host key, re-append GitHub to /etc/ssh/ssh_known_hosts and retry.


8) Containers (optional, if needed)

If a container must use the agent, bind‑mount the socket dir and export the env.

docker run --rm -it \
  -v /run/gitshared:/run/gitshared \
  -e SSH_AUTH_SOCK=/run/gitshared/ssh-agent.sock \
  -e GIT_SSH_COMMAND="ssh -o IdentitiesOnly=yes -o IdentityAgent=/run/gitshared/ssh-agent.sock -o StrictHostKeyChecking=yes -o UserKnownHostsFile=/etc/ssh/ssh_known_hosts" \
  -v /etc/ssh/ssh_known_hosts:/etc/ssh/ssh_known_hosts:ro \
  alpine:3.20 sh

Keep keys out of images. Access is gated by the gitshare group on the host.


9) Troubleshooting

  • **Permission denied (publickey)**
    • ssh-add -l shows no keys → re‑add: ssh-add ~/.ssh/id_ed25519.
    • Socket perms: ls -ld /run/gitshared → group must be gitshare, mode 0770.
    • Ensure the other user is in gitshare and re‑logged in.
  • **Agent admitted failure to sign**
    • Key not loaded in current agent → ssh-add -D && ssh-add ~/.ssh/id_ed25519.
  • Stale/broken socket
    • rm -f /run/gitshared/ssh-agent.sock && eval "$(ssh-agent -a /run/gitshared/ssh-agent.sock)".
  • Wrong known_hosts
    • Re‑append: ssh-keyscan github.com | sudo tee -a /etc/ssh/ssh_known_hosts.
  • Accidental second agent
    • Check with pgrep -a ssh-agent; keep only the one bound to /run/gitshared/ssh-agent.sock.

10) Optional: Persistent Agent via systemd (single, clean unit)

Only if you want login‑independent uptime. Do not use the earlier gitshared-ssh-agent@.service or generic ssh-agent.service variants; disable/mask them first.

systemctl --user disable --now gitshared-ssh-agent@.service 2>/dev/null || true
systemctl --user disable --now ssh-agent.service 2>/dev/null || true

Create ~/.config/systemd/user/gitshare-ssh-agent.service:

[Unit]
Description=ssh-agent bound to /run/gitshared/ssh-agent.sock

[Service]
Type=simple
ExecStart=/usr/bin/env bash -lc 'install -d -m 0770 -o $USER -g gitshare /run/gitshared; rm -f /run/gitshared/ssh-agent.sock; exec ssh-agent -D -a /run/gitshared/ssh-agent.sock'

[Install]
WantedBy=default.target

Activate:

systemctl --user daemon-reload
systemctl --user enable --now gitshare-ssh-agent.service

You still load keys with ssh-add per login, unless you adopt a secure keychain.


11) Deprecations (explicit)

  • Not used anymore: gitshared-ssh-agent@.service, ssh-agent.service (older explorations). Keep them disabled/masked to avoid agent conflicts.

12) Runbook (Ops)

  1. Start/verify agent → ssh-add -l.
  2. Add collaborators to **gitshare** → re‑login.
  3. Ensure zsh functions (run_as, git_as) are present in ~/.zshrc and GIT_SHARED_SOCK=/run/gitshared/ssh-agent.sock is exported.
  4. Verify with ssh -T git@github.com (host & run_as <user>); use git_as for repo operations.
  5. Audit CI/local logs: confirm no PATs, no per‑user keys.
  6. Rotate keys on schedule or incident; re‑add to agent.

13) Onboarding a New Local User (5‑minute guide)

  1. sudo usermod -aG gitshare <user> → user re‑logs in.
  2. Verify access: run_as <user> 'ssh -T git@github.com' (should greet your GitHub account).
  3. Provide run_as / git_as snippets in a shared dotfiles repo or paste into the user’s shell rc.

14) Minimal Directory Snapshot

/run/gitshared/ssh-agent.sock        # shared agent socket (0770, group=gitshare)
/etc/ssh/ssh_known_hosts             # contains github.com
/home/<hostuser>/.ssh/id_ed25519     # host user key (private)
~/.zshrc                             # contains run_as & git_as; exports GIT_SHARED_SOCK
~/.config/systemd/user/gitshare-ssh-agent.service   # (optional) single persistent unit


Change log (this doc vs. earlier draft)

  • Du hast alles richtig gebaut – Agent lĂ€uft, Key geladen, Socket-Rechte passen – aber der letzte Fehler ist „communication with agent failed“ nur bei proxyuser. Das ist kein Rechteproblem mehr, sondern ein Sicherheits-Guard in modernen OpenSSH-Versionen: ssh-agent akzeptiert standardmĂ€ĂŸig keine Anfragen von anderen UIDs, selbst wenn der Socket gruppen-schreibbar ist. Ergebnis: andere User können verbinden, der Agent lehnt aber das Protokoll ab → genau diese Meldung.
  • Kurz: Cross-UID-Sharing eines ssh-agent ist (in aktuellen OpenSSH) nicht mehr supported.
    Workarounds:Replaced standalone wrapper scripts with zsh functions run_as and git_as (your latest state).
  • Kept final socket model: /run/gitshared/ssh-agent.sock + gitshare group.
  • Kept note to disable earlier units (gitshared-ssh-agent@.service, ssh-agent.service).

Du hast alles richtig gebaut – Agent lĂ€uft, Key geladen, Socket-Rechte passen – aber der letzte Fehler ist „communication with agent failed“ nur bei proxyuser. Das ist kein Rechteproblem mehr, sondern ein Sicherheits-Guard in modernen OpenSSH-Versionen: ssh-agent akzeptiert standardmĂ€ĂŸig keine Anfragen von anderen UIDs, selbst wenn der Socket gruppen-schreibbar ist. Ergebnis: andere User können verbinden, der Agent lehnt aber das Protokoll ab → genau diese Meldung.

Kurz: Cross-UID-Sharing eines ssh-agent ist (in aktuellen OpenSSH) nicht mehr supported.
Workarounds:

Got it. Below are additions to append to your existing doc (nothing removed). They explain why we changed, what changed, and how to run it with the new agent relay. You can paste these sections after your current content.


15) Architecture Update: Agent Relay (what & why)

What changed

We keep one host user as the GitHub identity and the real ssh-agent bound to /run/gitshared/ssh-agent.sock (the “REAL” socket). We add a tiny relay (via socat) that exposes a second socket: /run/gitshared/ssh-agent.relay (the “RELAY” socket).

Local collaborators use the RELAY socket. The relay connects requests to the REAL socket.

[other local users] --SSH_AUTH_SOCK--> /run/gitshared/ssh-agent.relay
                                           │ (relay, same UID as host user)
                                           └──> /run/gitshared/ssh-agent.sock --keys--> [host user keys]

Why we changed (root cause)

Modern OpenSSH ssh-agent enforces a cross-UID guard: it will refuse requests from a different UID, even if the UNIX socket has group write permissions. That’s why you saw:

  • error fetching identities: communication with agent failed (from other users)
  • followed by Permission denied (publickey) to GitHub.

So: group permissions alone are not enough. The relay runs under the host user’s UID, so the agent sees requests as same-UID and accepts them—no key sprawl, no container changes.

What stays the same

  • Single identity (host user), keys live under the host user.
  • Strict SSH posture (IdentitiesOnly, StrictHostKeyChecking, system-wide known_hosts).
  • gitshare group still gates who can touch the shared sockets/relay.

16) Operational Change (one-liner)

Wherever the doc previously told you to use:

export GIT_SHARED_SOCK=/run/gitshared/ssh-agent.sock

use this instead:

export GIT_SHARED_SOCK=/run/gitshared/ssh-agent.relay

Your run_as / git_as functions continue to work; they’ll now point at the RELAY.


17) Implementation: Start/Verify (manual quick path)

As the host user (the Git identity):

# Real agent (only once per boot/session)
install -d -m 2770 -o "$USER" -g gitshare /run/gitshared
export SSH_AUTH_SOCK=/run/gitshared/ssh-agent.sock
rm -f "$SSH_AUTH_SOCK"
eval "$(ssh-agent -a "$SSH_AUTH_SOCK")"
ssh-add -D
ssh-add ~/.ssh/id_ed25519
ssh-add -l

# Relay (tiny proxy) → same user
sudo apt-get update -y && sudo apt-get install -y socat
rm -f /run/gitshared/ssh-agent.relay
socat UNIX-LISTEN:/run/gitshared/ssh-agent.relay,fork,mode=0660 \
      UNIX-CONNECT:/run/gitshared/ssh-agent.sock >/dev/null 2>&1 &
chgrp gitshare /run/gitshared/ssh-agent.relay
ls -l /run/gitshared/ssh-agent.sock /run/gitshared/ssh-agent.relay

As another local user (e.g., **proxyuser**):

export SSH_AUTH_SOCK=/run/gitshared/ssh-agent.relay
ssh-add -l                # should list the host user’s ED25519 key
ssh -T git@github.com     # “Hi <your_account>!”

18) Systemd (user) Unit for the Relay (robust & persistent)

Create ~/.config/systemd/user/gitshare-agent-relay.service under the host user:

[Unit]
Description=ssh-agent relay -> /run/gitshared/ssh-agent.relay
ConditionPathExists=/run/gitshared/ssh-agent.sock

[Service]
Type=simple
# Wait for REAL socket, then relay
ExecStart=/usr/bin/env bash -lc '\
  REAL=/run/gitshared/ssh-agent.sock; RELAY=/run/gitshared/ssh-agent.relay; \
  umask 007; \
  while [ ! -S "$REAL" ]; do sleep 0.5; done; \
  rm -f "$RELAY"; \
  exec socat UNIX-LISTEN:"$RELAY",fork,mode=0660 UNIX-CONNECT:"$REAL" \
'
ExecStartPost=/bin/chgrp gitshare /run/gitshared/ssh-agent.relay
Restart=always
RestartSec=1

[Install]
WantedBy=default.target

Activate:

systemctl --user daemon-reload
systemctl --user enable --now gitshare-agent-relay.service
systemctl --user status gitshare-agent-relay.service --no-pager

The REAL agent itself can stay managed by your existing gitshare-ssh-agent.service (§10 of your doc).


19) Wrappers (minor update)

Keep your functions exactly as before but export the relay path:

export GIT_SHARED_SOCK=/run/gitshared/ssh-agent.relay

run_as() {
  local user="$1"; shift
  sudo -u "$user" env SSH_AUTH_SOCK="$GIT_SHARED_SOCK" /bin/bash -lc "$*"
}

git_as() {
  local user="$1"; local dir_in="$2"; shift 2
  local gsc="ssh -o IdentitiesOnly=yes -o IdentityAgent=$GIT_SHARED_SOCK -o StrictHostKeyChecking=yes -o UserKnownHostsFile=/etc/ssh/ssh_known_hosts"
  sudo -u "$user" env SSH_AUTH_SOCK="$GIT_SHARED_SOCK" GIT_SSH_COMMAND="$gsc" /bin/bash -lc "cd \"$dir_in\" && git $*"
}

20) Security Model (unchanged principles, clarified)

  • Keys never leave the host user. The relay only forwards the agent protocol; it cannot read private keys.
  • Access remains gated by UNIX permissions and the gitshare group on /run/gitshared.
  • Keep StrictHostKeyChecking=yes and a system-wide /etc/ssh/ssh_known_hosts with github.com.
  • Treat membership in gitshare as sensitive—members can sign SSH handshakes via the agent.

21) Containers (if needed)

Bind-mount the relay socket (not the real one):

docker run --rm -it \
  -v /run/gitshared:/run/gitshared \
  -e SSH_AUTH_SOCK=/run/gitshared/ssh-agent.relay \
  -e GIT_SSH_COMMAND="ssh -o IdentitiesOnly=yes -o IdentityAgent=/run/gitshared/ssh-agent.relay -o StrictHostKeyChecking=yes -o UserKnownHostsFile=/etc/ssh/ssh_known_hosts" \
  -v /etc/ssh/ssh_known_hosts:/etc/ssh/ssh_known_hosts:ro \
  alpine:3.20 sh

22) Troubleshooting (relay-aware)

  • **ssh-add -l** (other user) → “communication with agent failed” ➀ Ensure you’re pointing at the relay: echo $SSH_AUTH_SOCK → /run/gitshared/ssh-agent.relay ➀ systemctl --user status gitshare-agent-relay.service (host user) must be active ➀ REAL socket exists: ls -l /run/gitshared/ssh-agent.sock ➀ Relay socket perms: srw-rw---- : gitshare
  • Relay restarts repeatedly ➀ REAL socket not ready—use the systemd unit above (it waits), or start the real agent first.
  • **Permission denied (publickey)** still ➀ On host user: SSH_AUTH_SOCK=/run/gitshared/ssh-agent.sock ssh-add -l must list the key ➀ Ensure github.com is in /etc/ssh/ssh_known_hosts ➀ Make sure Git actually uses SSH (not HTTPS remote).
  • File permission errors when pushing as a different user ➀ Grant group write to the repo tree: chgrp -R gitshare 
; chmod -R g+rwX 
; find 
 -type d -exec chmod g+s {} +

23) FAQ

Why not just share the REAL socket with group write? Because modern ssh-agent rejects requests from different UIDs even if the socket is writable by a group. The relay runs under the host user’s UID, so the agent accepts.

Is the relay secure? It doesn’t expose private keys; it only forwards agent RPC. Security hinges on UNIX ownership & gitshare membership—keep that group small.

Performance impact? Negligible—local UNIX domain socket hop.

Why not deploy keys or PATs? Relay preserves a single identity and avoids token/key sprawl while keeping strict host-side control.


24) Change Log (addendum)

  • Switched to agent relay at /run/gitshared/ssh-agent.relay to overcome ssh-agent’s cross-UID restriction.
  • No content removed from prior model; REAL agent remains at /run/gitshared/ssh-agent.sock.
  • Added systemd user unit gitshare-agent-relay.service that waits for the REAL socket.
  • Updated guidance to point wrappers and containers at the RELAY socket.

Awesome—here’s an extra section to append (nothing removed), spelling out exactly how to use the setup on the host and how collaborators like **proxyuser** use it day-to-day.


25) Practical Usage — Host vs. Other Local Users

This section gives copy-paste-ready workflows for both the host user (the Git identity) and other local users (e.g., proxyuser). It assumes the REAL agent is at /run/gitshared/ssh-agent.sock and the RELAY is at /run/gitshared/ssh-agent.relay (see §§15–18).

25.1 Host User Workflow (Git identity owner)

You can point at either socket. For consistency with everyone else, we recommend using the RELAY everywhere.

One-time (per boot / after login)

# Ensure the agent (REAL) and relay are running per §§10 & 18.
systemctl --user status gitshare-ssh-agent.service
systemctl --user status gitshare-agent-relay.service

Per-shell convenience

Add to your ~/.zshrc (host user):

export GIT_SHARED_SOCK=/run/gitshared/ssh-agent.relay
export SSH_AUTH_SOCK=$GIT_SHARED_SOCK

Verify identity

ssh-add -l                              # should show your ED25519 key
ssh -T git@github.com                   # should greet your account

Typical git tasks (host user)

# Ensure SSH remote (not HTTPS)
git remote -v
git remote set-url origin git@github.com:<owner>/<repo>.git

# Normal workflow
git fetch --all --prune
git pull --rebase
git push

Operating on someone else’s repo directory (host user)

If the repo lives under another user (e.g., /home/proxyuser/repo), ensure group write once:

sudo chgrp -R gitshare /home/proxyuser/repo
sudo chmod -R g+rwX   /home/proxyuser/repo
sudo find /home/proxyuser/repo -type d -exec chmod g+s {} +

Then either run directly:

git -C /home/proxyuser/repo push

or use your wrapper (§19):

git_as proxyuser ~/repo 'push'


25.2 Collaborator Workflow (e.g., proxyuser and others)

One-time on each collaborator

  • Ensure they’re in the gitshare group and re-login:

    id | grep gitshare || echo "Re-login required or add to group."
    
    
  • The host user controls the REAL agent & RELAY; collaborators do not need keys.

Per-shell convenience

Add to collaborator’s ~/.zshrc:

# Use the RELAY (not the REAL socket)
export SSH_AUTH_SOCK=/run/gitshared/ssh-agent.relay

No extra SSH config is required, but the remote must be SSH-style.

Verify access (collaborator)

ssh-add -l                      # should list the host user’s ED25519 key via relay
ssh -T git@github.com           # should greet the host user’s GitHub account

Use in any repo (collaborator)

# Ensure SSH remote (not HTTPS)
git remote -v
git remote set-url origin git@github.com:<owner>/<repo>.git

git fetch --all --prune
git pull --rebase
git push

If you see Permission denied (publickey), confirm you’re pointing at the relay:
echo $SSH_AUTH_SOCK → /run/gitshared/ssh-agent.relay.
If not, export SSH_AUTH_SOCK=/run/gitshared/ssh-agent.relay and retry.


25.3 Quick Commands (cheat sheet)

Host user (once):

# REAL agent + key (handled by §10 service in steady state)
ssh-add -l

Host user (relay health):

systemctl --user status gitshare-agent-relay.service --no-pager
ls -l /run/gitshared/ssh-agent.sock /run/gitshared/ssh-agent.relay

Any collaborator (every shell):

export SSH_AUTH_SOCK=/run/gitshared/ssh-agent.relay
ssh-add -l
ssh -T git@github.com

Git remote must be SSH:

git remote set-url origin git@github.com:<owner>/<repo>.git


25.4 Troubleshooting (host vs. collaborator)

  • Collaborator: ssh-add -l → “communication with agent failed”
    • Cause: using REAL socket or relay not running.
    • Fix: export SSH_AUTH_SOCK=/run/gitshared/ssh-agent.relay
    • Host user: systemctl --user status gitshare-agent-relay.service
  • Collaborator: Permission denied (publickey)
    • Check relay variable, then verify REAL agent has a key (host user):

      SSH_AUTH_SOCK=/run/gitshared/ssh-agent.sock ssh-add -l
      
      
    • Ensure /etc/ssh/ssh_known_hosts contains github.com.

  • Host user pushing into collaborator’s repo path fails with FS perms
    • Apply group write & setgid once (see §25.1).

25.5 Minimal .zshrc snippets (copy-paste)

Host user:

# Shared agent relay for everyone (consistent path)
export GIT_SHARED_SOCK=/run/gitshared/ssh-agent.relay
export SSH_AUTH_SOCK=$GIT_SHARED_SOCK
# (Optional) strict SSH defaults used by wrappers
export GIT_SSH_COMMAND='ssh -o IdentitiesOnly=yes -o IdentityAgent=/run/gitshared/ssh-agent.relay -o StrictHostKeyChecking=yes -o UserKnownHostsFile=/etc/ssh/ssh_known_hosts'

Collaborator (e.g., proxyuser):

export SSH_AUTH_SOCK=/run/gitshared/ssh-agent.relay
# (Optional) enforce strict SSH for all git
export GIT_SSH_COMMAND='ssh -o IdentitiesOnly=yes -o IdentityAgent=/run/gitshared/ssh-agent.relay -o StrictHostKeyChecking=yes -o UserKnownHostsFile=/etc/ssh/ssh_known_hosts'


Net effect: Host continues to own the GitHub identity and the REAL agent. Everyone—host and collaborators—points to the RELAY for a uniform developer experience. No key sprawl, no container changes, and the cross-UID restriction is cleanly sidestepped.

Perfect — here are append-only sections you can paste into your doc. They explain why we moved to the brokered approach, how the final solution works, and a cleanup/hardening plan after all the experiments.


27) Host-Brokered Git via Ephemeral Relay (final)

What we need:

  • Use the host user’s GitHub identity (private key stays on host).
  • Run git inside other users’ repo directories (e.g., proxyuser).
  • No key sprawl, no changing their ownership/permissions, no agent access for them.

How it works (overview):

  1. The REAL ssh-agent runs under the host user, at:

    /run/gitshared/ssh-agent.sock
    
    
  2. For each brokered command, the host spins up a short-lived relay socket:

    /run/gitshared/relay.<user>.<pid>.<rand>.sock
    
    

    The relay forwards to the REAL agent. It is owned by the host, group=gitshare, 0660.

  3. The host executes a tiny sudo-whitelisted runner as the target user.
    That runner receives the relay path and sets SSH_AUTH_SOCK + GIT_SSH_COMMAND itself, so we don’t rely on sudo env_keep.

  4. git runs with the target user’s UID (so the repo is accessible) but signs/authenticates via the host user’s key through the relay.

Why this wins:

  • Works around OpenSSH’s cross-UID agent restriction (agent rejects foreign UIDs).
  • Keeps private keys only under the host.
  • No ownership changes in other users’ repo trees.
  • No password prompts (tight sudoers whitelist).
  • Relay is ephemeral → minimal attack surface, automatic cleanup.

27.1 Components (final, minimal)

Sudoers (NOPASSWD, exact path):

# /etc/sudoers.d/git-broker
blade34242 ALL = (proxyuser) NOPASSWD: /usr/local/bin/git-broker-run *

Runner (sets env from the relay path):

# /usr/local/bin/git-broker-run  (root:root, 0755)
#!/usr/bin/env bash
set -euo pipefail
dir="${1:?need repo dir}"; shift
relay="${1:?need relay socket}"; shift || true
cd "$dir"
export SSH_AUTH_SOCK="$relay"
export GIT_SSH_COMMAND="ssh -o IdentitiesOnly=yes -o IdentityAgent=$relay -o StrictHostKeyChecking=yes -o UserKnownHostsFile=/etc/ssh/ssh_known_hosts"
exec /usr/bin/git "$@"

Host helper (**git_broker** function, uses group=gitshare relay + nohup/setsid):

# in host's ~/.zshrc  (source it afterwards)
# git_broker <targetUser> <repoDir> <git args...>
git_broker() {
  set -euo pipefail
  local tgt="${1:?target user}"; local repo="${2:?repo dir}"; shift 2
  local REAL="/run/gitshared/ssh-agent.sock"
  local RELAY="/run/gitshared/relay.${tgt}.$$.$RANDOM.sock"

  # ensure base dir & start ephemeral relay (TTY-detached)
  install -d -m 2770 -o "$USER" -g gitshare /run/gitshared
  rm -f "$RELAY"
  nohup setsid socat UNIX-LISTEN:"$RELAY",fork,group=gitshare,mode=0660 \
                   UNIX-CONNECT:"$REAL" >/dev/null 2>&1 &

  # wait for relay to appear
  for _ in {1..100}; do [[ -S "$RELAY" ]] && break; sleep 0.05; done
  [[ -S "$RELAY" ]] || { echo "relay did not appear"; return 1; }

  # run as target user, passing <repoDir> and <relay> before git args
  sudo -n -u "$tgt" /usr/local/bin/git-broker-run "$repo" "$RELAY" "$@"

  # best-effort cleanup
  rm -f "$RELAY" || true
}

Usage (host only):

# Read-only
git_broker proxyuser /home/proxyuser status
git_broker proxyuser /home/proxyuser fetch --all --prune

# Push / tags
git_broker proxyuser /home/proxyuser push -u origin HEAD
git_broker proxyuser /home/proxyuser push --tags


28) Rationale: Why we changed & why this design

  • Cross-UID agent restriction: Modern OpenSSH ssh-agent refuses requests from other UIDs even if the socket is group-writable. That produced error fetching identities: communication with agent failed for non-host users.
  • Relay vs. direct sharing: A local relay connects to the REAL agent as the host user, so the agent sees same UID and accepts. The relay itself is just a UNIX socket forwarder; it never exposes private key material.
  • No env fragility: Earlier attempts relied on sudo env_keep to transport SSH_AUTH_SOCK/GIT_SSH_COMMAND. The new runner sets them internally from the relay path, making the flow robust against sudo environment filtering.
  • No repo ownership changes: We execute git as the target user (via sudo), so we don’t need to chgrp/chmod their working tree.
  • Tight control surface: The relay is ephemeral per command and 0660 for gitshare. Only members of gitshare (host + explicitly allowed users) can open it; and we can shrink the group to just the host if we want host-only operations.

29) Security model (focused)

  • Private key stays with host: The REAL agent + key live under the host account. Other users never touch a private key.
  • Signing capability = socket access: Anyone who can open the relay can sign as the host identity (cannot extract the key, but can act on behalf). Keep gitshare membership strict.
  • Auditability:
    • sudo logs show each brokered call (runner + arguments).
    • git history author/committer is set per repo (git config user.*).
  • Optional stronger approval: Replace host key with FIDO2/ed25519-sk to require a physical touch for each signature, even via relay.

30) Ops quick checks

# Host: REAL agent healthy and has key?
SSH_AUTH_SOCK=/run/gitshared/ssh-agent.sock ssh-add -l

# Relay needed only for brokered calls; no long-lived relay required.
# End-to-end:
git_broker proxyuser /home/proxyuser ls-remote git@github.com:<owner>/<repo>.git | head


31) Cleanup & Hardening after the transition

Remove/disable old experiments and stale state:

  1. Disable any obsolete services that started extra agents/relays:

    # As host user (user services)
    systemctl --user disable --now gitshared-ssh-agent@.service 2>/dev/null || true
    systemctl --user disable --now ssh-agent.service 2>/dev/null || true
    systemctl --user disable --now gitshare-agent-relay.service 2>/dev/null || true
    
    

    (Keep your clean REAL agent service from §10 if you use it; you don’t need a persistent relay anymore.)

  2. Purge stale sockets / temp relays:

    sudo find /run/gitshared -maxdepth 1 -type s -name 'relay.*.sock' -delete
    sudo rm -f /run/gitshared/ssh-agent.relay  # if you used a persistent relay earlier
    
    
  3. Tighten **gitshare** membership (principle of least privilege):

    # Keep only host + users you explicitly want to broker for
    getent group gitshare
    # Remove extras if any:
    sudo gpasswd -d <user> gitshare
    
    
  4. Lock sudoers to expected paths (optional but recommended):

    • Narrow the runner’s allowed path (matches first argument) if you only broker in one base:

      # Only allow repos under /home/proxyuser
      blade34242 ALL = (proxyuser) NOPASSWD: /usr/local/bin/git-broker-run /home/proxyuser *
      
      
    • If you broker for multiple users, add one line per target user with their base path.

  5. Delete legacy wrapper scripts (we standardized on the single git_broker):
    Remove any older run_as/git_as scripts that assume direct agent sharing. Keep them only if you still need them for non-Git commands.

  6. Known_hosts baseline:
    Ensure github.com lives in /etc/ssh/ssh_known_hosts:

    ssh-keyscan github.com | sudo tee -a /etc/ssh/ssh_known_hosts >/dev/null
    
    
  7. Secret hygiene:

    • Confirm .gitignore blocks nginx/secrets/**, nginx/logs/**, **/*.key, **/*.pem, .env*, etc.
    • If any secret was briefly staged, unstage & purge:
      git rm -r --cached <path> and (if ever committed) use git filter-repo/BFG.
  8. Optional key refresh:
    If the host key was touched during debugging and you want a clean slate, rotate:

    • generate a new ed25519 key,
    • upload public part to GitHub,
    • remove old public key from GitHub,
    • ssh-add -D && ssh-add ~/.ssh/id_ed25519.

32) FAQ (brokered mode)

  • Why not just group-share the REAL agent socket?
    OpenSSH ssh-agent rejects foreign UIDs even with group write → you get “communication with agent failed.”
  • Why set env in the runner?
    Sudo filters env; relying on env_keep is brittle. Passing the relay path and setting env inside the runner is robust and auditable.
  • Can collaborators push by themselves now?
    Not with this design. Only the host launches relays and calls the runner. If you ever want a collaborator to push directly, use a repo-scoped deploy key (outside of this flow).
  • Does this change Git author/committer?
    No—SSH identity is the network signature; author/committer fields come from the repo’s user.name/user.email.

33) Who Can Push When (Host-Only Policy Explained)

This section documents, crisply and unambiguously, who can push under the final brokered design, and how to verify/enforce it. It complements §§27–32 and does not replace them.

33.1 Local vs. Remote Git (why status works but push doesn’t)

  • Local commands (no network): git status, git diff, git log
    → Always work for any UNIX user in that working tree. No SSH key/agent needed.
  • Remote commands (network, require SSH signing): git fetch, git pull, git push, git ls-remote
    → Require a valid SSH identity (agent/key) or HTTPS token.

With our model, the only usable SSH identity is the host user’s key, reachable via ephemeral relays created by the host for a single command (§27).

Therefore: proxyuser can run local commands anytime, but cannot push unless the host brokers the operation for that command.


33.2 Effective Policy (final)

  • The REAL agent lives at /run/gitshared/ssh-agent.sock under the host user and holds the private key.
  • A relay socket is created ephemerally per command by the host (git_broker 
). It is deleted right after.
  • The sudo-whitelisted runner (/usr/local/bin/git-broker-run) executes git as the target user but injects the relay path so authentication/signing still uses the host’s key.

Net effect:

  • Only the host can create a usable path to the key (by starting the relay).
  • Other users cannot reach a key/agent unless the host actively brokers that command.

33.3 When could proxyuser push? (only if you intentionally allow it)

proxyuser can push only in one of these deliberate cases:

  1. Host-brokered call (current/approved path):
    Host runs git_broker proxyuser /path/to/repo push 
 → allowed for that one command.
  2. Persistent relay exposed (not recommended):
    A long-lived relay 
/ssh-agent.relay with 0660 group access exists and proxyuser points SSH_AUTH_SOCK to it.
    ➜ Do not run such a persistent relay if you want host-only control.
  3. Own credentials for proxyuser (not your host identity):
    • Repo-scoped Deploy Key (write) on GitHub, or
    • SSH account key on proxyuser, or
    • HTTPS remote + personal/access token.
      ➜ Different identity; outside host-only model.
  4. Temporary access window (explicit):
    Host creates a relay and leaves it available (or grants an ACL) for a time-boxed period.
    ➜ Only if you intentionally open such a window.

If none of the above is true, proxyuser cannot push.


33.4 Enforcement Checklist (host-only)

Keep these in place to guarantee that only the host can push:

  • No persistent relay service running

    systemctl --user disable --now gitshare-agent-relay.service 2>/dev/null || true
    sudo rm -f /run/gitshared/ssh-agent.relay
    
    
  • Only ephemeral relays via **git_broker**

    • Use the §27 function that creates relay.<user>.<pid>.<rand>.sock and removes it after the command.
  • Minimal sudoers

    # /etc/sudoers.d/git-broker
    blade34242 ALL = (proxyuser) NOPASSWD: /usr/local/bin/git-broker-run *
    
    

    No rules that let proxyuser execute the runner or set env for themselves.

  • Runner owns env (not env_keep):
    /usr/local/bin/git-broker-run sets SSH_AUTH_SOCK/GIT_SSH_COMMAND from the relay argument.

  • Tight group membership

    getent group gitshare
    # Keep membership minimal; removing users prevents them from opening 0660 relays directly.
    
    

    (You may keep only the host in gitshare and still broker, because the relay is used by the target process launched via sudo.)

  • Single REAL agent

    pgrep -a ssh-agent
    ls -l /run/gitshared/ssh-agent.sock
    SSH_AUTH_SOCK=/run/gitshared/ssh-agent.sock ssh-add -l
    
    

33.5 Negative/Positive Verification (copy-paste tests)

Negative (prove **proxyuser** cannot push alone):

sudo -iu proxyuser bash -lc \
'GIT_SSH_COMMAND="ssh -o IdentitiesOnly=yes" git ls-remote git@github.com:<owner>/<repo>.git || echo "EXPECTED: denied"'
# EXPECTED: Permission denied (publickey) / failure

Positive (host-brokered, proves push path works):

git_broker proxyuser /home/proxyuser ls-remote git@github.com:<owner>/<repo>.git | head
# EXPECTED: refs listing (works because host created the relay for this command)


33.6 Post-Migration Cleanup Recap (safe to run)

  • Disable/remove any legacy agent/relay units:

    systemctl --user disable --now gitshared-ssh-agent@.service 2>/dev/null || true
    systemctl --user disable --now ssh-agent.service 2>/dev/null || true
    systemctl --user disable --now gitshare-agent-relay.service 2>/dev/null || true
    
    
  • Purge stale sockets:

    sudo find /run/gitshared -maxdepth 1 -type s -name 'relay.*.sock' -delete
    
    
  • Shell RCs: remove any global SSH_AUTH_SOCK=/run/gitshared/ssh-agent.relay or old wrappers that assume shared agents.

  • **.gitignore** & secrets: confirm excludes (nginx/secrets/**, keys, tokens, logs).
    If anything was staged by mistake: git rm -r --cached <path>.

  • Known hosts baseline:

    ssh-keyscan github.com | sudo tee -a /etc/ssh/ssh_known_hosts >/dev/null
    
    

33.7 TL;DR

  • proxyuser can always run local Git (e.g., status), but cannot push unless the host brokers that single command.
  • That’s by design: the only signing path is the host’s key via an ephemeral relay the host controls.
  • Keep the enforcement checklist to ensure this stays true, and use the verification snippets anytime to prove it.

34) Onboarding a New Local User (Brokered Mode)

This section shows exactly how to enable a new local user (e.g., alice) so that you (host) can run git in their directory using your host SSH identity, without changing their file ownership and without giving them agent access.

Assumes you already have §27 in place (REAL agent, git_broker, runner).


34.1 Create / confirm the UNIX user (if not existing)

# If needed (root):
sudo adduser alice


34.2 Allow the broker to work for this user

A) Sudoers (host may run the brokered runner as the new user)

sudo visudo -f /etc/sudoers.d/git-broker

Append one line (keep existing ones for other users):

blade34242 ALL = (alice) NOPASSWD: /usr/local/bin/git-broker-run *

This does not give alice any agent access. It only allows you to call the whitelisted runner as alice.

B) Relay access model

Use one of these (pick the one matching your current design):

  • Group-based (your current setup): ensure alice is in gitshare so her git process can open the ephemeral relay socket (0660, group=gitshare).

    sudo groupadd -f gitshare
    sudo usermod -aG gitshare alice
    # IMPORTANT: new group takes effect after a new login:
    sudo -iu alice id | grep gitshare
    
    
  • ACL-based (no group membership): keep §27’s ACL variant of git_broker (gives rw to exactly one user on the ephemeral socket).
    (If you go ACL-only, you do not need to add _alice_ to _gitshare_.)

Either way, there is no persistent relay. Only your git_broker creates a per-command socket and removes it afterwards.


34.3 “Git the user’s directory” (initialize a repo in their home)

As the new user (local-only steps; no agent needed):

# become the user
sudo -iu alice

# in their home (or any path you want to be the repo root)
cd ~
git init
git branch -M main

# (optional) set author metadata for this repo (affects commit headers, not SSH identity)
git config user.name  "Alice Local"
git config user.email "alice@example.local"

# add a remote (SSH form — your brokered identity will authenticate)
git remote add origin git@github.com:<owner>/<repo>.git

# (optional) first content & .gitignore
echo "# Alice's working tree" > README.md
printf '%s\n' \
  '.env*' '*.key' '*.pem' 'nginx/secrets/**' 'nginx/logs/**' \
  > .gitignore

git add .
git commit -m "Initial commit (by alice)"
exit


34.4 First remote test & push (host-brokered)

From your host account (blade34242):

# quick remote handshake test (lists refs if auth works)
git_broker alice /home/alice ls-remote git@github.com:<owner>/<repo>.git | head

# push the initial branch using your host SSH identity
git_broker alice /home/alice push -u origin main

git status / git log etc. always work locally for alice.
Remote operations (fetch/pull/push) go through your SSH identity only when you broker a command.


34.5 Day-to-day use (for you)

Examples you can run anytime as host:

# read-only ops in alice's repo
git_broker alice /home/alice status
git_broker alice /home/alice fetch --all --prune

# typical update
git_broker alice /home/alice pull --rebase

# push current branch / tags
git_broker alice /home/alice push
git_broker alice /home/alice push --tags


34.6 Security & policy (what this does not do)

  • alice cannot push on their own with your identity. There is no persistent relay; only your git_broker creates a per-call socket.
  • If you used the group-based model, alice being in gitshare alone still doesn’t help them: without an active relay you started, there’s nothing to connect to.
  • If you ever want alice to push independently, you must intentionally provide:
    • a deploy key (repo-scoped) or
    • their own SSH key on GitHub or
    • HTTPS remote with a token.
      (All outside the host-only policy.)

34.7 Quick verification & troubleshooting

# Host: real agent has key
SSH_AUTH_SOCK=/run/gitshared/ssh-agent.sock ssh-add -l

# Host-brokered remote listing (should print refs)
git_broker alice /home/alice ls-remote git@github.com:<owner>/<repo>.git | head

# Negative proof: as alice (no broker), remote should fail
sudo -iu alice bash -lc \
'GIT_SSH_COMMAND="ssh -o IdentitiesOnly=yes" git ls-remote git@github.com:<owner>/<repo>.git || echo "EXPECTED: denied"'

If remote tests fail even via git_broker:

  • Ensure /etc/ssh/ssh_known_hosts contains github.com:

    ssh-keyscan github.com | sudo tee -a /etc/ssh/ssh_known_hosts >/dev/null
    
    
  • Confirm your sudoers line exists for alice and no password is prompted:

    sudo -l -U blade34242 | sed -n '1,200p' | grep -A1 git-broker-run
    
    
  • If group-based: confirm alice is in gitshare and re-logged in:

    sudo -iu alice id | grep gitshare
    
    

34.8 De-onboarding (remove a user later)

  • Remove the sudo permission:

    sudo edit /etc/sudoers.d/git-broker   # delete the (alice) line
    
    
  • (Group-based only) Remove from gitshare:

    sudo gpasswd -d alice gitshare
    
    
  • No need to touch their repo; they keep full local control, but cannot brokered-push via your identity anymore.

Danke fĂŒr den klaren Befund. Das „Connection closed“ kommt sehr typisch von Job-Control/HUP-Interaktionen mit dem im Hintergrund gestarteten socat. Auch nohup+setsid helfen nicht immer—zsh schickt dir trotzdem Job-Events, und manche Setups (TTYS/HUP) reißen dann die SSH-Session mit runter.

Hier ist eine robuste Variante deiner git_broker-Funktion, die das zuverlÀssig verhindert:

  • startet den Relay vollstĂ€ndig entkoppelt (bevorzugt via systemd-run --user als transienter Dienst),
  • oder (Fallback) nutzt zsh-spezifisch &! (sofort disowned, kein Job-Output, keine Bindung an dein TTY),
  • kein **set -e** im Top-Level (kein versehentliches Shell-Exit).

Drop-in Fix: neue git_broker-Funktion

FĂŒge das bei **blade34242** in ~/.zshrc ein (alte Funktion komplett ersetzen) und source ~/.zshrc:

# git_broker <targetUser> <repoDir> <git args...>
git_broker() {
  emulate -L zsh                   # lokale, sichere zsh-Umgebung
  setopt localoptions no_monitor   # keine Job-Control Meldungen

  local tgt="${1:?target user}"
  local repo="${2:?repo dir}"
  shift 2

  local REAL="/run/gitshared/ssh-agent.sock"
  local RELAY="/run/gitshared/relay.${tgt}.$$.$RANDOM.sock"

  # Basisdir hart absichern: owner rwx, group r-x, others ---
  install -d -m 2750 -o "$USER" -g gitshare /run/gitshared

  # sauberes Starten: alten Socket weg
  rm -f "$RELAY"

  local used_unit=""
  if command -v systemd-run >/dev/null 2>&1; then
    # VollstÀndig vom TTY getrennt als transienter User-Unit
    used_unit="git-relay-${tgt}-$$-$RANDOM"
    systemd-run --user --unit="$used_unit" --collect \
      -p RuntimeMaxSec=300 -p KillMode=process \
      -p StandardOutput=null -p StandardError=null \
      /usr/bin/socat UNIX-LISTEN:"$RELAY",fork,group=gitshare,mode=0660 \
                     UNIX-CONNECT:"$REAL" >/dev/null 2>&1
  else
    # Fallback: &! = sofort disown (keine Job-Msgs, kein HUP an TTY)
    setsid /usr/bin/socat UNIX-LISTEN:"$RELAY",fork,group=gitshare,mode=0660 \
                           UNIX-CONNECT:"$REAL" >/dev/null 2>&1 &!
  fi

  # auf Socket warten
  for _ in {1..200}; do [[ -S "$RELAY" ]] && break; sleep 0.05; done
  [[ -S "$RELAY" ]] || { [[ -n "$used_unit" ]] && systemctl --user stop "$used_unit" >/dev/null 2>&1 || true; echo "relay did not appear"; return 1; }

  # als Zieluser ausfĂŒhren: Runner setzt SSH_AUTH_SOCK/GIT_SSH_COMMAND selbst
  sudo -n -u "$tgt" /usr/local/bin/git-broker-run "$repo" "$RELAY" "$@"
  local rc=$?

  # aufrÀumen
  rm -f "$RELAY" || true
  [[ -n "$used_unit" ]] && systemctl --user stop "$used_unit" >/dev/null 2>&1 || true

  return $rc
}

Warum das dein Disconnect-Problem löst

  • **systemd-run --user** startet socat außerhalb deiner Shell/TTY → keine Job-Events, kein HUP, keine Session-Bindung.
  • Fallback **&!** (zsh): Job wird disowned, zsh schreibt keine [2] done 
-Zeilen mehr und hĂ€ngt nicht am TTY.
  • Kein **set -e** global → keine versehentlichen Shell-Exits, wenn irgendwo ein Nicht-Null-RC auftaucht.

30-Sek-Checks

# 1) REAL-Agent + Key
SSH_AUTH_SOCK=/run/gitshared/ssh-agent.sock ssh-add -l

# 2) Test ohne Disconnect
git_broker proxyuser /home/proxyuser status

# 3) Remote-Handshake
git_broker proxyuser /home/proxyuser ls-remote git@github.com:blade34242/nginx-reverse-proxy.git | head


Optional: kleines Hardening, das hilft

# /run/gitshared: nur Traverse fĂŒr Gruppe, kein group-write (Race minimiert)
sudo chown blade34242:gitshare /run/gitshared
sudo chmod 2750 /run/gitshared

Der Relay-Socket selbst bleibt 0660 (group=gitshare), damit der als proxyuser laufende Git-Prozess ihn öffnen kann – aber nur wĂ€hrend dein Broker-Call lĂ€uft.


Wenn danach immer noch ein Disconnect kommt, sag mir bitte kurz:

  • Ausgabe von echo $ZSH_VERSION; set -o (zsh-Optionen),
  • ob systemd-run --user verfĂŒgbar ist (command -v systemd-run),
  • und den Timestamp aus journalctl --user -n 50 direkt nach dem Abbruch.

35) Security Posture — Attack Surfaces, Hardening, Residual Risk

This section documents what we changed, what it protects, and where risk remains, so ops and reviewers can reason about the model.


35.1 Threat Model (what we protect)

  • Private key never leaves the host user. Only the host’s REAL ssh-agent holds the key (/run/gitshared/ssh-agent.sock).
  • Other users never get an agent (no agent forwarding, no shared long-lived socket).
  • Brokered model: Host creates a short-lived relay socket per command and runs git as the target user via a whitelisted runner (sudoers). The relay is deleted after the command.

35.2 Attack Surfaces (introduced by the design)

  1. Ephemeral relay socket (UNIX socket file)
    • Path: /run/gitshared/relay.<user>.<pid>.<rand>.sock
    • Risk: another local user in the same group could connect while it exists and request signatures.
  2. **sudo** runner (/usr/local/bin/git-broker-run)
    • Risk: misuse if sudoers is overly broad, or if the runner executes arbitrary programs.
  3. Shared directory (/run/gitshared)
    • Risk: if group-writable, others could plant files or race the relay filename.
  4. OpenSSH agent behavior
    • We intentionally bypass the cross-UID restriction via a relay. If relay is exposed, it’s equivalent to exposing signing capability while it exists.

35.3 Hardening in Place (what we did)

  • No persistent relay. Only git_broker creates a relay per command and removes it immediately after.

  • Strict sudoers (NOPASSWD, exact path):

    blade34242 ALL = (proxyuser) NOPASSWD: /usr/local/bin/git-broker-run *
    
    
    • The runner only execs /usr/bin/git; no shell; no PATH tricks.
    • The runner sets SSH_AUTH_SOCK and GIT_SSH_COMMAND itself (no reliance on env_keep).
  • Strict SSH posture inside the runner:

    • IdentitiesOnly=yes, IdentityAgent=<relay>, StrictHostKeyChecking=yes,
    • UserKnownHostsFile=/etc/ssh/ssh_known_hosts (system-pinned GitHub host key; no interactive prompts).
  • REAL agent isolation:

    • Only under host user; single socket (/run/gitshared/ssh-agent.sock).
    • No ForwardAgent usage; no agent sockets leaked to other sessions.
  • Directory & socket permissions:

    • /run/gitshared created by host, not group-writable (recommend chmod 2750): traverse for group, no group write.
    • Relay sockets: 0660, group=gitshare (or ACL to exactly one user in ACL mode).
  • Randomized relay names to mitigate race/guessing (<pid>.<rand>).

  • Known-hosts baseline: ssh-keyscan github.com >> /etc/ssh/ssh_known_hosts.


35.4 Residual Risks (and how to mitigate)

  • Relay window abuse (local)
    Residual: A user in gitshare could connect to a relay during the brief window it exists.
    Mitigate:

    • Keep gitshare membership minimal (ideally: only host; use ACL variant to grant per-call access to a specific user).
    • Short relay lifetime (already ephemeral).
    • Use FIDO2/ed25519-sk keys or ssh-add -c (confirm required) to require human presence for signatures.
  • Denial of Service on relay
    Residual: A local user could hold relay connections open during the window.
    Mitigate: Keep gitshare tight; prefer ACL variant for single-user operations; keep relay per-command only.

  • Over-broad sudoers
    Residual: If sudoers allowed arbitrary commands or any path, host could unintentionally run more than git.
    Mitigate: Keep exactly:

    blade34242 ALL = (proxyuser) NOPASSWD: /usr/local/bin/git-broker-run *
    
    

    Optional: constrain first arg (path prefix):

    blade34242 ALL = (proxyuser) NOPASSWD: /usr/local/bin/git-broker-run /home/proxyuser *
    
    
  • Group-writable **/run/gitshared**
    Residual: If mode 2770, group members can create files there (relay-name race).
    Mitigate (recommended):

    sudo chown blade34242:gitshare /run/gitshared
    sudo chmod 2750 /run/gitshared    # owner rwx, group r-x, others ---
    
    

    The host still creates sockets; group can traverse to connect but cannot create files.

  • Compromised host account
    Residual: If host is compromised, attacker can sign as the GitHub identity.
    Mitigate:

    • Protect host account; use hardware-backed key (FIDO2) with touch; consider ssh-add -t <ttl> for key lifetimes.
    • Monitor sudo/journal logs for git-broker-run calls.

35.5 Operational Guidance (do this)

  • Keep **gitshare** minimal. Prefer ACL variant for single-user brokering. Review membership:

    getent group gitshare
    
    
  • Harden shared dir:

    sudo chown blade34242:gitshare /run/gitshared
    sudo chmod 2750 /run/gitshared
    
    
  • Verify no persistent relay:

    systemctl --user disable --now gitshare-agent-relay.service 2>/dev/null || true
    sudo rm -f /run/gitshared/ssh-agent.relay
    
    
  • Audit sudoers & runner:

    sudo visudo -c
    sudo -l -U blade34242 | grep -A1 git-broker-run
    ls -l /usr/local/bin/git-broker-run   # root:root, 0755
    
    
  • Disable agent forwarding globally (defense-in-depth):

    # /etc/ssh/ssh_config.d/99-no-agent-forwarding.conf
    Host *
      ForwardAgent no
    
    
  • Key hygiene:

    • Host private key ~/.ssh/id_ed25519 mode 600, dir ~/.ssh mode 700.
    • Consider rotating keys after the migration/testing phase.

35.6 Quick Audit (copy/paste)

# Only one REAL agent?
pgrep -a ssh-agent
ls -l /run/gitshared/ssh-agent.sock

# No persistent relay?
ls -l /run/gitshared/ssh-agent.relay || echo "OK: none"

# Shared dir hardened?
namei -mo /run/gitshared

# Sudoers scope OK?
sudo -l -U blade34242 | sed -n '1,200p' | grep -A1 git-broker-run

# GitHub handshake works only when brokered:
git_broker proxyuser /home/proxyuser ls-remote git@github.com:<owner>/<repo>.git | head
sudo -iu proxyuser bash -lc 'git ls-remote git@github.com:<owner>/<repo>.git || echo "EXPECTED: denied"'


Bottom line

  • Attack surface minimized: no persistent agent exposure; relay is short-lived and controlled by the host.
  • Weaknesses acknowledged: short relay window, local DoS, host compromise risk.
  • Mitigations applied: strict sudoers, runner with fixed binary & in-runner env, hardened dir perms, minimal group, strict SSH options, known_hosts pinning, optional hardware key / confirm.

hier ist eine knackige Namens-Taxonomie fĂŒr alle Varianten, die wir durchgespielt haben — mit Kurzbeschreibung, Synonymen und Status. Nutze diese Labels in deiner Doku / beim Reden darĂŒber.


✅ Aktuelles Ziel-Setup (empfohlen)

  1. TTY-safe Ephemeral Relay (per-Command)
    • Kurz: pro Git-Befehl kurzlebiger socat-Relay, gestartet TTY-sicher via systemd-run --user (Fallback: &! disowned).
    • Warum: kein Session-Drop, kein Dauer-Socket, minimales Angriffsfenster.
    • Synonyme: Per-Command Relay, Transient Relay, TTY-safe Relay.
    • Status: FINAL (verwenden).
  2. Brokered Runner (sudo-whitelist)
    • Kurz: Host ruft als Ziel-User den fixen Runner /usr/local/bin/git-broker-run auf; der Runner setzt selbst SSH_AUTH_SOCK/GIT_SSH_COMMAND (aus Relay-Pfad) und fĂŒhrt /usr/bin/git aus.
    • Warum: unabhĂ€ngig von env_keep, auditierbar, prĂ€zise Sudo-Kontrolle.
    • Synonyme: Runner-based Brokering, Self-setting Runner.
    • Status: FINAL (verwenden).
  3. REAL Agent (host-only)
    • Kurz: Ein einzelner echter ssh-agent unter dem Host-User, Socket /run/gitshared/ssh-agent.sock, Key nur dort geladen.
    • Synonyme: Host REAL Agent, Primary Agent.
    • Status: FINAL (verwenden; optional per systemd user service).

➕ Varianten (je nach Policy)

  1. ACL-Relay (per User)
    • Kurz: Relay 0600 + setfacl u:<user>:rw pro Call; keine Gruppen nötig.
    • Synonyme: Per-User ACL Relay.
    • Status: Optional (max. Least-Privilege).
  2. Group-Relay (gitshare)
    • Kurz: Relay 0660, group=gitshare; Ziel-User ist in gitshare.
    • Synonyme: Group-gated Relay.
    • Status: Optional (einfach, kleines Race-Fenster innerhalb der Gruppe).
  3. Host-Approval Mode
    • Kurz: Signieren nur nach Freigabe: ssh-add -x/-X (Lock/Unlock) oder FIDO2/ed25519-sk mit Touch.
    • Synonyme: Agent Lock, Hardware-touch Required.
    • Status: Optional (fĂŒr stĂ€rkere Freigabe-Kontrolle).
  4. Host-only Mode
    • Kurz: Keine Users in gitshare + ACL-Relay; nur gezielt adressierter User erhĂ€lt rw auf diesen Socket.
    • Synonyme: Strict Host-only.
    • Status: Optional (hĂ€rteste Policy).

đŸ—‘ïž Veraltet / vermeiden

  1. Direct Shared Agent (group-shared socket)
    • Kurz: Andere User verbinden direkt auf den REAL-Agent via Gruppen-Rechte.
    • Problem: OpenSSH Cross-UID-Guard → „communication with agent failed“.
    • Synonyme: Direct Share, Shared SSH_AUTH_SOCK.
    • Status: DEPRECATED (funktioniert nicht mehr sinnvoll).
  2. Wrappers mit **SSH_AUTH_SOCK** (run_as/git_as)
    • Kurz: Zsh-Funktionen, die direkt den REAL-Agent setzen.
    • Problem: Scheitert am Cross-UID-Guard; inkonsistent.
    • Status: DEPRECATED (nur noch fĂŒr non-Git Kommandos ok).
  3. Persistent Relay Service
  • Kurz: Dauerhaftes 
/ssh-agent.relay mit 0660.
  • Problem: Langes Angriffsfenster, ungewollte Selbst-Pushes möglich.
  • Status: DEPRECATED (abschalten).
  1. Broker mit **env_keep**-AbhÀngigkeit
  • Kurz: Verließ sich auf Defaults! 
 env_keep fĂŒr SSH_AUTH_SOCK/GIT_SSH_COMMAND.
  • Problem: Fragil, distro/sudo-abhĂ€ngig.
  • Status: DEPRECATED (ersetzt durch Runner-setzt-Env).

🌐 Alternative Auth-Wege (bewusst anders)

  1. Deploy-Key Mode
  • Kurz: Repo-scoped Key (read/write) auf GitHub, liegt beim Ziel-User.
  • Status: Alternative (eigene IdentitĂ€t, nicht Host-Key).
  1. HTTPS + Token
  • Kurz: Remote via HTTPS mit Personal/Access Token.
  • Status: Alternative (nicht unser Host-Key-Modell).
  1. Container Agent Mount
  • Kurz: /run/gitshared in Container binden + SSH_AUTH_SOCK/GIT_SSH_COMMAND setzen.
  • Status: Optional (nur falls Container Git machen muss; sonst vermeiden).

🧭 „Welches Label nutze ich wann?“ (Kurz-Navigation)

  • Wir fahren „TTY-safe Ephemeral Relay + Brokered Runner + REAL Agent (host-only)“.
  • Policy feinjustieren:
    • Mehr Sicherheit: Host-only Mode + ACL-Relay.
    • Mehr Convenience: Group-Relay (gitshare).
    • Explizite Freigabe: Host-Approval Mode (Lock/Unlock oder FIDO2).

Wenn du willst, packe ich dir diese Liste als Block direkt in deine Doku — sag nur, ob deutsch/englisch und ob mit kurzen Pros/Cons je Eintrag.

Here’s a crisp, security-first analysis (with ratings + recommendations) of every approach we touched, in clean English you can reuse with stakeholders.


Security Analysis & Recommendations

1) Threat model (assumptions)

  • Adversary: other local users on the same host (or inside the same VM/container) with normal user privileges; accidental misuse; misconfiguration.
  • Crown jewels: the private SSH key backing your GitHub identity.
  • Goal: allow the host user to push from other users’ working trees without distributing keys or granting durable signing capability.

2) Terminology (use these terms consistently)

  • REAL agent: the only ssh-agent that actually holds the private key (host user).
    Path: /run/gitshared/ssh-agent.sock
  • Relay: a UNIX socket forwarder (via socat) that connects a client process to the REAL agent. No keys flow through; only signing requests.
  • Brokered runner: /usr/local/bin/git-broker-run — a tiny root-owned script you invoke via sudo (target user). It sets SSH_AUTH_SOCK/GIT_SSH_COMMAND from the relay argument and execs /usr/bin/git.
  • git_broker: the host’s helper function that spins up an ephemeral relay and calls the runner as the target user for one Git command.

3) Approach-by-approach rating (Risk | Misuse | Complexity)

Legend: Risk = residual security risk; Misuse = chance users can push unintentionally; Complexity = operational cost.
(1 = Low/Best, 5 = High/Worst)

# Approach Risk Misuse Complexity Verdict
A Direct Shared Agent (group-shared REAL socket) 5 5 2 Deprecated (blocked by OpenSSH cross-UID; insecure if bypassed)
B Wrapper with shared SSH_AUTH_SOCK (run_as/git_as) 5 4 2 Deprecated (fails on modern OpenSSH; fragile)
C Persistent Relay Service (
/ssh-agent.relay always on) 4 4 2 Avoid (long exposure window; easy misuse)
D Broker relying on sudo env_keep 3 3 3 Avoid (env filtering is distro/sudo dependent; brittle)
E Group-Relay per command (relay 0660, group=gitshare) 2 2–3 3 OK (small race window inside group)
F ACL-Relay per command (0600 + setfacl u:<user>:rw) 1–2 1–2 3–4 Good (least privilege, per-user scoping)
G Host-Approval (agent lock, or FIDO2 touch required) 1 1 3 Great add-on (human presence gating)
H Host-only (strict) (only host in gitshare, ACL relay) 1 1 3–4 Strong (no group race; precise access)
I TTY-safe Ephemeral Relay + Brokered Runner (FINAL) 1–2 1–2 3 Recommended (short exposure; robust; no disconnects)

What you’re running now (I): TTY-safe Ephemeral Relay + Brokered Runner + REAL agent — this scores low risk, low misuse, medium complexity (acceptable), and is our recommended operational baseline.


4) Deep dive — final recommended design (what makes it secure)

  • Key isolation: The private key lives only in the REAL agent under the host user; no copies, no agent forwarding.
  • Short exposure: The relay exists only for one command, then it’s removed. No persistent socket to abuse.
  • No env fragility: The runner sets SSH_AUTH_SOCK/GIT_SSH_COMMAND from the relay path; we do not depend on sudo passing environment through.
  • TTY-safe: Relay launched as a transient user service (systemd-run --user) or as a disowned job (&!). No SSH session drops.
  • Policy enforcement: Only the host can create a usable signing path. Other users can run local Git (status, log) but cannot push unless you broker that command.

Residual risk (and mitigations):

  • Relay race within **gitshare**: another member could touch the relay during the brief window.
    → Keep gitshare minimal or use ACL-Relay per user (best).
  • Local DoS: someone holds the relay open while it exists.
    → Minimal group; prefer ACL mode; keep relay lifetime tiny.
  • Host compromise: attacker controlling host account can sign.
    → Use FIDO2 keys (touch required), short ssh-add -t lifetimes; monitor sudo/journal logs.

5) When to use each approach (pragmatic mapping)

  • I: TTY-safe Ephemeral Relay + Runner (Recommended)
    Multi-user hosts; you need to push from other users’ working trees under your identity; strict control; auditability.
  • F/H: ACL-Relay + Host-only
    Highest assurance. Use when group sharing is too broad. Each command gives exactly one user temporary rw on one socket.
  • G: Host-Approval (Lock / FIDO2)
    Sensitive repos / production branches. Adds human presence control even if a relay is reachable.
  • E: Group-Relay per command
    Small, trusted teams; convenience > strict least-privilege. Keep gitshare tiny, relay lifetime short.
  • C: Persistent Relay
    Generally avoid. If you must (e.g., tightly controlled CI VM without other users), lock down ownership, firewall the machine, and rotate keys often.
  • A/B/D: Direct share, old wrappers, env_keep broker
    Do not use in modern environments. Either broken by OpenSSH or too fragile/insecure.
  • Alternatives (not host identity)
    • Deploy keys: service accounts, per-repo automation with non-personal identity.
    • HTTPS + token: environments that block SSH or need narrow scopes.
    • Container mount of agent: only when containers must push under host identity; bind-mount socket read-only, keep container users controlled.

6) Concrete recommendations (priority order)

  1. Stay on the final model (I): TTY-safe Ephemeral Relay + Brokered Runner + REAL agent.

  2. Tighten group: Prefer ACL-Relay per user or keep gitshare to host + exactly the users you broker for.
    (Best: host-only in gitshare and ACL-Relay; zero group race.)

  3. Harden **/run/gitshared**:

    sudo chown blade34242:gitshare /run/gitshared
    sudo chmod 2750 /run/gitshared   # owner rwx, group r-x, others ---
    
    
  4. No persistent relay services:

    systemctl --user disable --now gitshare-agent-relay.service 2>/dev/null || true
    sudo rm -f /run/gitshared/ssh-agent.relay
    
    
  5. Keep sudoers precise (one line per target user, exact path):

    blade34242 ALL = (proxyuser) NOPASSWD: /usr/local/bin/git-broker-run *
    
    

    If desired, restrict first arg (path prefix) for extra safety.

  6. Consider FIDO2 (ed25519-sk) for production branches to enforce touch-per-signature.

  7. Logging & review: watch journalctl --user and sudo logs for git-broker-run calls; rotate keys on schedule.


7) One-liners to verify posture (copy/paste)

# REAL agent present and key loaded
SSH_AUTH_SOCK=/run/gitshared/ssh-agent.sock ssh-add -l

# No persistent relay
ls -l /run/gitshared/ssh-agent.relay || echo "OK: no persistent relay"

# Shared dir hardened
namei -mo /run/gitshared

# Sudoers precise
sudo -l -U blade34242 | sed -n '1,200p' | grep -A1 git-broker-run

# Negative: target user alone cannot push
sudo -iu proxyuser bash -lc 'git ls-remote git@github.com:<owner>/<repo>.git || echo "EXPECTED: denied"'

# Positive: brokered command works
git_broker proxyuser /home/proxyuser ls-remote git@github.com:<owner>/<repo>.git | head


8) Executive summary (for slides/tickets)

  • We centralized SSH identity in the host’s REAL agent; other users never see the private key.
  • We broker per-command Git operations via a tty-safe ephemeral relay and a sudo-whitelisted runner.
  • Risk is low: no persistent socket, minimal group exposure (or ACL), strict SSH options, known-hosts pinned.
  • For high-sensitivity repos, add FIDO2 touch or agent lock.
  • Avoid legacy patterns (direct shared agent, persistent relay, env_keep brokers).

This gives you the vocabulary, the scoring, and the “when to use what” so you can defend the design and operate it confidently.

37) Current Architecture (final) — What it is now & why we switched

37.1 At a glance (what runs, when, and who can do what)

  • REAL ssh-agent (host-only):
    Runs under the host user (your Git identity), holds the private key. Socket: /run/gitshared/ssh-agent.sock.
  • Ephemeral relay (per command):
    A short-lived socat socket is created only for the duration of a brokered Git command. Launched TTY-safe via systemd-run --user (fallback: disowned &!), so your SSH session never drops.
  • Brokered runner (sudo-whitelisted helper):
    /usr/local/bin/git-broker-run is invoked automatically by **git_broker** for each command, as the target user.
    It forces Git to use the relay by setting per-call core.sshCommand with IdentityAgent=<relay> (and keeps StrictHostKeyChecking + system known_hosts). We explicitly do not use IdentitiesOnly here, so the agent key can be offered across UIDs via the relay.
  • Access gate:
    Group‐based (gitshare) or ACL variant controls which local users can open the ephemeral relay socket during that brief window.
  • Sudoers:
    A precise NOPASSWD rule allows the host to run only the runner as specific target users; target users themselves cannot use your identity.

Result: Only the host can “broker” pushes/pulls in other users’ directories. Target users keep full local Git (status/log), but cannot reach GitHub with your key unless you broker a command.


37.2 Why we switched (lessons learned from earlier attempts)

  • Direct shared agent (group-shared REAL socket) → blocked by OpenSSH cross-UID guard → “communication with agent failed”.
  • Background relay tied to TTY (plain nohup/setsid) → random SSH session disconnects due to job control / SIGHUP.
  • ENV-dependent broker (relying on SSH_AUTH_SOCK passed via sudo) → fragile across distros/sudo policy.
  • **IdentitiesOnly=yes** under target UID → agent key not offered (no matching IdentityFile), ending with **Permission denied (publickey)**.
  • Persistent relay service → unnecessary exposure window (not least-privilege).

The final model fixes all of that:

  • TTY-safe transient relay (no disconnects, no long exposure),
  • runner enforces IdentityAgent=<relay> per call (not fragile to ENV),
  • no IdentitiesOnly under target UID (agent key can be offered),
  • strict SSH options + system known_hosts,
  • precise sudoers scope.

37.3 Exact components (final, “known good”)

  • REAL agent (host): /run/gitshared/ssh-agent.sock, key loaded (ssh-add -l shows your ED25519).
  • Shared dir perms: /run/gitshared is 2750 (owner=rwx, group=rx, others=---) → group can traverse; cannot plant files.
  • Relay (per command): /run/gitshared/relay.<user>.<pid>.<rand>.sock, mode 0660, group gitshare (or ACL to the single target user).
  • Runner: /usr/local/bin/git-broker-run (root:root, 0755) sets git -c core.sshCommand="ssh 
 -o IdentityAgent=<relay> 
" and keeps StrictHostKeyChecking=yes + /etc/ssh/ssh_known_hosts.
  • Sudoers (host → target): one line per allowed user, e.g.
    blade34242 ALL = (proxytest) NOPASSWD: /usr/local/bin/git-broker-run *
  • Broker function (host shell): git_broker <user> <repo> <git-args
> creates relay, calls runner, cleans relay; no sudo in front (it sudos internally).

37.4 Operational roles & guarantees

  • Host user can:
    • broker pull/push/fetch in any enabled target user’s repo,
    • keep the key centralized and audited,
    • add/remove target users via sudoers (who you may broker for) and group/ACL (who may open the ephemeral relay).
  • Target user can:
    • do local Git freely,
    • cannot push with your identity unless you broker that command (no persistent relay, no agent share).
  • Security posture:
    • minimal exposure window (relay exists only per command),
    • strict SSH (no TOFU prompts, host key pinned),
    • least privilege via group or ACL, and precise sudoers.

37.5 Onboarding a new local user (quick recipe)

  1. Sudoers: add one NOPASSWD line for that user (host→runner as target).
  2. Access to relay: either put the user in gitshare or use the ACL relay variant. New login required for group changes.
  3. Repo state: target user’s directory is a Git repo with a correct SSH remote (git@github.com:owner/repo.git). If unborn, make an initial commit.
  4. Broker usage: from host, run e.g.
    git_broker <user> /home/<user> ls-remote origin (read test), then pull/push.

37.6 Common symptoms → cause → one-line fix

  • **Permission denied (publickey)** (brokered):
    Cause: target user can’t open relay, or Git not using relay.
    Fix: ensure /run/gitshared is 2750 and user has access (group/ACL); runner must set core.sshCommand with IdentityAgent=<relay>.
  • SSH session drops on broker call:
    Cause: relay tied to your TTY.
    Fix: use systemd-run --user (or disowned &!) in broker.
  • **sudo: a password is required**:
    Cause: no sudoers line for that target user.
    Fix: add the NOPASSWD runner rule for that user.
  • **src refspec main does not match any**:
    Cause: unborn branch.
    Fix: create an initial (even empty) commit, then push with -u.

37.7 Why this design is the keeper

  • Resilient: no dependency on volatile env; per-call enforcement ensures the right agent path every time.
  • Safe by default: short-lived relay, strict SSH, host-only key custody.
  • Operable: clear knobs (sudoers + group/ACL), deterministic logs (runner prints what it uses), and no surprise disconnects.

Mental model:
You run git_broker 
. It briefly builds a bridge (relay), flips a switch (runner) that makes Git use that bridge, does the work, and then tears the bridge down. Your key never leaves the host agent.

38) Appendix — All Functions, Scripts & Config (final, copy-paste ready)

Use these exact blocks in your doc. They reflect the current, working setup (TTY-safe ephemeral relay + brokered runner, no IdentitiesOnly).


38.1 Host shell function — git_broker (group mode, TTY-safe)

Where: host user’s ~/.zshrc
What it does: creates a short-lived relay socket, calls the runner as the target user, then cleans up. Uses systemd-run --user when available to avoid SSH disconnects.

# git_broker <targetUser> <repoDir> <git args...>
git_broker() {
  emulate -L zsh
  setopt localoptions no_monitor

  local tgt="${1:?target user}"
  local repo="${2:?repo dir}"
  shift 2

  local REAL="/run/gitshared/ssh-agent.sock"
  local RELAY="/run/gitshared/relay.${tgt}.$$.$RANDOM.sock"

  # shared dir: owner rwx, group r-x (no group write)
  install -d -m 2750 -o "$USER" -g gitshare /run/gitshared
  rm -f "$RELAY"

  local used_unit=""
  if command -v systemd-run >/dev/null 2>&1; then
    used_unit="git-relay-${tgt}-$$-$RANDOM"
    systemd-run --user --unit="$used_unit" --collect \
      -p RuntimeMaxSec=300 -p KillMode=process \
      -p StandardOutput=null -p StandardError=null \
      /usr/bin/socat UNIX-LISTEN:"$RELAY",fork,group=gitshare,mode=0660 \
                     UNIX-CONNECT:"$REAL" >/dev/null 2>&1
  else
    setsid /usr/bin/socat UNIX-LISTEN:"$RELAY",fork,group=gitshare,mode=0660 \
                           UNIX-CONNECT:"$REAL" >/dev/null 2>&1 &!
  fi

  # wait for relay
  for _ in {1..200}; do [[ -S "$RELAY" ]] && break; sleep 0.05; done
  [[ -S "$RELAY" ]] || { [[ -n "$used_unit" ]] && systemctl --user stop "$used_unit" >/dev/null 2>&1 || true; echo "relay did not appear"; return 1; }

  # call runner AS target user (first two args must be repo, relay)
  sudo -n -u "$tgt" /usr/local/bin/git-broker-run "$repo" "$RELAY" "$@"
  local rc=$?

  rm -f "$RELAY" || true
  [[ -n "$used_unit" ]] && systemctl --user stop "$used_unit" >/dev/null 2>&1 || true
  return $rc
}

Usage (examples):

git_broker proxytest /home/proxytest status
git_broker proxytest /home/proxytest ls-remote origin | head
git_broker proxytest /home/proxytest pull
git_broker proxytest /home/proxytest push -u origin main


38.2 Runner — /usr/local/bin/git-broker-run (root:root, 0755)

Where: /usr/local/bin/git-broker-run
What it does: runs one Git command as the target user, forcing Git to use the relay (-c core.sshCommand="ssh 
 -o IdentityAgent=<relay> 
").
Note: we intentionally do not set IdentitiesOnly.

#!/usr/bin/env bash
set -euo pipefail

dir="${1:?need repo dir}"; shift
relay="${1:?need relay socket}"; shift || true

# visible debug so you can see what arrives
echo "[runner] EUID=$(id -un)  dir=$dir" >&2
echo "[runner] relay=$relay    args=$*" >&2

# show relay visibility
if [[ -S "$relay" ]]; then
  ls -l "$relay" >&2 || true
  (command -v getfacl >/dev/null 2>&1 && getfacl -p "$relay" >&2) || true
else
  echo "[runner] relay socket NOT FOUND at start" >&2
fi

cd "$dir"

# allow agent keys via relay (env path)
export SSH_AUTH_SOCK="$relay"

# per-call SSH override (no IdentitiesOnly)
sshcmd=(ssh
          -o IdentityAgent="$relay"
          -o StrictHostKeyChecking=yes
          -o UserKnownHostsFile=/etc/ssh/ssh_known_hosts)

# one quick probe (non-interactive) for diagnostics
echo "[runner] ssh probe via relay ..." >&2
/usr/bin/ssh -T "${sshcmd[@]:1}" git@github.com </dev/null || true
echo "[runner] ssh probe done" >&2

# force Git to use the relay for THIS call
exec /usr/bin/git -c core.sshCommand="${sshcmd[*]}" "$@"


38.3 Sudoers (host → target users)

Where: /etc/sudoers.d/git-broker (edit via visudo -f /etc/sudoers.d/git-broker)
What it does: allows the host user to run only the runner as specific target users without password.

# Host may run only this one program as these users, no password
blade34242 ALL = (proxyuser) NOPASSWD: /usr/local/bin/git-broker-run *
blade34242 ALL = (proxytest) NOPASSWD: /usr/local/bin/git-broker-run *
# add more target users as needed:
# blade34242 ALL = (proxy)     NOPASSWD: /usr/local/bin/git-broker-run *


38.4 One-time system prep (idempotent)

Run as host user (may prompt for sudo password once):

# group for relay access (if not present)
sudo groupadd -f gitshare

# secure shared dir: traverse for group, no group-write
sudo install -d -m 2750 -o "$USER" -g gitshare /run/gitshared

# add target users to group (re-login required for them)
sudo usermod -aG gitshare proxyuser
sudo usermod -aG gitshare proxytest

# pin GitHub host key system-wide (avoids TOFU prompts)
ssh-keyscan github.com | sudo tee -a /etc/ssh/ssh_known_hosts >/dev/null

# start REAL agent (once per boot or via your own login script)
export SSH_AUTH_SOCK=/run/gitshared/ssh-agent.sock
pgrep -a ssh-agent >/dev/null || eval "$(ssh-agent -a "$SSH_AUTH_SOCK")"
ssh-add -l >/dev/null 2>&1 || ssh-add ~/.ssh/id_ed25519


38.5 (Optional) ACL mode variant (per-user relay, no group membership)

If you prefer least privilege per call (no gitshare group), keep the runner as-is and add a second broker function that grants access with an ACL to the specific target user:

# git_broker_acl <targetUser> <repoDir> <git args...>
git_broker_acl() {
  emulate -L zsh
  setopt localoptions no_monitor

  local tgt="${1:?target user}"
  local repo="${2:?repo dir}"
  shift 2

  local REAL="/run/gitshared/ssh-agent.sock"
  local RELAY="/run/gitshared/relay.${tgt}.$$.$RANDOM.sock"

  install -d -m 2750 -o "$USER" -g gitshare /run/gitshared
  rm -f "$RELAY"

  local used_unit=""
  if command -v systemd-run >/dev/null 2>&1; then
    used_unit="git-relay-${tgt}-$$-$RANDOM"
    systemd-run --user --unit="$used_unit" --collect \
      -p RuntimeMaxSec=300 -p KillMode=process \
      -p StandardOutput=null -p StandardError=null \
      /usr/bin/socat UNIX-LISTEN:"$RELAY",fork,mode=0600 \
                     UNIX-CONNECT:"$REAL" >/dev/null 2>&1
  else
    setsid /usr/bin/socat UNIX-LISTEN:"$RELAY",fork,mode=0600 \
                           UNIX-CONNECT:"$REAL" >/dev/null 2>&1 &!
  fi

  for _ in {1..200}; do [[ -S "$RELAY" ]] && break; sleep 0.05; done
  [[ -S "$RELAY" ]] || { [[ -n "$used_unit" ]] && systemctl --user stop "$used_unit" >/dev/null 2>&1 || true; echo "relay did not appear"; return 1; }

  # grant only this target user rw to the relay
  command -v setfacl >/dev/null 2>&1 && setfacl -m u:"$tgt":rw "$RELAY" || true

  sudo -n -u "$tgt" /usr/local/bin/git-broker-run "$repo" "$RELAY" "$@"
  local rc=$?

  rm -f "$RELAY" || true
  [[ -n "$used_unit" ]] && systemctl --user stop "$used_unit" >/dev/null 2>&1 || true
  return $rc
}

Sudoers bleibt identisch. In ACL-Mode brauchst du die Ziel-User nicht in gitshare.


38.6 Quick health checks (paste into your doc)

# REAL agent & key
SSH_AUTH_SOCK=/run/gitshared/ssh-agent.sock ssh-add -l
ssh -T -o IdentityAgent=/run/gitshared/ssh-agent.sock git@github.com

# Directory perms
namei -mo /run/gitshared   # expect: drwxr-sr-x (2750) owner=host, group=gitshare

# Sudoers scope
sudo -l -U blade34242 | grep -A1 git-broker-run

# Broker read test (should print refs; runner logs visible at top)
git_broker proxytest /home/proxytest ls-remote origin | head

# First push (if unborn)
git_broker proxytest /home/proxytest checkout -B main
git_broker proxytest /home/proxytest commit --allow-empty -m "Initial commit"
git_broker proxytest /home/proxytest push -u origin main


38.7 What changed vs. early attempts (one paragraph for your doc)

We switched from a direct shared agent (blocked by OpenSSH cross-UID restrictions) and a persistent relay (unnecessary exposure + TTY disconnect issues) to a per-command TTY-safe relay launched by the host and a runner that forces Git to use the relay via core.sshCommand (no reliance on environment). We also removed IdentitiesOnly for target users so the agent key is actually offered via the relay. The result is stable pushes, no session drops, and least-privilege access: the key stays with the host; the relay exists only for the duration of a single brokered command.