Skip to content

fix: known_hosts ワイルドカード行での MITM 誤警告を修正 + 依存の定期更新 - #25

Merged
kwrkb merged 4 commits into
masterfrom
chore/maintenance-2026-09
Sep 18, 2026
Merged

kwrkb merged 4 commits into
masterfrom
chore/maintenance-2026-09

Conversation

@kwrkb

@kwrkb kwrkb commented Sep 17, 2026 •

Copy link
Copy Markdown
Owner

定期メンテナンスと、その過程で見つかったバグ 2 件の修正。

本命は 2. のホスト鍵検証バグです。依存更新は定期更新にとどまります(脆弱性対応は既に master に入っています)。

1. 依存更新(定期)

  • golang.org/x/crypto v0.56.0 → v0.57.0、golang.org/x/term v0.45.0 → v0.46.0(間接依存の x/sys が v0.48.0 へ)
  • 現行リリースへの定期更新。セキュリティ修正ではない — GO-2026-6354 / GO-2026-6355 は master の 5e71f7d(v0.56.0)で既に解消済みで、v0.56.0 に該当する advisory は無い
  • go ディレクティブは Termux 互換のため 1.26.0 のまま、toolchain go1.26.6 も据え置き
  • Go 1.27.1(ローカル)と go1.26.6(CI と同じ)の両方で govulncheck が到達可能な脆弱性 0 件

2. known_hosts のワイルドカード行で MITM 誤警告(実バグ)

hostKeyAlgorithmsFromKnownHosts がホスト行を完全一致でしか照合しておらず、192.168.1.* や *.example.com のようなパターン行は「未登録」扱いになってネゴシエーション制限が nil になっていた。一方 knownhosts コールバックはパターンを解釈するため、複数種別のホスト鍵を提供するサーバ(Windows OpenSSH は rsa / ecdsa / ed25519)に対して known_hosts に無い種別が選ばれ、何も変わっていないホストに REMOTE HOST IDENTIFICATION HAS CHANGED! が出ていた。

  • 読み取り側の照合を OpenSSH 準拠にした(* / ? / カンマ区切り / ! 否定)
  • @cert-authority / @revoked 行はスキップする。前者が持つのは CA 鍵の種別でホスト鍵の種別ではなく、制限リストに混ぜると証明書ベースのホストへの接続を壊す
  • 削除経路(replaceHostKeyInKnownHosts)の hostMatchesAddr は完全一致のまま据え置き。 ここでパターンを展開すると、鍵変更を承認したときにパターン行が丸ごと消え、同じパターンに覆われた他ホストの鍵まで巻き添えになる
  • 照合が広がる副作用として、パターン行が実サーバと別種別の鍵を指す構成では制限付きハンドシェイクが落ち、回復が shouldRetryWithoutHostKeyAlgorithms の再試行頼みになる。この判定は sentinel error が無く文字列一致なので、実ハンドシェイク由来のエラーで再試行が発火することをテストで固定した

回帰テストは 2 本とも、修正前のコードで実際に落ちることを確認済み(誤警告のほうは REMOTE HOST IDENTIFICATION HAS CHANGED を出して失敗する)。

3. looksLikeNonWindows の誤検知

Unix シェル名を出力全体への部分一致で探していたため、ssh: handshake failed… や Publish: … が "sh:" を含むだけで「Windows ではない」と判定されていた。判定が真のときは graceful degradation ではなく即中断するため、誤検知のコストが大きい。

  • シェル名は行頭一致に限定(markerValue / hasMarkerLine で既に採っている方針に合わせた)。加えて行頭が / の場合にかぎり絶対パスの basename でも照合する(下記 4. のレビュー対応で追加)
  • command not found のような十分に特徴的なメッセージ断片は部分一致のまま残した
  • env: は行頭一致の候補からも外した。PowerShell の $env:VAR 名前空間と衝突し、このツール自身が実行する Write-Output $env:SSH_CONNECTION の出力を非 Windows と誤判定し得る

検証

項目 結果
make check(build + vet + test + integration ビルド) パス
go test -race ./... パス
govulncheck ./...(Go 1.27.1 / go1.26.6 両方) 0 件
goreleaser check パス
make itest-admin / make itest-user 未実行

統合テストは実機 Windows ホストと実ターミナルが必要なため未実行。今回の 2 件はどちらも SSH クライアント側のロジックで、Go のテストサーバで実コードパスを exercise できている。ただし looksLikeNonWindows はリモート実行経路にあるため、実際の Windows 出力が新しい行頭リストに引っかからないことは実機実行でしか確認できない。マージ前に make itest-all を回すことを推奨。

なお .github/workflows/{ci,release}.yml のアクション版(checkout@v7 / setup-go@v7 / goreleaser-action@v7 / load-secrets-action@v5)と .goreleaser.yaml も点検したが、更新の必要はなかった。

4. レビュー対応(19b813d)

Codex レビューの P2 指摘 2 件に対応。いずれも修正前のコードで新規テストが落ちることを確認済み。

  • hostPatternMatch のホストパターン照合を大文字小文字非依存にした。 OpenSSH の match_hostname() はホスト名とパターンの両方を lowercase してから照合するため、バイト比較のままでは *.Example.COM が win.example.com に当たらず、!WIN.example.com が win.example.com を除外できなかった。どちらも 2. が直そうとしているワイルドカード誤警告をそのまま残す
    • 畳むのは hostPatternMatch の内部だけ。knownHostsLineMatchesAddr の addr は matchHashedHost にも渡っており、HMAC はバイト厳密なので呼び出し側で正規化すると混合ケースのハッシュ化エントリが引けなくなる
    • 削除経路の hostMatchesAddr は完全一致のまま据え置き(意図的な非対称)。 あの関数が走るのは x/crypto/ssh/knownhosts のコールバックが「鍵が変わった」と判定した後だけで、同パッケージには大小文字を畳む処理が一切無い(Normalize() も lowercase しない)。ここだけ広げると、コールバックが一致させていない行まで削除対象になる
  • looksLikeNonWindows が絶対パスで名乗るシェルを取りこぼしていた。 dash の /bin/sh: 1: powershell: not found は message 断片のどれにも一致せず(not found 単体は断片に無い)、行頭も sh: ではないため false を返し、resolveKeyFileTarget が「回復可能な admin 判定失敗」とみなして Windows 配置経路へ進んでいた
    • 行頭が / の場合にかぎり最初の : までを basename に落として照合する。/ 始まりに限定することで、ssh: handshake failed のような行を巻き込まない 3. の誤検知耐性は保つ
    • 削除済みの powershell: not found 断片の復活ではなく basename 一致を採ったのは、/bin/dash / /usr/bin/bash など他のシェルにも同じ形で効くため

make check / go test -race ./... は 19b813d でも再実行してパス。統合テストは引き続き未実行(上表のとおり)。

