feat(ssh): add collect-host-public-key method (#917) #88

Merged
stack72 merged 1 commit from issue-917/collect-host-public-key into main 2026-07-02 17:20:45 +00:00
Owner

Summary

  • Add collect-host-public-key method to @swamp/ssh that reads a remote host's SSH public key file over SSH, validates it as a single OpenSSH public key line, computes its SHA256 fingerprint, and emits a structured hostPublicKey resource per host.
  • Supports all existing fleet targeting (selectors, transport types, temp key materialization, ControlMaster) — the method follows the same patterns as exec/script/copy.
  • Validates against private key content, multi-line output, empty files, and unrecognized key algorithms. Shell-quotes the hostKeyPath via POSIX single-quoting for safe command construction.
  • Bumps version to 2026.07.02.1 with upgrade entry. Updates manifest methods table and README with full documentation.

Closes swamp-club#917

Test plan

  • 4 new schema validation tests (CollectHostPublicKeyArgsSchema: defaults, custom path, empty rejection, hosts required)
  • 8 new integration tests (ssh_test.ts): successful collection with field verification, custom hostKeyPath, empty output, multi-line output, private key rejection, invalid algorithm, non-zero exit with resource-before-throw, multiple hosts
  • Updated model shape test to include new method and resource
  • All 246 tests pass (0 failed)
  • deno check, deno lint, deno fmt --check, deno install --frozen all clean
  • Upgrade path verified: published 2026.06.27.12026.07.02.1 via swamp extension source add

🤖 Generated with Claude Code

## Summary - Add `collect-host-public-key` method to `@swamp/ssh` that reads a remote host's SSH public key file over SSH, validates it as a single OpenSSH public key line, computes its SHA256 fingerprint, and emits a structured `hostPublicKey` resource per host. - Supports all existing fleet targeting (selectors, transport types, temp key materialization, ControlMaster) — the method follows the same patterns as `exec`/`script`/`copy`. - Validates against private key content, multi-line output, empty files, and unrecognized key algorithms. Shell-quotes the `hostKeyPath` via POSIX single-quoting for safe command construction. - Bumps version to `2026.07.02.1` with upgrade entry. Updates manifest methods table and README with full documentation. Closes swamp-club#917 ## Test plan - [x] 4 new schema validation tests (`CollectHostPublicKeyArgsSchema`: defaults, custom path, empty rejection, hosts required) - [x] 8 new integration tests (`ssh_test.ts`): successful collection with field verification, custom `hostKeyPath`, empty output, multi-line output, private key rejection, invalid algorithm, non-zero exit with resource-before-throw, multiple hosts - [x] Updated model shape test to include new method and resource - [x] All 246 tests pass (0 failed) - [x] `deno check`, `deno lint`, `deno fmt --check`, `deno install --frozen` all clean - [x] Upgrade path verified: published `2026.06.27.1` → `2026.07.02.1` via `swamp extension source add` 🤖 Generated with [Claude Code](https://claude.com/claude-code)
feat(ssh): add collect-host-public-key method (#917)
All checks were successful
CI / cve/researcher - fmt (pull_request) Has been skipped
CI / cve/researcher - lint (pull_request) Has been skipped
CI / cve/researcher - test (pull_request) Has been skipped
CI / ssh - test (pull_request) Successful in 1m15s
CI / cve/dirtyfrag - lockfile up to date (pull_request) Has been skipped
CI / cve/mini-shai-hulud - lockfile up to date (pull_request) Has been skipped
CI / cve/researcher - lockfile up to date (pull_request) Has been skipped
CI / software-factory - check (pull_request) Has been skipped
CI / software-factory - fmt (pull_request) Has been skipped
CI / software-factory - lint (pull_request) Has been skipped
CI / model/digitalocean - check (pull_request) Has been skipped
CI / software-factory - lockfile up to date (pull_request) Has been skipped
CI / software-factory - test (pull_request) Has been skipped
CI / model/hetzner-cloud - check (pull_request) Has been skipped
CI / model/digitalocean - lockfile up to date (pull_request) Has been skipped
CI / aws models - lockfiles up to date (pull_request) Has been skipped
CI / model/hetzner-cloud - lockfile up to date (pull_request) Has been skipped
CI / aws models - sample check (pull_request) Has been skipped
CI / gcp models - sample check (pull_request) Has been skipped
CI / gcp models - lockfiles up to date (pull_request) Has been skipped
CI / cloudflare models - sample check (pull_request) Has been skipped
CI / cloudflare models - lockfiles up to date (pull_request) Has been skipped
CI / codegen - check (pull_request) Has been skipped
CI / codegen - fmt (pull_request) Has been skipped
CI / codegen - lint (pull_request) Has been skipped
CI / codegen - lockfile up to date (pull_request) Has been skipped
CI / CI Security Review (pull_request) Has been skipped
CI / Adversarial Code Review (pull_request) Has been skipped
CI / Claude Code Review (pull_request) Successful in 3m30s
CI / Merge Gate (pull_request) Successful in 25s
fec5e911d8
Add a new method to the SSH extension that reads a remote host's SSH
public key file over an existing SSH connection, validates it as a
single OpenSSH public key line, computes its SHA256 fingerprint, and
emits a structured hostPublicKey resource per host.

This enables host certificate workflows where the SSH extension
observes remote host key material without signing or managing
certificates.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Author
Owner

Code Review

Blocking Issues

None.

Suggestions

  1. atob can throw an opaque DOMException on malformed base64 (operations.ts, computeFingerprint). If a remote file contains a syntactically correct key line (passes parsePublicKeyLine) but its base64 field is not valid base64, atob(base64Data) throws a DOMException with no host name or context. A small try/catch that rethrows as a named Error (e.g. collect-host-public-key failed on ${host}: key base64 data is not valid) would make failures easier to diagnose.

  2. Dead guard after throwOnHostFailures (operations.ts, runCollectHostPublicKey). The if (r.exitCode !== 0 || r.error) continue; check in the second loop is unreachable when throwOnHostFailures uses fail-fast semantics (which the non-zero exit throws via throwOnHostFailures test confirms). The guard is harmless and matches the defensive style elsewhere in the file, but removing it or moving it before throwOnHostFailures would make the control flow more readable.


The implementation is solid overall. posixQuote correctly handles single-quote escaping, the private-key detection (-----BEGIN) guards against the most dangerous misconfiguration, the SHA-256 fingerprint computation matches ssh-keygen -lf output, and the temp-key cleanup is correctly in a finally block. Test coverage is thorough: schema defaults, rejection cases, multi-host success, and non-zero-exit behaviour are all exercised with mock executors rather than live SSH connections.

## Code Review ### Blocking Issues None. ### Suggestions 1. **`atob` can throw an opaque `DOMException` on malformed base64** (`operations.ts`, `computeFingerprint`). If a remote file contains a syntactically correct key *line* (passes `parsePublicKeyLine`) but its base64 field is not valid base64, `atob(base64Data)` throws a `DOMException` with no host name or context. A small try/catch that rethrows as a named `Error` (e.g. `collect-host-public-key failed on ${host}: key base64 data is not valid`) would make failures easier to diagnose. 2. **Dead guard after `throwOnHostFailures`** (`operations.ts`, `runCollectHostPublicKey`). The `if (r.exitCode !== 0 || r.error) continue;` check in the second loop is unreachable when `throwOnHostFailures` uses fail-fast semantics (which the `non-zero exit throws via throwOnHostFailures` test confirms). The guard is harmless and matches the defensive style elsewhere in the file, but removing it or moving it before `throwOnHostFailures` would make the control flow more readable. --- The implementation is solid overall. `posixQuote` correctly handles single-quote escaping, the private-key detection (`-----BEGIN`) guards against the most dangerous misconfiguration, the SHA-256 fingerprint computation matches `ssh-keygen -lf` output, and the temp-key cleanup is correctly in a `finally` block. Test coverage is thorough: schema defaults, rejection cases, multi-host success, and non-zero-exit behaviour are all exercised with mock executors rather than live SSH connections.
stack72 deleted branch issue-917/collect-host-public-key 2026-07-02 17:20:45 +00:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
swamp-club/swamp-extensions!88
No description provided.