Commit Graph
18 Commits
Author SHA1 Message Date
clawbot 17579504e3 Copy go-bip39 into internal/bip39 (closes #42)
check / check (push) Successful in 5m38s
go-bip39's repository no longer exists, so keyfunc now carries the
part of v1.1.0 it uses, with the upstream LICENSE beside it, and
imports it from internal/bip39. Upstream's tests and test vectors for
the kept code come along unchanged; where they called a removed
function, crypto/rand stands in for NewEntropy and EntropyFromMnemonic
for MnemonicToByteArray.

Changes beyond the trimming are what the linter asked for: the
package-level variables moved into the functions that use them, three
error strings were lower-cased, and the unknown-word error now wraps
ErrInvalidMnemonic.

go.mod no longer requires go-bip39. go mod tidy keeps its two go.sum
lines, because a test in sneak/secret's bip85 package imports it, and
drops six lines that only go-bip39's own requirements needed.

Model: opus-5-5
2026-10-06 01:00:18 +00:00
clawbot 56e20b66e4 ssh install refuses stray arguments before -- and a symlinked authorized_keys (closes #61)
check / check (push) Failing after 3s
ssh install now takes the host alone before --: any other word there, or a second argument without --, is refused before the mnemonic is read or sftp runs, so keyfunc ssh install alice@host frank@host no longer installs the key for frank@host. The first listing of ~/.ssh is now ls -n, which shows the file type, so a symlinked authorized_keys is refused before any upload instead of being replaced by a regular file; the README says so.

Judgement calls: install -- host is refused; a symlinked authorized_keys is refused even when its target already holds the key.

Model: opus-5-5
Co-authored-by: clawbot <sneak+clawbot@sneak.cloud>
2026-10-04 18:25:45 +02:00
clawbot 5b36e42e4d age -o keeps a symlink, pipe, device or redirected stream at the path (closes #59)
check / check (push) Failing after 2s
age encrypt -o and age decrypt -o now look at the -o path before writing. A path that is the same file as the tool's standard output or standard error, under any name, is written to that stream, so a redirected file keeps its contents, inode and mode. A new path or a regular file is written beside it and renamed over it, as before, with the signal handling of #48. A symlink gets the same rule for what it points at, so the link survives; a dangling one is refused. A named pipe or a device is written directly. The README says a replaced file gets mode 0600.

Rule suppressed: gosec G304 on the direct open of the -o path.

Model: opus-5-5
2026-10-04 16:59:02 +02:00
clawbot dad29597bd ssh install ends on a signal while sftp's ssh is still connecting (closes #57)
check / check (push) Failing after 1s
A signal during ssh install now sends sftp a SIGTERM instead of killing it, as ssh to already does for ssh, so sftp stops the ssh it started. The sftp command also has a WaitDelay of a quarter second, so a child that still holds sftp's output cannot keep the tool waiting on a host that does not answer: it ends with status 1 and removes its working directory. The test's sftp stand-in now leaves such a child.

Judgement call: exec.ErrWaitDelay counts as success, so a run that used ControlPersist does not report a failure for a key it added.

Model: opus-5-5
Co-authored-by: clawbot <sneak+clawbot@sneak.cloud>
2026-10-04 14:25:47 +02:00
clawbot c96b77dd67 Every command ends on SIGINT, SIGTERM or SIGHUP; an interrupted age -o leaves no file (closes #48)
check / check (push) Failing after 1s
SIGINT, SIGTERM and SIGHUP are no longer caught for the whole run, so they end any command at once, the mnemonic prompt included. One package, internal/cli/signals, catches them only where cleanup is needed, and only those not ignored at start, so nohup still works: ssh to and ssh install while their child runs, and age encrypt -o and age decrypt -o while they write. A signal received by the time the input ends leaves no new file and exits 1; otherwise the whole file is put in place, never an unfinished one.

Judgement call: the guarantee is stated for a signal keyfunc has received, as Go cannot promise more; the main goroutine stays on the main thread so a Ctrl-C on a pipeline is seen first on Linux. Unverified on macOS.

Model: opus-5-5 (implementation); fable-5-1 (design)
2026-10-04 13:42:49 +02:00
clawbot 1d1c8182be Current templates: safe.directory, golangci-lint v2.14.0, fetch-depth 0, the policy's last stage (closes #50)
check / check (push) Failing after 2s
Brings keyfunc to the current sneak/prompts templates: REPO_POLICIES.md and .golangci.yml are the template copies, the lint phase runs golangci-lint v2.14.0 on the template digest (its one new finding fixed), and .dockerignore gains the template line for submodule configs. The stage that compiles keyfunc marks /src safe for git, so a context sent as a tar stream still stamps the tag or short commit. The last stage is now a development environment, as the policy asks of a non-server repo: run the tool as docker run IMAGE keyfunc .... The CI checkout fetches tags.

Deviation: .gitea/workflows/check.yml differs from the template copy by fetch-depth: 0, which REPO_POLICIES.md requires.

Model: opus-5-5
2026-10-04 08:59:08 +02:00
clawbot d4fbcbc83d README child-mnemonic vector and host-key note; ssh install refuses a ~/.ssh it cannot enter (closes #51)
check / check (push) Successful in 2m4s
The README now gives the child mnemonic keyfunc prints for the abandon ... about test mnemonic at index 0, checked by the README vectors test; the BIP-85 specification vector stays, marked as starting from a master key keyfunc cannot take. It also says ssh install needs the host key in known_hosts already, and how to get round that. ssh install now also lists ~/.ssh/. on its first connection and refuses, before any upload, a ~/.ssh it can read but not enter, which sftp shows as empty; a file where ~/.ssh belongs is refused the same way.

Model: opus-5-5
Co-authored-by: clawbot <sneak+clawbot@sneak.cloud>
2026-10-04 07:25:49 +02:00
clawbot 897b43a206 Derive from the mnemonic's words joined by single spaces (closes #49)
check / check (push) Successful in 2m21s
The BIP-39 seed was computed over the mnemonic string as given, with only its ends trimmed, so the same words one per line, tab-separated or double-spaced passed the checksum but gave different keys with no warning. The words are now joined by single spaces before the checksum and the seed, for every source: the mnemonic command, both environment variables and the prompt. Single-spaced input gives the same keys as before; a test checks the README vector for each spacing.

Model: opus-5-5
2026-10-04 07:08:46 +02:00
clawbot 8d1c873bb7 Move the Go module to sneak.berlin/go/keyfunc (closes #15)
check / check (push) Successful in 1m33s
The module path becomes sneak.berlin/go/keyfunc, as the repo policy sets for Go modules: go.mod, every import and the -X path in the Makefile. The old path gets no alias. The README gives a go install line for the new path, which resolves with @latest only once main carries the move, and its TODO list now names the open 1.0 issues.

Model: opus-5-5
2026-10-04 01:42:43 +02:00
clawbot 40e9beea8c Clean up the agent socket and working files when a signal ends the tool (closes #17)
check / check (push) Successful in 5s
`cli.Main` ran the command tree on a background context, so SIGINT, SIGTERM or SIGHUP killed the process before deferred cleanup ran: `ssh to` left its agent socket and directory behind, and `ssh install` left a copy of the host's `authorized_keys` in its working directory. `Main` now runs the tree on a `signal.NotifyContext` for those signals; the cancelled context ends the child `ssh` or `sftp` and the cleanup runs. `ssh to` stops its child with SIGTERM, not a kill, so `ssh` restores the terminal. Exit status after a signal is 1 unless `ssh` reported its own.

The test re-runs the test binary as the tool, waits for the agent socket, sends each signal and checks the directory is gone.

Disclosure: the repeated `"uptime"` test literal became a `remoteCommand` constant because `goconst` required it.

Model: opus-4-8 (implementation, review); fable-5-1 (merge message)
2026-09-21 16:58:24 +02:00
clawbot 64dcc7f42b Keep the mnemonic out of the ssh and sftp children (closes #16)
check / check (push) Failing after 0s
`keyfunc ssh to` and `keyfunc ssh install` started the system `ssh` and `sftp` with the tool's whole environment, so a mnemonic given in `KEYFUNC_MNEMONIC` stayed readable in the child's environment and could be forwarded to the host by a `SendEnv` line. Both children now get the environment with `KEYFUNC_MNEMONIC` and `KEYFUNC_MNEMONIC_COMMAND` removed, through one helper, `childEnv`, in the ssh cli package. The mnemonic command still runs with the full environment. Two tests drive the real commands against the stand-in `ssh` and `sftp` and check that a third variable still arrives.

Model: opus-4-8 (implementation, review); fable-5-1 (merge message)
2026-09-21 14:58:26 +02:00
clawbot 3d90ac87f1 ssh install tells a missing .ssh from one it cannot enter (closes #10)
check / check (push) Failing after 0s
The first sftp session now lists .ssh before fetching authorized_keys. The file reads as empty only when sftp reports .ssh itself as missing, or the listing succeeded and the file is reported missing. A directory or file that is there but cannot be read fails the run and nothing is written, so no existing authorized_keys is replaced by content that was not built from what was read. An .ssh that already exists keeps its mode; the directory is made and set to 0700 only when none was found. The README describes the rule and states batch mode's limit: a key or an agent must authenticate.

Model: opus-4-8 (implementation); fable-5-1 (summary)
2026-09-21 09:49:59 +02:00
clawbot e6ddf49acc Report the module version for a go install build (closes #18)
check / check (push) Successful in 1m41s
keyfunc --version printed dev for any binary not built with make build. When no version was stamped at build time, the tool now reports the module version recorded in the binary's build info, which go install fills in. A stamped version still wins, and a local build with neither still prints dev.

Model: opus-4-8 (implementation); fable-5-1 (summary)
2026-09-21 09:39:38 +02:00
clawbot 8aeed7b901 ssh install works over sftp and runs nothing on the host (closes #10)
check / check (push) Successful in 4s
ssh install no longer runs a command on the host. It reads .ssh/authorized_keys over sftp, takes the empty reading only from sftp's own message about that path, appends the derived key locally when it is not already present, uploads the result beside the file with mode 0600 and renames it over the original. Any other failure prints what sftp said, writes nothing and exits 1. sftp batch mode disables password prompts, so a key or agent is required; a directory the owner cannot enter reads as a host with no file, which README.md states.

Model: opus-5 (implementation); fable-5-1 (landing)
2026-09-08 08:20:17 +02:00
clawbot b9c8631788 The ssh install and ssh to commands (closes #2)
check / check (push) Successful in 2m30s
keyfunc ssh install appends the public line on a host through the system ssh, only when absent, feeding the line on standard input; keyfunc ssh to serves the derived key from an in-process agent on a private socket and runs the system ssh with it, the private key never on disk. Two review rounds; the second passed with no findings.

Model: opus-5 (implementation and review); fable-5-1 (landing)
2026-09-07 18:49:42 +02:00
clawbot 5bbeec86d6 The age commands: pub, priv, encrypt and decrypt (closes #3)
check / check (push) Successful in 28s
keyfunc age derives an age identity at the generic path the way secret's agehd does, prints the recipient or the identity, and encrypts to or decrypts with it, the derived recipient always among encrypt's recipients. Two review rounds; the second passed with no findings, the clamping step now pinned by a fixed identity test.

Model: opus-5 (implementation and review); fable-5-1 (landing)
2026-09-07 18:35:11 +02:00
clawbot d69bed722a The mnemonic command: child mnemonics (closes #4)
check / check (push) Successful in 4s
keyfunc mnemonic derives a 12, 18 or 24 word child mnemonic through BIP-85's own mnemonic application, with the specification's test vectors as tests. One review round, passed with no findings; the reviewer reproduced the vectors from an implementation written from the specification alone.

Model: opus-5 (implementation and review); fable-5-1 (landing)
2026-09-07 18:05:08 +02:00
clawbot 279cba6bcf Skeleton, mnemonic input, derivation, and the ssh pub and priv commands (closes #1)
check / check (push) Successful in 5s
The module, the script entrypoints and Makefile, Docker-only linting, the mnemonic sources in the specified order with their refusals, the BIP-85 derivation, and keyfunc ssh pub and priv with the README test vectors as tests. Two review rounds; the second passed with no findings.

Model: opus-5 (implementation and review); fable-5-1 (landing)
2026-09-07 17:34:54 +02:00