Skip to content

fix: round-K audit follow-ups (input hardening + config robustness)#20

Merged
flamerged merged 1 commit into
masterfrom
fix/audit-batch-k
May 1, 2026
Merged

fix: round-K audit follow-ups (input hardening + config robustness)#20
flamerged merged 1 commit into
masterfrom
fix/audit-batch-k

Conversation

@flamerged
Copy link
Copy Markdown
Owner

Summary

New audit batch — five fixes (3× medium, 1× high, 1× low) bundled into one fix: PR. Two flagged items were already shipped in Rounds H/I (Critical "remote home path" → fixed in v0.7.3 via `ssh echo $HOME` + cache; Low "config error spam" → already throttled in v0.7.3) — those are skipped here.

  • HIGH — SSH option-injection via leading -: a remote name like -oProxyCommand=evil.sh was passed unvalidated to spawn('ssh', [name, …]), where ssh would parse it as flags. Now `isValidRemoteName` rejects `-`-prefixed/whitespace/empty values at config-input time, and all three ssh spawn sites pass `--` before the remote as defense-in-depth.
  • MEDIUM — Include directives in ~/.ssh/config: parseSSHConfig now follows them recursively with a loader pattern (testable). Resolves `/`, anchors relative paths to `/.ssh/`, supports basic ``/`?` glob — covers the canonical `Include ~/.ssh/config.d/` setup. Recursion capped at 16 levels. `Match` blocks are skipped.
  • MEDIUM — SSH upload concurrency cap: a 2-slot semaphore around pipeToRemote. Burst of 5 screenshots no longer spawns 5 simultaneous ssh sessions.
  • MEDIUM — custom macOS screenshot prefix: `defaults read com.apple.screencapture name` queried at startup; if the user set a custom prefix, a second regex is built (with metacharacter escaping) and folded into `isMacScreenshotFilename`.
  • LOW — wasteful 200ms config polling: loadConfig() now mtime-gates. The poll loop's 5x/sec calls only re-read+parse when `fs.statSync` shows a newer mtimeMs.

Test plan

  • yarn typecheck / lint / build / format:check
  • yarn test — 37 tests, +11 new (Include with single+multi specs, recursion cap, Match skip, isValidRemoteName cases, custom-prefix regex including metachar escaping)
  • CI matrix Node 20/22/24 green

- isValidRemoteName(): reject custom remote names that ssh would parse as
  flags (`-oProxyCommand=…`), have whitespace, or are empty. Wired into
  the interactive add-remote prompt with a user-facing rejection message.
  All three ssh spawn sites now also pass `--` before the remote, so even
  if a `-`-prefixed name slipped past the validator (e.g. via a
  hand-edited config.json), ssh wouldn't treat it as flags.
- parseSSHConfig() now follows `Include` directives recursively. The
  loader is injected (testable). Real-world loader expands `~`, anchors
  relative paths to `~/.ssh/`, and handles basic `*`/`?` glob in the
  basename — covers `Include ~/.ssh/config.d/*` cleanly. Recursion capped
  at 16 levels to short-circuit cyclic includes. `Match` blocks are
  skipped (they don't introduce new Host names).
- pipeToRemote() now serializes through a 2-slot semaphore. Five rapid
  Cmd+Shift+3s previously spawned five parallel ssh sessions; now at most
  two run concurrently with the rest queued.
- Custom macOS screenshot prefix support: at startup, `defaults read
  com.apple.screencapture name` is queried and a second regex is built
  from the override (with metacharacter escaping). Users who customized
  their prefix via `defaults write … name "MyShot"` are now picked up.
- loadConfig() now mtime-gates: stat first, return cached parse if
  mtimeMs matches the last successful load. The poll loop calls this
  5x/sec; on the steady-state hot path it now does one stat instead of a
  full read+JSON.parse. saveConfig() invalidates the cache.

+11 tests (Include, multi-spec Include, recursion cap, Match skip,
isValidRemoteName, custom prefix regex). Total 37.
@flamerged flamerged merged commit e71cac3 into master May 1, 2026
5 checks passed
@flamerged flamerged deleted the fix/audit-batch-k branch May 1, 2026 19:42
@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 1, 2026

🎉 This PR is included in version 0.7.5 🎉

The release is available on GitHub release

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant