Commit Graph
21 Commits
Author SHA1 Message Date
clawbot ccdc576cd3 Copy go-bip39 into internal/bip39 (closes #42)
check / check (push) Successful in 4m30s
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
Co-authored-by: clawbot <sneak+clawbot@sneak.cloud>
2026-10-06 03:27:15 +02: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 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 1ddc2c747a make fmt and make fmt-check cover Markdown with prettier (closes #39)
check / check (push) Successful in 2m37s
make fmt and make fmt-check now cover the Markdown files with prettier as well as the Go source: prettier 3.8.1 pinned in package.json and yarn.lock, four-space indents and proseWrap always in .prettierrc, all three copied unchanged from the sneak/prompts templates. script/bootstrap adds pinned node (through nvm from a hash-checked archive when none is installed), yarn and prettier after the pinned Go. README.md is reformatted by make fmt and its Entrypoints section says what each step needs.

Model: opus-5-5
2026-10-04 05:42:46 +02:00
clawbot 7280ee35f4 script/bootstrap installs a pinned Go so script/cibuild runs on the Gitea runner (closes #46)
check / check (push) Successful in 3m12s
script/cibuild runs script/bootstrap on the Gitea runner, which has Docker and git but no Go, and bootstrap could not install it: its apt path never ran apt-get update. Bootstrap now installs Go at the version in the Dockerfile's golang image from go.dev, checked against sha256 values in the script, into ~/.local/go whenever the go first on PATH reports another version; the Makefile and the fmt, fmt-check and precommit scripts put ~/.local/go/bin first on their PATH. apt-get update runs once before the first apt install. Bumping the golang digest now means bumping GO_VERSION and its four hashes.

Partially verified: checked in a clean ubuntu:24.04 container, not yet on the Gitea runner.

Model: opus-5-5
2026-10-04 05:06:31 +02:00
clawbot 90596de901 Lint and test as phases of the Dockerfile (closes #38)
check / check (push) Failing after 3s
Lint and test are now phases of the one Dockerfile, as the current repo policy requires: a lint phase on the pinned golangci-lint image and a test phase on the pinned Go image, and the build stage depends on both, so a plain docker build . fails when either fails. Dockerfile.lint is gone. REPO_POLICIES.md and script/lint, test, docker and cibuild are byte-identical to the current sneak/prompts copies, so every docker build in script/ is uncached and tagged. make test now needs Docker on the host; formatting is checked on the host only.

Judgement calls: the test phase installs gcc and musl-dev unpinned for -race; no -count=1, since a build stage holds no earlier result.

Model: opus-5-5
2026-10-04 02:59:03 +02:00
clawbot d6b87f7502 The linter config, .gitignore and .dockerignore from the current templates (closes #40)
check / check (push) Successful in 1m32s
.golangci.yml is now a byte-identical copy of the current template: depguard is on with the test-support rule, and the gomodguard_v2 block list is in. .gitignore and .dockerignore are the current templates with this repo's own entries added at the end; the .dockerignore secret-file patterns now keep key files and .env files out of the build context, while .git stays in for the version stamp. No Go source needed changes.

Model: opus-5-5
2026-10-04 02:25:50 +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 f8c796db9b Add the MIT license (closes #14)
check / check (push) Successful in 1m52s
Adds LICENSE with the standard MIT text and "Copyright (c) 2026 sneak", the license sneak chose. The README now calls keyfunc MIT-licensed in its first paragraph, its License section points at LICENSE, and the license line leaves the TODO list.

Model: opus-5-5
2026-10-03 14:42:59 +02:00
clawbot dd14677145 README checked against the tree by running every example (closes #22)
check / check (push) Successful in 42s
Every example in the README was run as written with the published test mnemonic and behaved as the README says, so no sentence changed. The only edit removes the landed work from the TODO section, which now lists the two open owner decisions.

Disclosures:
- `ssh install` and `ssh to` were run by the implementer against a throwaway local `sshd`; the reviewer could not repeat that run and checked those sections by reading the code.
- The child mnemonic vector is reachable only through the test suite and was confirmed there.

Model: opus-4-8 (implementation, review); fable-5-1 (merge message)
2026-09-21 18:07:36 +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 15ebe24f7b README: policy sections and age and child mnemonic vectors (closes #21)
check / check (push) Successful in 6s
The README gains the sections REPO_POLICIES.md requires: a first sentence naming the category and author, Getting Started, Entrypoints (one line per `script/` file), Rationale, Design, TODO (the open issues between the tree and 1.0), License and Author. It also publishes test vectors for age and child mnemonics, copied from the tests.

Disclosures:
- No license is named; the choice is open on the tracker and the README says so.
- The 12-word child mnemonic is the BIP-85 specification vector, the only one the test asserts, and is labelled as such.
- Markdown is hand-wrapped; `make fmt` here formats Go only.

Model: opus-4-8 (implementation, review); fable-5-1 (merge message)
2026-09-21 16:24:28 +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 c3fd26a1de keyfunc: the specification
The README is the specification sneak approved on 2026-09-07, moved
here from the hacks repository: deterministic SSH keys, age identities
with encrypt and decrypt, and child mnemonics, all derived from one
BIP-39 mnemonic and stored nowhere.

Model: fable-5-1
2026-09-07 14:27:57 +00:00