docs: update OpenWiki - #94
Closed
github-actions[bot] wants to merge 13 commits into
Closed
Conversation
github-actions
Bot
force-pushed
the
openwiki/update
branch
5 times, most recently
from
August 11, 2026 09:10
599034f to
19fc009
Compare
Closes #85 (Phase 1, single host). Scripts had to rebuild the ssh command line by hand — `sshub host resolve` plus manual -p/-i/-J/-o assembly — which threw away the stored identity, the askpass wiring and ProxyJump the launcher already knows. `sshub exec web-01 -- systemctl is-active nginx` uses all of it and hands back the remote exit code. stdio passes through (stdin inherited, so pipes work), `-T` by default and `-tt` behind --tty, --timeout kills and exits 124 like timeout(1), --format json buffers the run into one record. A command given on the exec line overrides the host's stored remote_command. Mosh is refused: it has no one-shot command mode, and silently opening an interactive session is a hang, not an error, for a script. The run is audited as `via exec` — filterable with `sshub audit list --via exec`, and excluded from `--via connect`, which now means interactive connects only. The command string itself is never logged: it can carry a password in an argument. Tested: unit tests for the argv shape (command after the target, --tty maps to -tt, stored remote_command overridden), an oracle test that runs the argv we build through the real `ssh -G` and checks it still resolves to the intended host/user/port with the right requesttty (seen red first: moving the command before the target makes OpenSSH reject it with "hostname contains invalid characters"), and smoke tests for the parse failures, the unknown host and the mosh refusal.
Adversarial and security review of the first commit found three ways a scripted `sshub exec` could hang or run the wrong thing. All reproduced first, then fixed: - `--timeout` never fired with `--format json` over a ProxyJump. Killing only ssh leaves the `ssh -W` helper holding the output pipes, so the drain threads never saw EOF. The run now gets its own process group and the timeout kills the group — which also stops the helper outliving the deadline as an orphan. The group is created only when a timeout was asked for: it takes the child out of the terminal's foreground group, and without a deadline Ctrl-C must still reach ssh. - exec could sit on an ssh prompt forever, which issue #85 forbids and the help text denied. ssh reads prompts from /dev/tty, so redirecting stdin does not help. Without a staged secret the run is now `BatchMode=yes`; with one it cannot be, because BatchMode also switches off the askpass helper. - A host whose `~/.ssh/config` sets `RemoteCommand` made ssh refuse the run: "Cannot execute command-line and remote command", exit 255. The argv-tail truncation only covered launcher-managed hosts, so ssh_config hosts had no override at all. `-o RemoteCommand=none` now covers both. Smaller, same review: a remote command's own flags no longer reach the global profile parser (`sshub exec web -- aws --profile prod s3 ls` failed with "unknown profile 'prod'"); a `-flag` without `--` is a usage error instead of being silently dropped; `--timeout` with no value is a usage error rather than a silently disabled timeout, and 0 is refused; a spawn failure emits a JSON record instead of empty stdout. Tests: a second oracle case asks real ssh about the RemoteCommand conflict — with a config that sets it, our argv is accepted and the same argv without `RemoteCommand=none` is refused, so the option cannot be dropped unnoticed. The override unit test now builds its input through the real `build_ssh_argv` instead of a hand-written copy of its output. Smoke tests cover the three new usage errors. openwiki/workflows/cli.md documents the command and the new exit code 124.
Closes #101. A host stored with an address like `-oProxyCommand=id` was pushed onto the argv verbatim, so ssh read it as a flag instead of a hostname and ran `id` locally on the next connect. Verified against the real ssh: $ sshub host resolve evil --format json argv: ['ssh', '-o', 'ForwardAgent=no', '-oProxyCommand=id'] $ ssh -F /dev/null -G -T "-oProxyCommand=id" -- uptime hostname uptime proxycommand id This is not the user's own typo: `address` is written verbatim from imported PuTTY, Termius and mRemoteNG files, so a crafted export was local code execution on whoever imported it and connected. Reachable through connect, exec and tunnels alike, which is why the guard goes where they all pass rather than into any one of them: every target now goes through `safe_ssh_target`, which rewrites a leading-dash target into the `ssh://` URI form. OpenSSH refuses that outright instead of parsing it as a flag, so a hostile row fails loudly; ordinary targets are untouched. `ssh -- <host>` is not an alternative — the words after `--` become the remote command. The oracle test asks OpenSSH both halves: that it still reads the old form as `proxycommand id` (so the guard cannot quietly become obsolete or stop firing), and that it refuses the guarded one. Not fixed here, noted in #101: rejecting such a value at write time so a hostile row never lands at all, with an error naming the field. This commit is the part that also protects rows already in a database.
fix(ssh): a leading-dash host target is no longer an ssh option
# Conflicts: # CHANGELOG.md
feat(cli): sshub exec runs one command on a saved host
Closes #101 — the write-side half. #102 stopped such a value from being executed at connect time; this stops it from being stored at all, which is where the user (or an import of a file someone else wrote) finds out. `create_host` and `update_host` reject a `name`, `address` or `username` starting with `-`: those three make up the connection target, and ssh reads a leading dash as a flag. Nothing legitimate looks like this. The three importers skip a refused row and count it instead of aborting on `?`: one poisoned entry in a mRemoteNG/PuTTY/Termius export must not cost the rest of the file, and the summary line says how many were dropped. The TUI form checks the same rule before writing, because a store error travelling out of `save_host_form` would leave `handle_key` and end the run loop — the e2e test here fails exactly that way when the check is removed. ssh_config-sourced rows are deliberately left alone: that address comes from the user's own ~/.ssh/config, refusing to sync it would silently drop a host they can see in their config, and the connect-time guard from #102 already covers it.
fix(store): refuse a host field ssh would read as an option
The examples block gained `sshub exec` with the feature, but the paragraph above it still listed the CLI as "hosts, groups, identities, tunnels, SFTP, and the audit log" and the exit codes as 0/1/2 — so the one command a script reaches for first was invisible to anyone reading the section rather than scanning the code block, and 124 was undocumented.
docs(readme): name exec in the headless CLI intro
The Features list carried **Audit** and **Settings overlay** twice each, in both cases as a short version and a longer one that said everything the short one did plus more (the session-log path on the audit row, the session-logging toggle on the overlay row). Kept the fuller of each. It also had no bullet for the headless CLI at all — the half of SSHub a script uses, `sshub exec` included — while listing every TUI feature.
docs(readme): drop two duplicated feature bullets, add the CLI
github-actions
Bot
force-pushed
the
openwiki/update
branch
from
August 12, 2026 09:22
19fc009 to
fa504b5
Compare
Owner
|
Closing: this one can never merge. It was opened by `app/github-actions`, and a PR created with `GITHUB_TOKEN` does not trigger workflows — the commit has zero checks, while `main` requires Lint + both Test jobs, so `mergeStateStatus` stays BLOCKED forever. It also targets `main`, which only ever receives release merges. The cause is fixed: `openwiki-update.yml` sets `base: development`, and that reached `main` with v0.14.2, so scheduled runs now open against `development` (as #106 did). Nothing here is lost — the next run regenerates it. Written by Claude Opus 5 (Claude Code) on behalf of the maintainer. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Automated OpenWiki documentation update.
This PR was generated by the scheduled OpenWiki workflow.