🤖 Generated with Claude Code

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 17, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-09-18T09:21:12.460454Z ec70d0f New commits
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d498e61517

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread ssh.go Outdated
star, starMatch := -1, 0
for ai < len(addr) {
switch {
case pi < len(pattern) && (pattern[pi] == '?' || pattern[pi] == addr[ai]):

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Fold case when matching host patterns

OpenSSH matches known-host hostname patterns case-insensitively, but this byte comparison is case-sensitive. For example, *.Example.COM does not match win.example.com, and *.example.com,!WIN.example.com incorrectly applies to win.example.com. Consequently hostKeyAlgorithmsFromKnownHosts can ignore or misapply valid pattern entries, leaving the wildcard false-warning/TOFU behavior unresolved for mixed-case entries; normalize both operands to a common ASCII case before wildcard matching.

Useful? React with 👍 / 👎.

kwrkb and others added 2 commits September 18, 2026 06:47
x/crypto は直前の v0.56.0(到達可能な DoS 2 件を解消済み)から、現行リリースの
v0.57.0 への定期更新。v0.56.0 に該当する advisory は無く、セキュリティ修正ではない。
x/term も v0.46.0 へ揃え、間接依存の x/sys が v0.48.0 に上がる。

Go 1.27.1(ローカル)と toolchain go1.26.6(CI)の両方で govulncheck が
到達可能な脆弱性 0 件。go ディレクティブは Termux 互換のため 1.26.0 のまま。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
hostKeyAlgorithmsFromKnownHosts はホスト行を完全一致でしか照合しておらず、
`192.168.1.*` のようなパターン行は「未登録」と判定されてネゴシエーション制限が
nil になっていた。一方 knownhosts コールバックはパターンを解釈するため、サーバが
複数種別のホスト鍵を提供する場合(Windows OpenSSH は rsa/ecdsa/ed25519)に
known_hosts に無い種別が選ばれ、何も変わっていないホストに対して
REMOTE HOST IDENTIFICATION HAS CHANGED を誤表示していた。

読み取り用の照合を OpenSSH 準拠にする(`*` / `?` / カンマ列 / `!` 否定)。
`@cert-authority` / `@revoked` 行は対象外にする(前者の種別は CA 鍵のもので、
制限リストに混ぜると証明書ホストへの接続を壊す)。

削除経路(replaceHostKeyInKnownHosts)の hostMatchesAddr は完全一致のまま
据え置く。ここでパターンを展開すると、鍵変更を承認したときに 1 行が丸ごと消えて
同じパターンに覆われた他ホストの鍵まで巻き添えになる。

照合が広がる副作用として、パターン行が実サーバと別種別の鍵を指す構成では
制限付きハンドシェイクが落ち、回復が shouldRetryWithoutHostKeyAlgorithms の
再試行頼みになる。実ハンドシェイク由来のエラーで再試行が発火することをテストで
固定した。誤警告の回帰テストとあわせ、いずれも修正前のコードで落ちることを確認済み。

あわせて looksLikeNonWindows のシェル名判定を行頭一致に変更する。部分一致では
`ssh: …` や `Publish: …` が "sh:" を含むだけで非 Windows と誤判定され、
graceful degradation ではなく即中断していた。`env:` は PowerShell の `$env:`
名前空間と衝突するため行頭一致の候補からも外す。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@kwrkb
kwrkb force-pushed the chore/maintenance-2026-09 branch from d498e61 to 40abddd Compare September 18, 2026 09:06
@kwrkb kwrkb changed the title chore: 依存更新(脆弱性 2 件解消)と known_hosts ワイルドカード誤警告の修正 fix: known_hosts ワイルドカード行での MITM 誤警告を修正 + 依存の定期更新 Sep 18, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 40abddd0ec

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread deploy.go
Comment on lines +207 to +210
var nonWindowsShellPrefixes = []string{
"bash:", "sh:", "zsh:", "ksh:", "csh:", "tcsh:", "dash:", "ash:", "fish:",
"powershell:",
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Recognize absolute-path shell prefixes

When the remote Unix login shell identifies itself by an absolute path, this prefix list no longer recognizes the failure. For example, /bin/sh -c powershell emits /bin/sh: 1: powershell: not found; it contains none of the message signatures and does not start with sh:, so looksLikeNonWindows returns false. resolveKeyFileTarget then treats this as a recoverable admin-check failure and proceeds down the Windows deployment path instead of reporting that the target is unsupported. Preserve the false-positive fix while accepting basename prefixes such as /bin/sh: (or specifically retaining the removed powershell: not found signature).

Useful? React with 👍 / 👎.

kwrkb and others added 2 commits September 18, 2026 18:15
Codex レビューの P2 指摘 2 件。

- hostPatternMatch のホストパターン照合を大文字小文字非依存にする。
  OpenSSH の match_hostname() はホスト名とパターンの両方を lowercase して
  から照合するため、バイト比較のままでは `*.Example.COM` が
  win.example.com に当たらず、`!WIN.example.com` が win.example.com を
  除外できなかった。どちらも本 PR が直そうとしているワイルドカード誤警告を
  そのまま残す。畳むのは hostPatternMatch の内部だけに閉じる。
  knownHostsLineMatchesAddr の addr は matchHashedHost にも渡っており、
  HMAC はバイト厳密なので呼び出し側で正規化すると混合ケースの
  ハッシュ化エントリが引けなくなる。
  削除経路の hostMatchesAddr も完全一致のまま据え置く。あの関数が走るのは
  x/crypto/ssh/knownhosts のコールバックが「鍵が変わった」と判定した後だけで、
  同パッケージには大小文字を畳む処理が無い(Normalize も lowercase しない)。
  ここだけ広げるとコールバックが一致させていない行まで削除対象になる。

- looksLikeNonWindows が絶対パスで名乗るシェルを取りこぼしていた。
  dash の `/bin/sh: 1: powershell: not found` は message 断片のどれにも
  一致せず、行頭も `sh:` ではないため false を返し、resolveKeyFileTarget が
  「回復可能な admin 判定失敗」とみなして Windows 配置経路へ進んでいた。
  行頭が `/` の場合にかぎり最初の `:` までを basename に落として照合する。
  `/` 始まりに限定することで、"ssh: handshake failed" のような行を
  巻き込まない既存の誤検知耐性は保つ。

いずれも修正前のコードで新規テストが落ちることを確認済み。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SDRW423QvvZ1t3ESWPezBn
@kwrkb
kwrkb merged commit 42093fc into master Sep 18, 2026
1 check passed
@kwrkb
kwrkb deleted the chore/maintenance-2026-09 branch September 18, 2026 12:11
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