Skip to content

Fix review findings: port forwards, refresh retry, snippets, host rename, pairing lockout and more - #82

Merged
ehoeve786 merged 3 commits into
mainfrom
ccr-ca861e8a-6un6h1
Nov 1, 2026
Merged

ehoeve786 merged 3 commits into
mainfrom
ccr-ca861e8a-6un6h1

Conversation

@ehoeve786

@ehoeve786 ehoeve786 commented Nov 1, 2026 •

Copy link
Copy Markdown
Owner

Fixes the findings from two reviews: one of an older snapshot (only the five findings main still had are kept) and one of main as of f845e68.

Port forwards

  • Owner forwards work again. Since aaf770f every forward required a token line that the Port forwards screen never showed and browsers or database clients can't send, so every connection was dropped. Forwards the owner opens no longer require it; MCP agents' forwards still do.
  • Forwards end when MCP is turned off. Pooled MCP sessions, and the forwards on them, now close when the server stops (including the idle timer and screen-off option), an agent token is revoked, or Claude is disconnected. They also close within a minute of MCP read access being turned off for that host, or the host being deleted. Pooled sessions are shared, so revoking one client also closes other clients' forwards.
  • Forwards can't stall the app. The accept loop and relays run on their own threads instead of the shared IO pool, which about 64 forwards could exhaust. MCP may keep at most 8 per connection. ssh.close_forward now needs read access, the same as opening one.

OAuth

  • Refresh retry window. A retry inside the 30-second window used to leave two live token pairs, so a stolen refresh token replayed in that window was never detected. Now only the retry's pair stays live, and the other copy's next refresh is treated as theft and revokes the login.
  • Pairing lockout. The 5-wrong-tries lockout, which anyone with the public URL could trip to cancel the owner's code, is replaced by a 10/minute rate limit on POST /authorize. That allows about 100 guesses per code against 31^8 codes.

Other fixes

  • Snippet quoting. Values are now escaped for where they sit. Inside '…' plain single-quoting left the value unquoted, and inside "…" it left $(…) live. Only exec-scoped agents can run snippets, so there was no privilege gain, but the tool's "quoted, not interpreted" promise was false.
  • Host rename. Renaming replaces the old entry instead of leaving a copy with its trusted key and MCP permissions. A name already in use is refused. Scheduled jobs follow the new name.
  • Trust new key. It now updates the latest saved record, so an edit made since the connect started (such as an MCP permission turned off) is kept.
  • Upload resume. The partial file is also matched by the source's modified time, so an edited file starts over instead of resuming onto old bytes.
  • Plugin commands stop after 1 hour.
  • Terminal writer thread exits when idle instead of leaking one per reconnect.
  • Folder upload removes its partial file on failure or cancel.

Behaviour changes

  • Renaming a host breaks Tasker/MacroDroid actions that use the old name; they need to be edited and saved again. Scheduled jobs are updated automatically.
  • A plugin command running longer than 1 hour is stopped and reported as failed.
  • An owner forward can be used by any app on the phone, as with standard SSH clients.

Testing

  • gradle testDebugUnitTest: 114 tests, no failures. New tests cover the refresh retry, snippet quoting inside single and double quotes and after an escaped quote, and wrong pairing guesses.
  • gradle :app:assembleDebug succeeds.
  • Not run locally: emulator tests and the signed release build (CI runs both). None of the fixes were tried in the running app.

🤖 Generated with Claude Code

ehoeve786 and others added 2 commits November 1, 2026 08:45
- Check SFTP aux sessions against the host key learned on a first connect
- Rename the MCP "read_file" switch to what it does (list the host), off for new hosts
- Don't follow linked folders when downloading or pasting folders
- Renaming a host replaces it instead of copying; refuse names already taken
- Send key passphrases as UTF-8
- Cap queued live plugin output at 4 MB; stop plugin commands after 1 hour
- Let the terminal writer thread exit when idle instead of leaking per reconnect
- Record which MCP client made each audited call
- Rate-limit pairing attempts instead of locking the code after 5 wrong tries
- Keep the Bitwarden unlock prompt alive across rotation
- Remove partial files left by a failed or cancelled folder upload

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Main had moved 111 commits on. It already fixed the SFTP host-key check, symlinked folders,
UTF-8 passphrases, the live output cap and the audit client, and made the read_file switch
real and removed UnlockActivity, so those changes are dropped in favour of main's code.

Kept: host rename replaces instead of copying, 1-hour plugin command timeout, idle-exiting
terminal writer thread, pairing rate limit instead of a 5-try lockout, and cleanup of partial
files from a failed folder upload.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@ehoeve786 ehoeve786 changed the title Fix full-repo review findings in SSH, SFTP, MCP and plugin code Fix remaining review findings: host rename, plugin timeout, writer thread, pairing lockout, partial uploads Nov 1, 2026
- Owner port forwards no longer require the MCP token line, which the app never
  showed and ordinary clients can't send, so they work again
- Pooled MCP sessions (and their forwards) close when the MCP server stops, a
  client is revoked, Claude is disconnected, or MCP read access is turned off
- Forwards run on their own threads instead of the shared IO pool, MCP may keep
  at most 8 per connection, and closing one needs the same access as opening it
- A refresh retry inside the grace window leaves only one live token pair; the
  other copy's next refresh is treated as theft
- Upload resume also matches the source's modified time
- "Trust new key" updates the latest saved record instead of a stale copy
- Snippet values are escaped for the quotes they sit in
- Renaming a host also renames it in scheduled jobs

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@ehoeve786 ehoeve786 changed the title Fix remaining review findings: host rename, plugin timeout, writer thread, pairing lockout, partial uploads Fix review findings: port forwards, refresh retry, snippets, host rename, pairing lockout and more Nov 1, 2026
@ehoeve786
ehoeve786 merged commit c583c91 into main Nov 1, 2026
6 checks passed
@ehoeve786
ehoeve786 deleted the ccr-ca861e8a-6un6h1 branch November 1, 2026 16:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant