Skip to content

Repository files navigation

who-signed

Which project, and which command, just asked to use your SSH key?

How it works · Install · Security · Contributing

The Key Ledger dashboard, showing signing requests with the project and program behind each one


Your SSH agent asks you to approve a signature. It does not tell you what asked — it cannot, because by the time it sees the request all it knows is that something on the machine wants a signature.

So when a compromised npm package asks for one at the moment you expected a prompt, you approve it. who-signed is the missing half of that prompt:

An expanded entry showing an npm postinstall script that was denied, with its full chain of parent programs

It sits transparently in front of your agent and records, for every request:

  • which command and chain of programs (ssh-keygen ‹ git ‹ zsh ‹ Cursor)
  • from which directory and git project
  • which key, by name and fingerprint
  • whether it was a git signature, an SSH login, or a key listing
  • and whether you approved or denied it

Your agent is never modified, and private keys never pass through it. It sees the requests, never the key material.

How it works walks through the whole process, from git commit to log entry, assuming no prior knowledge.

Works with

Any SSH agent: Secretive, 1Password, gpg-agent, YubiKey, or plain ssh-agent. The installer detects whichever you use.

macOS today. Linux needs a small change (SO_PEERCRED instead of LOCAL_PEERPID) — the hook is in whosigned/procinfo.py.

Install

git clone https://github.com/sarangshahane/who-signed.git ~/who-signed && ~/who-signed/install.sh

The installer detects your agent, installs a LaunchAgent that keeps the proxy running, points ~/.zshrc and ~/.ssh/config at it, and refuses to finish unless the agent actually answers through the proxy. It backs up every file it edits and is safe to re-run.

Then open a new terminal. You now have a who-signed command:

who-signed doctor    # check the install is healthy
who-signed open      # open the dashboard
who-signed status    # machine-readable status

And the dashboard is at:

http://127.0.0.1:8722

That is it — no token, no flags, no command to run first. Bookmark it.

Updating

The LaunchAgent runs the code from wherever you cloned it, so an update is a pull plus a restart:

git -C ~/who-signed pull && launchctl kickstart -k gui/$(id -u)/com.who-signed.agent

Re-running install.sh also works and is safe — it is idempotent — but it is only necessary if the LaunchAgent, shell config or who-signed command need recreating.

Commands

Command Does
who-signed run Run the proxy in the foreground (the LaunchAgent does this for you)
who-signed open Open the dashboard in your browser
who-signed url Print the dashboard URL (with a token, if one is required)
who-signed status Machine-readable status as JSON
who-signed doctor Check the install is actually healthy
who-signed config Show effective configuration

Uninstall

~/who-signed/uninstall.sh

Stops the agent, removes the LaunchAgent, strips its blocks out of ~/.zshrc and ~/.ssh/config, and restores your original agent. Your audit log is kept; add --purge to delete it too.

Configuration

~/.who-signed/config.json, all keys optional:

{
  "upstream_socket": "/path/to/your/agent.sock",
  "http_port": 8722,
  "require_token": false,
  "log_max_bytes": 5000000,
  "log_keep": 30,
  "redact_secrets": true,
  "connect_timeout": 5.0,
  "response_timeout": 300.0
}

What it protects, and what it does not

It records metadata about requests. Never key material. Your keys stay in the Secure Enclave, your YubiKey, or wherever your agent keeps them.

The log is yours alone. ~/.who-signed/ is 0700 and the log is 0600, so other accounts on the machine cannot read which projects you work on.

Command lines are redacted. Tokens, passwords, and keys that appear on a command line are replaced before anything is written. See whosigned/redact.py.

The dashboard rejects requests whose Host header is not loopback, so a web page you visit cannot reach it by DNS rebinding. That protection is always on and costs you nothing.

On a shared Mac, add a token. Set "require_token": true and the dashboard also demands a secret (printed by who-signed url). This guards against other user accounts on the same machine, which can reach loopback HTTP even though they cannot read ~/.who-signed. It is off by default because it adds nothing against code running as you — that can read the log file directly — and it would make every visit require a setup step.

It fails open, by design. If the proxy stops, ~/.zshrc falls back to your real agent so you can still sign — but those signatures are not recorded. Anyone who can run code as you can stop the proxy. This is a visibility tool, not a security boundary.

The log is not tamper-proof. It is a file you own; anything running as you can edit it. For stronger guarantees, ship a copy off the machine.

Attribution can rarely be wrong. The requesting process is identified by PID, and a process that exits immediately could have its PID recycled before we read it. When the timing looks impossible the record is flagged pid_reused rather than quietly attributed to the wrong program.

Privacy

This records what you work on, when, and the commands you run. The data never leaves your machine — there is no telemetry, no upload, and no network listener beyond loopback.

If your organisation ever adds central collection, that changes the tool from a personal audit log into employee monitoring, with the legal and ethical obligations that follow. Make that opt-in, documented, and agreed — do not bolt it on quietly.

Screenshots

The images above come from a demo instance with synthetic data — the projects, keys and commands in them are invented, not anyone's real activity.

Development

python3 -m unittest discover -s tests

110 tests, no dependencies, no network, and no real agent required — the suite runs against a fake agent socket, so it never touches your keys.

How it works

your tool ──► who-signed ──► your real SSH agent
                  │
                  ├── appends a record to ~/.who-signed/audit-log.jsonl
                  └── serves the Key Ledger on 127.0.0.1

Every byte is forwarded unchanged in both directions; the audit record is a side effect that can never alter the conversation. Key names are learned from the agent's own identity listings rather than read off disk — reading another app's container can block indefinitely behind a macOS privacy prompt, and on the signing path that would hang every git command.

Author

Built by Sarang Shahane.

Issues and pull requests welcome — see CONTRIBUTING.md.

Licence

MIT © Sarang Shahane — see LICENSE.

About

Audit which project and command asks to use your SSH key. A transparent proxy in front of Secretive, 1Password, gpg-agent or ssh-agent, with a local Key Ledger dashboard.

Topics

Resources

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages