fix(ssh): avoid false missing-key errors for large agent listings
Nobody has claimed this yet.
Assessment
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Newbie friendliness
- 77/100
Research direction
Start in skills/headless/scripts/mac/lib.sh around lines 49–51, where load_config checks ssh-add -L with grep -qF; inspect how the shared library uses pipefail. Reproduce with the isolated large agent listing in the issue, then run the probe again after changing the check. Done means a loaded usable key is accepted with a large listing, while an absent key and an actual agent failure are still rejected.
Written by the indexing model from the issue text.
Description
Summary
load_config can reject a passphrase-protected SSH key that is already loaded and usable in ssh-agent when the agent's identity listing is sufficiently large. The shared check treats the listing producer's SIGPIPE as evidence that the key is absent, even though its matching consumer succeeded.
This is a functional false failure in the documented passphrase-key workflow. It occurs before any target connection or password-store lookup.
Tested source and environment
Headless main at fe1a381c6f382ec3c9822da0e706484414bc63e0. Reproduced through Crabbox 0.82.0 in a disposable Linux VM with the real OpenSSH ssh-agent, ssh-add, ssh-keygen, and Bash. An independent local macOS run produced the same result.
The fixture used its own supervised agent, generated keys, and synthetic key passphrase. It did not read or modify the operator's agent, credentials, SSH configuration, or remote machines. No real secret store was used.
Reproduction
Run the following from the checkout root in a disposable shell. It creates an isolated agent and fixture keys, then tests only configuration loading:
(
set -eu
repo=$PWD
work=$(mktemp -d)
ssh-agent -D -a "$work/agent.sock" >/dev/null 2>&1 &
agent_pid=$!
trap 'kill "$agent_pid" 2>/dev/null || true; rm -rf "$work"' EXIT
export SSH_AUTH_SOCK="$work/agent.sock"
for _ in {1..100}; do
[ ! -S "$SSH_AUTH_SOCK" ] || break
sleep 0.02
done
ssh-keygen -q -t ed25519 -N '' -C fixture -f "$work/key"
ssh-add "$work/key" >/dev/null 2>&1
ssh-keygen -q -p -P '' -N synthetic-fixture-passphrase -f "$work/key" >/dev/null
skills/headless/scripts/linux/new-config.sh "$work/config" \
--address example.invalid --login admin --password-ref cmd:false \
--ssh-key "$work/key"
comment=$(printf 'x%.0s' {1..2048})
for i in {1..96}; do
ssh-keygen -q -t ed25519 -N '' -C "$comment" -f "$work/extra-$i"
ssh-add "$work/extra-$i" >/dev/null 2>&1
done
ssh-add -T "$work/key.pub"
/bin/bash -c 'source "$1"; load_config "$2"; echo ACCEPTED' \
probe "$repo/skills/headless/scripts/mac/lib.sh" "$work/config"
)
The generated passphrase is synthetic and applies only to the throwaway key. The fixture establishes a large valid identity list; the exact output size and failure threshold can vary by platform and pipe buffering.
Observed evidence and controls
The complete probe exercised the unchanged key in a small agent, added unrelated fixture identities, checked an actual agent signature, ran the shared precheck, inspected pipeline status, and finally removed only the unrelated identities:
CONTROL small-agent load_config exit=0
CONTROL agent signing test exit=0; identity-list bytes=106175
HEADLESS load_config exit=1
headless: <fixture-key> has a passphrase and is not loaded in ssh-agent ...
PIPE ssh-add=141 grep=0
CONTROL full-consuming match exit=0
RESTORED removing unrelated fixture identities makes load_config exit=0
ssh-add -T successfully signed and verified using the corresponding public key. The key was therefore not merely present as a matching string: its private key was usable through the agent. The full-consuming control used ssh-add -L | grep -F KEY_BLOB >/dev/null under pipefail; it succeeded without changing any agent identity. Restoring the small listing also made the original headless precheck succeed again, with the same encrypted key file and loaded target identity.
Expected behavior and cause
The skill explicitly supports passphrase-protected keys loaded in ssh-agent. Adding unrelated identities should not make a loaded usable key appear absent.
skills/headless/scripts/mac/lib.sh:49-51 checks ssh-add -L | grep -qF ..., and the same library enables set -euo pipefail. With enough output remaining after the match, grep -q exits early, ssh-add receives SIGPIPE, and the pipeline is nonzero despite grep returning 0. load_config then prints the incorrect not-loaded diagnosis. The Linux and Windows controller libraries source this shared implementation; the executed checks cover Linux and macOS controllers, not native Windows activation.
Impact, workaround, and validation
The defect blocks operations using load_config for this valid agent state. The demonstrated case used 97 generated identities and an approximately 106 KiB listing; prevalence with ordinary user agents was not measured, and no fixed cross-platform key-count threshold is claimed.
A dedicated agent containing only the required key avoids the observed precheck failure. The small-list control and restoration directly verified that state without decrypting or replacing the target key. Users should not need to remove unrelated identities from their normal agent merely to satisfy a false absence check.
Regression coverage should retain the loaded passphrase-key success case while exercising a listing large enough to expose early pipe closure. It should still reject a genuinely absent target key and should not confuse an actual agent failure with a successful match whose producer was interrupted by the consumer.
Triage assessment
- Impact level: P2
- Assessment status: supported
- Impact basis: A valid loaded and usable key is rejected before connection when unrelated agent output is large enough to trigger SIGPIPE; the failure is bounded to the demonstrated listing condition.
- Workaround status: available
- Workaround basis: The same encrypted key was accepted with only its own identity loaded, and removing only unrelated fixture identities restored acceptance after the failure.
- Dominant language
- Shell
- Stars
- 92
- Forks
- 6
- Avg merge
- 25m
- Merged PRs (30d)
- 5
Getting set up
This project ships no dev container, Dockerfile or contributing guide, so setting up is up to you: start from its README, and see our first-contribution guide for the general steps.
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from kunchenguid/headless
-
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
kunchenguid/headless#23 ·
-
Difficulty 2/5 1-3 hours Newbie friendliness 75/100
kunchenguid/headless#21 ·
-
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
kunchenguid/headless#19 ·
-
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
kunchenguid/headless#16 ·
-
Difficulty 2/5 1-3 hours Newbie friendliness 83/100
kunchenguid/headless#12 ·
All issues in kunchenguid/headless
Similar issues
-
bug
Difficulty 2/5 1-3 hours Newbie friendliness 68/100
msys2/MINGW-packages#33356 · 1 comment ·
Maintainers usually reply within 1 day
-
Misleading 'Update → Config → Hyprland' menu item silently resets user configs without confirmationOpen
Difficulty 2/5 1-3 hours Newbie friendliness 72/100
Maintainers usually reply within 1 day
-
bug
Difficulty 2/5 1-3 hours Newbie friendliness 82/100
ekalinin/github-markdown-toc#204 ·
Maintainers usually reply within 1 day
-
Difficulty 1/5 1-3 hours Newbie friendliness 88/100
-
Difficulty 2/5 1-3 hours Newbie friendliness 78/100
mack-a/v2ray-agent#1370 ·