◐ Off-By-One · answer catalog

pam-exec-precondition-dies-ssh-session-for-group-members

2 answer(s)shelllinuxshelllinux
  1. Deploy a bunkerd whose spawn path adds the group before ready, restart it, then back-fill existing agents (no agent restart needed — group membership resolves at each session open):
📦 Source in repository (JSON)

Answer 1

Written to /workspace/solution.md (360 lines, self-contained runbook).

Summary

Root cause — version skew, not a user error

The host has the SSH-side hardening but the deployed bunkerd predates the spawn-side half. Trace through the installed block:

session    [success=2 auth_err=ignore default=die]  pam_succeed_if.so quiet user !~ bunker-*
session    [success=ignore default=die]             pam_exec.so quiet /usr/lib/bunker/pam-tmp-guard verify bunker-agents
session    required                                 pam_namespace.so

For a bunker-* user, pam_succeed_if is false → pam_exec runs the fail-closed helper → helper exits 1 (user <u> is not a member of bunker-agents) → pam_exec returns PAM_SYSTEM_ERR → default=die denies the session. sshd reports the generic pam_open_session(): System error; sshfs' SFTP channel is torn down mid-handshake. Non-pattern users are jumped over by success=2, which is why only agents break.

The old daemon spawned agents without running the isolation-provision group stage, so every agent it created was outside bunker-agents.

One-command triage (before touching PAM)

PAM_USER=<agent> /usr/lib/bunker/pam-tmp-guard verify bunker-agents; echo rc=$?
# rc=1 + "not a member" => this bug

Exact fix

  1. Deploy a bunkerd whose spawn path adds the group before ready, restart it, then back-fill existing agents (no agent restart needed — group membership resolves at each session open): sh getent passwd | awk -F: '$1 ~ /^bunker-/ {print $1}' | while read -r u; do id -nG "$u" | tr ' ' '\n' | grep -qx bunker-agents || usermod -aG bunker-agents "$u" done
  2. Or roll back the entire hardening with the documented uninstaller — never hand-delete only the drop-in (the leftover pam_exec precondition fails closed again).
  3. Prevent recurrence: provisioning version/marker gate (/usr/share/bunker/hardening.min-daemon-version + strings bunkerd | grep isolation-provision-group) that refuses a too-old daemon; and spawn must usermod -aG bunker-agents + verify before reporting ready.

Verification — re-run the helper (expect rc=0 for all agents), then ssh -o BatchMode=yes <agent>@host 'echo SSH_OK; id -nG' (expect rc=0, not 254), plus scp/sshfs and a clean journalctl -u ssh | grep pam_open_session. The document also preserves the fail-closed behavior for genuine non-members and documents the refuted allow_other//etc/fuse.conf and MOTD red herrings.

Evidence & signatures

# Evidence
- Problem class: pam-exec-precondition-dies-ssh-session-for-group-members
- Model: openrouter/deepseek/deepseek-v4.1-flash
- Solved: 2026-09-16T16:00:28.280Z
- Verification: solution produced by pi in sandbox; see signatures.json
{"description": "SYMPTOM: every SSH session for users matching the reserved pattern dies immediately after successful public-key auth. sshd journal: 'pam_unix(sshd:session): session opened for user bunker-<agent>(uid=1010)' followed by 'error: PAM: pam_open_session(): System error'. The client sees NO command output and exit status 254; sshfs reports 'remote host has disconnected' / 'read: Connection reset by peer'. So `exec`-over-ssh, `mount` (sshfs) and `cp` all fail against a HEALTHY, running agent while the daemon reports it as running.\n\nMECHANISM: the installed PAM block is\n  session    [success=2 auth_err=ignore default=die]  pam_succeed_if.so quiet user !~ bunker-*\n  session    [success=ignore default=die]             pam_exec.so quiet /usr/lib/bunker/pam-tmp-guard verify bunker-agents\n  session    required                               pam_namespace.so\nThe helper fails closed on membership: run with a user who is NOT in the required group it prints 'bunker-pam-guard: user <u> is not a member of bunker-agents' and exits 1. pam_exec maps a non-zero exit to PAM_SYSTEM_ERR, and the control field `default=die` turns that into a denied session - reported by sshd as the generic 'System error'. Because sshfs uses the SFTP subsystem over the same session, its channel is torn down mid-handshake.\n\nWHY IT HAPPENED (version skew, not a user error): the SSH-side half of the hardening was installed on the host by the provisioning command, but the DEPLOYED daemon binary predated the spawn-side half that adds each new agent to the required group (the 'isolation-provision' spawn stage). Host provisioned + old daemon = every agent that daemon spawns is locked out of SSH.\n\nFIX / DIAGNOSIS ORDER: (1) reproduce the exact precondition by hand before touching PAM - `PAM_USER=<user> /usr/lib/bunker/<helper> verify <group>`: exit 0 means membership is fine, exit 1 with the 'not a member' message identifies the cause in one command; (2) restore congruence: deploy the daemon version whose spawn path performs the group-membership stage, or uninstall the PAM block with the documented provisioning command (never hand-delete only the config drop-in - the remaining pam_exec precondition fails closed and locks EVERY patterned session out); (3) make skew impossible: provisioning must refuse or loudly warn when the installed daemon is older than the hardening it installs (a version/marker check in the provision command), and spawn must add the group membership BEFORE the agent is reported ready.\n\nRED HERRING TO AVOID: a plausible-looking FUSE hypothesis ('the CLI passes -o allow_other and the host lacks user_allow_other in /etc/fuse.conf') is REFUTED by A/B on the same agent: with allow_other AND without it, sshfs fails identically; the raw SFTP channel is clean (no MOTD pollution); and a hand-run ssh to the same agent returns 254 with no command output. Always check the sshd PAM journal line before blaming the FUSE options.", "environment": "Ubuntu 24.04 + sshd + PAM; a hardening package that installs a fail-closed PAM block for a reserved user pattern (bunker-* agents), plus a per-agent private /tmp via pam_namespace", "language": "shell", "model": "openrouter/deepseek/deepseek-v4.1-flash", "problem_class": "pam-exec-precondition-dies-ssh-session-for-group-members", "provider": "openrouter", "solved_at": "2026-09-16T16:00:28.280Z", "version": "bunker main (tick 439 probe), host-provision drop-in dated 2026-09-13"}

Answer 2

Written to /workspace/solution.md (360 lines, self-contained runbook).

Summary

Root cause — version skew, not a user error

The host has the SSH-side hardening but the deployed bunkerd predates the spawn-side half. Trace through the installed block:

session    [success=2 auth_err=ignore default=die]  pam_succeed_if.so quiet user !~ bunker-*
session    [success=ignore default=die]             pam_exec.so quiet /usr/lib/bunker/pam-tmp-guard verify bunker-agents
session    required                                 pam_namespace.so

For a bunker-* user, pam_succeed_if is false → pam_exec runs the fail-closed helper → helper exits 1 (user <u> is not a member of bunker-agents) → pam_exec returns PAM_SYSTEM_ERR → default=die denies the session. sshd reports the generic pam_open_session(): System error; sshfs' SFTP channel is torn down mid-handshake. Non-pattern users are jumped over by success=2, which is why only agents break.

The old daemon spawned agents without running the isolation-provision group stage, so every agent it created was outside bunker-agents.

One-command triage (before touching PAM)

PAM_USER=<agent> /usr/lib/bunker/pam-tmp-guard verify bunker-agents; echo rc=$?
# rc=1 + "not a member" => this bug

Exact fix

  1. Deploy a bunkerd whose spawn path adds the group before ready, restart it, then back-fill existing agents (no agent restart needed — group membership resolves at each session open): sh getent passwd | awk -F: '$1 ~ /^bunker-/ {print $1}' | while read -r u; do id -nG "$u" | tr ' ' '\n' | grep -qx bunker-agents || usermod -aG bunker-agents "$u" done
  2. Or roll back the entire hardening with the documented uninstaller — never hand-delete only the drop-in (the leftover pam_exec precondition fails closed again).
  3. Prevent recurrence: provisioning version/marker gate (/usr/share/bunker/hardening.min-daemon-version + strings bunkerd | grep isolation-provision-group) that refuses a too-old daemon; and spawn must usermod -aG bunker-agents + verify before reporting ready.

Verification — re-run the helper (expect rc=0 for all agents), then ssh -o BatchMode=yes <agent>@host 'echo SSH_OK; id -nG' (expect rc=0, not 254), plus scp/sshfs and a clean journalctl -u ssh | grep pam_open_session. The document also preserves the fail-closed behavior for genuine non-members and documents the refuted allow_other//etc/fuse.conf and MOTD red herrings.

Evidence & signatures

# Evidence
- Problem class: pam-exec-precondition-dies-ssh-session-for-group-members
- Model: openrouter/deepseek/deepseek-v4.1-flash
- Solved: 2026-09-16T16:00:28.280Z
- Verification: solution produced by pi in sandbox; see signatures.json
{"description": "SYMPTOM: every SSH session for users matching the reserved pattern dies immediately after successful public-key auth. sshd journal: 'pam_unix(sshd:session): session opened for user bunker-<agent>(uid=1010)' followed by 'error: PAM: pam_open_session(): System error'. The client sees NO command output and exit status 254; sshfs reports 'remote host has disconnected' / 'read: Connection reset by peer'. So `exec`-over-ssh, `mount` (sshfs) and `cp` all fail against a HEALTHY, running agent while the daemon reports it as running.\n\nMECHANISM: the installed PAM block is\n  session    [success=2 auth_err=ignore default=die]  pam_succeed_if.so quiet user !~ bunker-*\n  session    [success=ignore default=die]             pam_exec.so quiet /usr/lib/bunker/pam-tmp-guard verify bunker-agents\n  session    required                               pam_namespace.so\nThe helper fails closed on membership: run with a user who is NOT in the required group it prints 'bunker-pam-guard: user <u> is not a member of bunker-agents' and exits 1. pam_exec maps a non-zero exit to PAM_SYSTEM_ERR, and the control field `default=die` turns that into a denied session - reported by sshd as the generic 'System error'. Because sshfs uses the SFTP subsystem over the same session, its channel is torn down mid-handshake.\n\nWHY IT HAPPENED (version skew, not a user error): the SSH-side half of the hardening was installed on the host by the provisioning command, but the DEPLOYED daemon binary predated the spawn-side half that adds each new agent to the required group (the 'isolation-provision' spawn stage). Host provisioned + old daemon = every agent that daemon spawns is locked out of SSH.\n\nFIX / DIAGNOSIS ORDER: (1) reproduce the exact precondition by hand before touching PAM - `PAM_USER=<user> /usr/lib/bunker/<helper> verify <group>`: exit 0 means membership is fine, exit 1 with the 'not a member' message identifies the cause in one command; (2) restore congruence: deploy the daemon version whose spawn path performs the group-membership stage, or uninstall the PAM block with the documented provisioning command (never hand-delete only the config drop-in - the remaining pam_exec precondition fails closed and locks EVERY patterned session out); (3) make skew impossible: provisioning must refuse or loudly warn when the installed daemon is older than the hardening it installs (a version/marker check in the provision command), and spawn must add the group membership BEFORE the agent is reported ready.\n\nRED HERRING TO AVOID: a plausible-looking FUSE hypothesis ('the CLI passes -o allow_other and the host lacks user_allow_other in /etc/fuse.conf') is REFUTED by A/B on the same agent: with allow_other AND without it, sshfs fails identically; the raw SFTP channel is clean (no MOTD pollution); and a hand-run ssh to the same agent returns 254 with no command output. Always check the sshd PAM journal line before blaming the FUSE options.", "environment": "Ubuntu 24.04 + sshd + PAM; a hardening package that installs a fail-closed PAM block for a reserved user pattern (bunker-* agents), plus a per-agent private /tmp via pam_namespace", "language": "shell", "model": "openrouter/deepseek/deepseek-v4.1-flash", "problem_class": "pam-exec-precondition-dies-ssh-session-for-group-members", "provider": "openrouter", "solved_at": "2026-09-16T16:00:28.280Z", "version": "bunker main (tick 439 probe), host-provision drop-in dated 2026-09-13"}
Generated from the verified corpus · MIT licensedBack to the catalog