diff --git a/README.md b/README.md index 9c7efc3a4..b00439c61 100644 --- a/README.md +++ b/README.md @@ -19,8 +19,8 @@ brew install aida0710/tap/sshc Homebrewを使わない場合は、installerとbinaryを同じReleaseへ固定します。 ```sh -SSHC_VERSION=v0.35.0 sh -c \ - 'curl -fsSL https://raw.githubusercontent.com/aida0710/sshc/v0.35.0/install.sh | sh' +SSHC_VERSION=v0.36.0 sh -c \ + 'curl -fsSL https://raw.githubusercontent.com/aida0710/sshc/v0.36.0/install.sh | sh' ``` Windows PowerShell: diff --git a/docs/release-install.md b/docs/release-install.md index 53417b15b..2fc8dc3c7 100644 --- a/docs/release-install.md +++ b/docs/release-install.md @@ -13,8 +13,8 @@ Homebrew formula はソースからビルドするため、Go toolchain も Home ## インストールスクリプト(macOS / Linux) ```sh -SSHC_VERSION=v0.35.0 sh -c \ - 'curl -fsSL https://raw.githubusercontent.com/aida0710/sshc/v0.35.0/install.sh | sh' +SSHC_VERSION=v0.36.0 sh -c \ + 'curl -fsSL https://raw.githubusercontent.com/aida0710/sshc/v0.36.0/install.sh | sh' ``` URLと`SSHC_VERSION`には同じ導入対象のタグを指定します。`main`上のスクリプトは次の変更で内容が変わるため、pipeで直接実行しません。新しい版へ更新するときは、[GitHub Releases](https://github.com/aida0710/sshc/releases)でタグを確認して両方を置き換えます。 @@ -72,8 +72,8 @@ powershell -NoProfile -ExecutionPolicy Bypass -Command "irm https://github.com/a 再現可能な導入では、スクリプトと成果物を同じタグへ固定します。 ```powershell -$env:SSHC_VERSION = 'v0.35.0' -irm https://github.com/aida0710/sshc/releases/download/v0.35.0/install.ps1 | iex +$env:SSHC_VERSION = 'v0.36.0' +irm https://github.com/aida0710/sshc/releases/download/v0.36.0/install.ps1 | iex ``` 手動で配置する場合は、[GitHub Releases](https://github.com/aida0710/sshc/releases) からx64では`sshc-windows-amd64.exe`、Arm64では`sshc-windows-arm64.exe`を取得し、`checksums.txt`と照合してから`sshc.exe`へ名前を変更します。 diff --git a/docs/releases/README.md b/docs/releases/README.md index 0c758496c..a9dc5d51d 100644 --- a/docs/releases/README.md +++ b/docs/releases/README.md @@ -94,3 +94,4 @@ - [v0.33.9](v0.33.9.md) - [v0.34.0](v0.34.0.md) - [v0.35.0](v0.35.0.md) +- [v0.36.0](v0.36.0.md) diff --git a/docs/releases/v0.36.0.md b/docs/releases/v0.36.0.md new file mode 100644 index 000000000..46abc5d38 --- /dev/null +++ b/docs/releases/v0.36.0.md @@ -0,0 +1,83 @@ +# sshc v0.36.0 + +v0.36.0では、2026年9月18日に行った仕様・実装・セキュリティの監査で見つかった問題をまとめて修正しました。ブラウザー登録の仕組みを作り直し、CLIがengineを確かめてから秘密情報を渡すようにし、SSH設定の解決をOpenSSHと同じ規則に揃えています。SFTPでは、サーバーが返す不正な名前の拒否、別ホストへの移動での削除前の照合、ダウンロードの速度低下の解消を行いました。 + +あわせて、SFTPのタブをドラッグしてペインを分ける操作、シンボリックリンクの解決、Terminalの再接続時の画面サイズの反映、削除前の確認ダイアログの統一、英語UIの用語の統一を行っています。 + +## 更新後に必要な操作 + +- **ブラウザーの登録を一度やり直してください。** 登録情報の保存形式が変わり、v0.35.0以前の登録は読み込みません。更新後にengineを再起動し、`sshc`(または`sshc open`)が表示するURLをブラウザーで開くと登録し直せます。 +- **engineを再起動してください。** 新しいCLIは、engineが引き継ぎ用の秘密を持っていることを確かめてから`status`や`vault`の操作を行います。更新前から動き続けているengineに対しては、engineを確認できない旨のエラー(`did not prove it holds the handoff secret`)になります。 + +## ブラウザー登録とCLIの安全境界 + +- これまでブラウザーの登録トークンは一度発行すると変わりませんでしたが、ブラウザーが登録情報で入り直すたび(engineの再起動後や、新しいタブでsshcを開いたとき)に新しいトークンへ差し替えるようにしました。差し替え前のトークンは、同じブラウザーの別タブのために1分間だけ同じ新しいトークンを返します。それを過ぎて古いトークンが提示された場合は複製とみなし、その登録を失効させます。 +- 登録は最後に使ってから**30日**で失効します。engineが使うポートが変わった場合も、以前のポート向けの登録はすべて失効します。登録できるブラウザーは16件までで、古いものから消えます。 +- engine側が保存するのは、照合用のハッシュ、発行と最終使用の時刻、ポート番号だけです。トークンそのものはワークスペースにも同期スナップショットにも含めません。 +- CLI(`sshc status`、`sshc vault`など)は、引き継ぎ用の秘密を送る前に、engineへ乱数を送って秘密で署名した応答を検証します。engineが後始末を経ずに消え、同じポートを別のプロセスが占有していても、そのプロセスにマスターパスワードやS3の認証情報を渡しません。端末を閉じたときのSIGHUPでも通常の終了と同じ後始末を行い、引き継ぎ情報を残しません。 +- Webは、登録の回復が401で拒否された場合に保存済みトークンを捨て、再読み込みのたびに同じ拒否を繰り返さないようにしました。使用済みの初回URLを開いた場合も、登録済みのブラウザーならそのまま入れます。 + +## SSH設定の解決 + +- `Match host`は、OpenSSHと同じくaliasではなく**解決後のHostName**と比較するようになりました。これまでは`Match host `がaliasに一致していましたが、`HostName`を別に書いている設定では一致しなくなります。OpenSSHの`ssh -G`と同じ結果になるように、`Match host`にはHostNameを書いてください。 +- `ProxyJump`と`ProxyCommand`の両方が解決された設定は、これまで接続を拒否していましたが、OpenSSHと同じく**先に書かれた方だけを使い**、後から来た行を無視するように変更しました。無視した行はConnectionsのAnalysisと保存前のプレビューに「無視された設定」として表示します。`ProxyCommand none`の後の`ProxyJump`は無視され、`ProxyJump none`の後の`ProxyCommand`は有効です。 +- `known_hosts`の`!pattern`(否定)をOpenSSHと同じように解釈します。 + +## SFTP + +- **タブのドラッグでペインを分割・結合**できるようにしました。「2ペイン」ボタンは廃止し、Terminalと同じくタブをファイル一覧へドラッグすると左右2ペインに分かれます。ペインが1つならタブを落とした側に新しいペインを開き、元のペインには空のタブを残します。ペインが2つならもう一方へタブが移り、最後のタブが出て行ったペインは閉じます。境目のつまみで幅を変えられ、キーボードでは選択中のタブをShift+←/→で移せます。 +- **シンボリックリンク**は一覧でリンク先と種類を解決し、「名前 → リンク先」で表示します。ディレクトリへのリンクはフォルダーとして開き、ファイルへのリンクは詳細・プレビュー・編集・ダウンロードがリンク先を扱います。保存はリンク先を書き換えてリンクを残します。壊れたリンクとループは開けません。コピー・移動・削除・engineとの転送は従来どおりリンクを辿りません。 +- 一覧と比較ダイアログのサイズ表記をKiB/MiB/GiBにし、列名をSizeにしました。 +- ドロップの扱いを整理しました。同じ接続先の同じディレクトリへ落としても何もしません。同じ接続先の別のディレクトリへ落とした場合はダイアログを出さずに移動します。別ホストへ落とした場合は従来どおりコピー/移動を選びます。 +- 別のウィンドウやページからのドラッグは受け付けなくなりました。これまでは、別のページがsshcの行を装ったデータをドラッグに載せると、ペインへ落とした時点で確認なしにengineの転送が並ぶ可能性がありました。行の実体はページ内に置き、ドラッグには不透明な識別子だけを載せます。 +- 2ペイン表示で各ペインが狭くなると、一覧がスマホ向けの高い行に切り替わっていた問題を直しました。マウス操作のデスクトップでは1行の密な一覧(名前・サイズ・日付)を使い、タッチ端末だけ従来の高い行を残します。 +- **ダウンロードの速度**: 受信したデータの塊ごとに保存先のファイルを閉じ直してengineへ位置を報告していたため、ファイルサイズの二乗に比例して遅くなっていました(17 MiBで約15秒)。8 MiBごと、終了時、再試行の前だけ報告するように変更しました。切断時は届いた分を報告してから再試行します。 +- engine側のコピー/移動で「上書き」ボタンが何もしなかった問題を直しました。ファイルを失ったアップロードだけを再添付待ちとして扱います。 +- **サーバーが返す不正な名前を拒否**します。壊れた、または悪意のあるSFTPサーバーが一覧に`..`や`/`を含む名前を返した場合、これまではホスト間のディレクトリコピーだけが検証を持たず、信頼している別のサーバーの任意のパスへ書き込める可能性がありました。空、`.`、`..`、`/`、NULを含む名前が返された一覧は全体をエラーにします。engine側のディスクへ書く場合は`\`も拒否します。 +- 別ホストへの**移動**は、コピー直後の状態と一致したファイルだけを削除するようにしました。コピー後に書き換えられた元のファイルを、転送しないまま消しません。 +- 新しくアップロードしたファイルの公開は、検証後に別のクライアントが同名のファイルを作っていた場合、上書きせず競合として失敗します。 +- 2分を超えるディレクトリ走査を伴うengine側のジョブが「中断」扱いにされていた問題を直しました。 +- 転送一覧が古い応答で上書きされ、開始直後のジョブが待機中に戻ってしまう問題を直しました。 +- Terminalからのリンクで開いたファイルのDownload/Editが、ペインがengine側のディスクを表示中は無反応だった問題を直しました。 +- テキストエディターは保存中に編集できないようにし、保存中の入力が保存結果で消えないようにしました。 +- 128文字を超えるパスの削除と権限変更がブラウザーからエラーになっていた問題を直しました。 +- 転送キューの保存ファイルは、シンボリックリンクを辿らず、上限付きで読み込み、一時ファイル経由で書き込むようにしました。壊れたキューは別名で残します。 + +## Terminal + +- 再接続したセッションを、セッション開始時ではなく**ブラウザーの最新のサイズ**で開くようにしました。再接続を待っている間にウィンドウやサイドバーの幅を変えた場合も反映します。これまでは再接続後の行数・列数が表示とずれ、リモートのプログラムが見えない行へ描くことがありました。 +- 再接続の握手中に「再接続を止める」を押すと、接続に成功しても入力が届かず手動の再接続もできなくなる問題を直しました。 +- サーバーが応答しない場合の検出を直しました。keepaliveの応答待ちに期限がなく、`ServerAliveCountMax`が効いていませんでしたが、OpenSSHと同じく間隔内に応答がなければ1回の失敗として数えます。 +- ポート転送で、書き終えてから応答を待つクライアントの応答が落ちることがあった問題を直しました。ForwardAgentのソケットが接続ごとに残っていた問題も直しています。 +- kitty形式(OSC 99)の分割された通知を上限なく連結していたため、悪意のあるサーバーの出力でengineのメモリが増え続ける可能性がありました。配信する上限で打ち切ります。 +- keyboard-interactive認証の表示文からは制御文字を取り除きます。 +- Terminal設定の保存時に、範囲外の値(verbosity、再接続回数、背景の濃さ、配色など)を拒否するようにしました。これまでは保存されてしまい、以後Settingsとショートカットの同期が読み込みに失敗していました。列数・行数は1〜1000の範囲で受け付けます。 + +## 削除前の確認と画面の統一 + +- Vaultの資格情報(専用鍵のパスフレーズの解除を含む)、スニペット、保存したWorkspaceレイアウトは、これまで確認なしに削除していましたが、他の削除と同じ確認ダイアログを出すようにしました。キーボードショートカットのプリセットの確認も同じダイアログになります。 +- 保存したWorkspaceレイアウトの削除に失敗した場合、これまでは何も表示していませんでしたが、他の操作と同じく画面にエラーを出します。 +- 英語UIで同じ画面に「console」と「session」が混在していたのを、日本語と同じく開いているものは「session」、画面や外観は「terminal」に統一しました。 +- パネル直下のエラー表示の色と余白を統一しました。 +- ロック画面の言語選択は、Menuと同じ表記を使います。 + +## Sync + +- 自動同期の失敗理由と、手動のPull/Pushの失敗理由が、同じ原因でも別の名前で表示されることがあった問題を直しました。 +- Vaultが存在しない状態でSyncの設定を変更した場合に、サーバーエラーではなくVaultがロック中のときと同じ扱いにします。 + +## ドキュメント + +- 接続時に自動実行するスニペットの説明を実装に揃え、複数ホストでの実行を「コマンド」ページに書きました。 +- pagesとdocsの記述を実装に合わせて直し、書かれていなかった機能を追加しました。 + +## 内部の変更 + +- Web(非同期操作の状態、ブラウザーの保存、定期取得、JSONを送るAPI呼び出し、サイズと日時の整形、ホスト検索)とengine(remotesyncの失敗分類、CLIのengine状態取得、systemd/launchdのservice管理、SFTPエントリの変換)で、同じ処理の重複を1つの実装にまとめました。動作は変えていません。 +- APIの契約(`api/openapi.yaml`)と実装の食い違いを揃えました。確認トークンが必要な操作に`X-SSHC-Action`ヘッダーを宣言し、aliasの上限を64文字に統一しています。 + +## 更新後 + +Webではバイナリ更新後に稼働中のengineを再起動し、`sshc`(または`sshc open`)のURLからブラウザーを登録し直してください。Androidでは正式版APKを更新してください。 + +**変更履歴**: [v0.35.0...v0.36.0](https://github.com/aida0710/sshc/compare/v0.35.0...v0.36.0) diff --git a/install.sh b/install.sh index 3ed3d1cdf..dbcd943ab 100755 --- a/install.sh +++ b/install.sh @@ -1,8 +1,8 @@ #!/bin/sh # sshc の CLI を入れる。 # -# SSHC_VERSION=v0.35.0 sh -c \ -# 'curl -fsSL https://raw.githubusercontent.com/aida0710/sshc/v0.35.0/install.sh | sh' +# SSHC_VERSION=v0.36.0 sh -c \ +# 'curl -fsSL https://raw.githubusercontent.com/aida0710/sshc/v0.36.0/install.sh | sh' # # 配布物は UI を埋め込んだ単一の CLI バイナリである。 # インストール前の検証内容と変更内容は標準出力へ表示する: diff --git a/internal/buildcontract/install_script_test.go b/internal/buildcontract/install_script_test.go index a67553f36..586f3785c 100644 --- a/internal/buildcontract/install_script_test.go +++ b/internal/buildcontract/install_script_test.go @@ -218,7 +218,7 @@ func TestTheDocumentedInstallerPinsScriptAndArtifactsToOneRelease(t *testing.T) if strings.Contains(text, "raw.githubusercontent.com/aida0710/sshc/main/install.sh") { t.Errorf("%s still executes the mutable main installer", path) } - for _, required := range []string{"SSHC_VERSION=v0.35.0", "/sshc/v0.35.0/install.sh"} { + for _, required := range []string{"SSHC_VERSION=v0.36.0", "/sshc/v0.36.0/install.sh"} { if !strings.Contains(text, required) { t.Errorf("%s lacks version-pinned installer fragment %q", path, required) } diff --git a/pages/en/guide/install.md b/pages/en/guide/install.md index 29847b382..9105a93ab 100644 --- a/pages/en/guide/install.md +++ b/pages/en/guide/install.md @@ -15,11 +15,11 @@ sshc is a terminal app for macOS, Linux, Windows, and Android. On desktop, one ` brew install aida0710/tap/sshc ``` -Without Homebrew, pin both the installer URL and the binary version to the same release. This example installs `v0.35.0`. +Without Homebrew, pin both the installer URL and the binary version to the same release. This example installs `v0.36.0`. ```sh -SSHC_VERSION=v0.35.0 sh -c \ - 'curl -fsSL https://raw.githubusercontent.com/aida0710/sshc/v0.35.0/install.sh | sh' +SSHC_VERSION=v0.36.0 sh -c \ + 'curl -fsSL https://raw.githubusercontent.com/aida0710/sshc/v0.36.0/install.sh | sh' ``` After installation, `sshc update` delegates upgrades to Homebrew or to a receipt-aware installer. It shows the planned change and asks for confirmation. In non-interactive automation, review the plan and use `sshc update --yes`. diff --git a/pages/guide/install.md b/pages/guide/install.md index 181e9a364..7839407c7 100644 --- a/pages/guide/install.md +++ b/pages/guide/install.md @@ -15,11 +15,11 @@ sshcはmacOS、Linux、Windows、Androidで利用できるターミナルアプ brew install aida0710/tap/sshc ``` -Homebrewを使わない場合は、インストーラーとバイナリのバージョンを同じReleaseタグに固定してください。次は`v0.35.0`を導入する例です。 +Homebrewを使わない場合は、インストーラーとバイナリのバージョンを同じReleaseタグに固定してください。次は`v0.36.0`を導入する例です。 ```sh -SSHC_VERSION=v0.35.0 sh -c \ - 'curl -fsSL https://raw.githubusercontent.com/aida0710/sshc/v0.35.0/install.sh | sh' +SSHC_VERSION=v0.36.0 sh -c \ + 'curl -fsSL https://raw.githubusercontent.com/aida0710/sshc/v0.36.0/install.sh | sh' ``` 導入後は`sshc update`で更新できます。Homebrewで入れた場合はHomebrewから、`install.sh`で入れた場合は同じ配布元から更新されます。変更内容を事前に確認済みの自動処理では、`sshc update --yes`を指定して対話確認を省略できます。