feat(env): PLAN03-1 PR1 devbase env export (Local + Stdio)#14
Conversation
- lib/devbase/env/bundle.py: tar.gz + manifest.yml バンドル構築/展開、sha256 検証、未知 version 拒否、パストラバーサル拒否 - lib/devbase/env/cipher.py: pyrage 経由の age 暗号化/復号 (X25519 / OpenSSH ed25519,rsa / passphrase / @path 参照) - lib/devbase/env/storage.py: Local + Stdio backend、s3/gs は本 PR では未実装で明示エラー - lib/devbase/env/io_export.py: 機密キー検知警告、既定鍵 (~/.ssh/id_rsa.pub) 自動利用、--passphrase-stdin と DEST='-' 併用拒否 - cli.py / commands/env.py: env export サブコマンド登録 + SUBCMD_MAP 更新 - pyproject.toml: pyrage>=1.2 を deps、pytest>=8.0 を dev group、tool.pytest.ini_options 追加 - tests/env, tests/cli: ラウンドトリップ + 異常系 28 件 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
takemi-ohama
left a comment
There was a problem hiding this comment.
🤖 cross-review | round 1 | codex | REQUEST_CHANGES
file:// パス処理の不整合と、manifest 検証時の例外ハンドリング欠落を優先修正してください。
takemi-ohama
left a comment
There was a problem hiding this comment.
🤖 cross-review | round 1 | gemini | REQUEST_CHANGES
env export の実装について、1点だけ堅牢性向上のための修正提案をインラインコメントしました。
秘密鍵ファイルの形式判定処理に関するものです。ご確認をお願いします。
- storage.py: LocalBackend で file:// URI を url2pathname で実パスへ変換 - bundle.py: manifest.files の要素型 (dict, path: str, sha256: str) を検証 - cipher.py: age 秘密鍵判定をバイト列で行い、UTF-8 デコード失敗を明示エラー化 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
レビュー対応サマリ (round 1/5)codex review 2件 + gemini review 1件、合計 3件 すべて対応しました (commit f406c8c)。 対応内容
テスト
deferred / rejected はありません。 |
takemi-ohama
left a comment
There was a problem hiding this comment.
🤖 cross-review | round 2 | codex | REQUEST_CHANGES
lib/devbase/env/storage.py の file://<host>/... 解釈と、lib/devbase/env/bundle.py の tar 重複エントリ未検知は、入力によって誤動作または曖昧な復元を招くため、先に塞ぐのが安全です。
takemi-ohama
left a comment
There was a problem hiding this comment.
🤖 cross-review | round 2 | gemini | REQUEST_CHANGES
This PR introduces new features for environment management, including bundling, storage abstraction, and encryption. The implementation is solid and the previous review comments have been addressed correctly.
I have a few minor suggestions for improving test coverage and robustness against edge cases. Please see the inline comments.
- storage: file:// URI の netloc が空/localhost 以外なら StorageError で拒否 (codex major) - bundle: tar 内の重複エントリを BundleError で検出 (codex major) - cipher: _resolve_recipient の @path 再帰に深さ制限 (上限 5) を追加 (gemini minor) - tests/storage: file:// URI roundtrip と remote host 拒否の test を追加 (gemini minor) - tests/bundle: _validate_manifest 不正系 (files が list でない / entry が dict でない / path 不正 / sha256 不正) + 重複エントリの test を追加 (gemini minor) - tests/cipher: @path 循環参照で CipherError を返す test を追加 (gemini minor)
/ndf:fix round 2 サマリレビュー指摘 5 件すべてを修正しました (commit 8baca0c)。 修正内訳
テスト
deferred / rejected: 0 件 |
takemi-ohama
left a comment
There was a problem hiding this comment.
🤖 cross-review | round 3 | codex | REQUEST_CHANGES
manifest の完全性検証をバイパスできる経路が 1 件あるため、sha256 を必須検証にしてください。
takemi-ohama
left a comment
There was a problem hiding this comment.
🤖 cross-review | round 3 | gemini | APPROVE
This PR is well-implemented and addresses all previous feedback with corresponding tests. I have one minor suggestion to improve default key discovery, but it doesn't block approval.
- bundle.py: manifest.files[*].sha256 を必須の 64 文字 16 進文字列として検証 None / 欠落 / 長さ違い / 非 16 進は BundleError。完全性チェック迂回を防止 - cipher.py: default_recipient_paths / default_identity_paths に ed25519 (id_ed25519.pub / id_ed25519) を追加し、rsa より優先 - tests: sha256 欠落 / None / 長さ違い / 非 16 進の異常系テストを追加 - tests: ed25519 がデフォルトパス候補に含まれ rsa より優先されることを検証 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Round 3 修正サマリround 3 の 2 件のレビュー指摘に対応しました (commit: cbb98ca)。 修正内容
テスト
追加テスト:
両スレッドとも resolve 済みです。再レビューをお願いします。 |
takemi-ohama
left a comment
There was a problem hiding this comment.
🤖 cross-review | round 4 | codex | REQUEST_CHANGES
例外系で DevbaseError 派生に正規化されていない経路が残っているため、失敗時の挙動を一貫させる修正を提案します。
takemi-ohama
left a comment
There was a problem hiding this comment.
🤖 cross-review | round 4 | gemini | REQUEST_CHANGES
Windows 環境での移植性に関する軽微な問題を1点インラインコメントしました。ご確認をお願いします。他は問題ないため、これの対応をもって承認とさせてください。
- bundle: yaml.safe_load の結果が dict でない場合に BundleError を送出 (top-level が list/str/数値の場合に AttributeError が漏れるのを防止) - cipher: @path 参照ファイルが UTF-8 でない場合 CipherError に包んで再送出 (UnicodeDecodeError が呼び出し側に漏れていた) - storage.resolve: Windows ドライブレター (C:\path 等) を urlparse が scheme と誤認する問題に対応し LocalBackend にフォールバック 各修正に対応する異常系 test を追加 (合計 +5 test)。
Round 4 修正サマリ (commit 2c09ae7)codex 2 件 (major) + gemini 1 件 (minor) の計 3 件すべてを修正し、各々異常系 test を追加しました。
追加した test (+5)
test 結果deferred / rejected: なし。次ラウンドのレビューをお願いします。 |
takemi-ohama
left a comment
There was a problem hiding this comment.
🤖 cross-review | round 5 | codex | APPROVE
追加の修正アクションはありません。
takemi-ohama
left a comment
There was a problem hiding this comment.
🤖 cross-review | round 5 | gemini | COMMENT
REQUEST_CHANGES
堅牢性の観点で2点、改善提案があります。
-
bundle.pyのunpackにおける競合状態:extractfileはtarfile.TarInfoオブジェクトを引数に取りますが、これはアーカイブ内のメタデータのみを参照し、実際のファイル内容はtarfileオブジェクトから遅延して読み込まれます。現状の実装では、members辞書にファイル内容を読み込むループ (for info in tf.getmembers():) と、その後のmanifest検証が分離しています。もし巧妙に細工された tar アーカイブ(例えば、manifest.ymlの後に同じ名前で異なる内容のファイルが再度含まれるなど)を与えられた場合、getmembers()が返すリストの順序によっては、意図しないmanifest.ymlを使って検証し、その後の処理で別の内容が使われるという競合状態が発生する可能性があります。tf.extractfile(info)で取得したファイルオブジェクトをすぐにread()して内容を確定させることで、この曖昧さを排除できます。 -
cipher.pyの_resolve_identityにおけるファイルハンドルリーク:pyrage.ssh.Identity.from_buffer(raw)が例外を送出した場合、raw = path.read_bytes()で開かれたファイルハンドルが適切にクローズされない可能性があります。read_bytesは内部でopenとcloseを行いますが、プロセスが予期せず終了した場合などにリソースリークの懸念が残ります。with open(path, "rb") as f:を使って明示的にファイルコンテキストを管理することで、例外発生時でも確実にファイルがクローズされることを保証でき、より堅牢な実装になります。
- lib/devbase/env/cipher.py: path.read_bytes() を try/except OSError で ラップし、I/O エラー時も CipherError で統一されたエラー型を返す - tests/env/test_cipher.py: monkeypatch で read_bytes に OSError を 発生させて CipherError に包まれることを検証する test を追加 gemini round 5 指摘 (minor / 堅牢性) に対応。 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Round 5 修正 Summary (最終ラウンド)commit: 776cb8d codexAPPROVE — 追加修正なし。 gemini (2件)
テスト
変更ファイル
これで /ndf:fix の 5 ラウンドが完了しました。 |
takemi-ohama
left a comment
There was a problem hiding this comment.
🤖 cross-review | round 6 | codex | REQUEST_CHANGES
gzip ヘッダ時刻により「再現性あり」という設計意図と実装が不一致です。加えて recipient 読み込み時の I/O 例外が CipherError に正規化されておらず、エラーハンドリング方針が揺れているため、2 点の修正を提案します。
takemi-ohama
left a comment
There was a problem hiding this comment.
🤖 cross-review | round 6 | gemini | REQUEST_CHANGES
バンドルの検証処理と、秘密鍵の形式判定ロジックについて、堅牢性・拡張性を向上させるための2点を提案します。
takemi-ohama
left a comment
There was a problem hiding this comment.
🤖 cross-review | round 5 | gemini | APPROVE
1点の軽微な堅牢性に関する修正提案をインラインコメントとしていますが、全体として大きな問題は見受けられませんでした。以前のレビューでの指摘事項は適切に修正されています。
- bundle.pack: gzip.GzipFile(mtime=0) でラップし出力を完全に決定的にする - bundle._validate_manifest: tar 内ファイルセットと manifest の完全一致を 検証し、manifest に記載のない未知ファイルを BundleError で拒否する - cipher._resolve_recipient: @path の read_text で発生する OSError を CipherError に包んで一貫したエラーハンドリングにする - cipher._resolve_identity: OpenSSH ヘッダで先に SSH 鍵を判別する分岐を 追加し、鍵形式判別を明示化 (将来の形式追加もしやすくする) - tests: pack 決定性 / unknown file 拒否 / @path OSError ラップ / OpenSSH ヘッダ優先判別の test を追加
recipient ファイルにコメント (# 始まり) や空行が混在していても扱えるよう、 有効な最初の行のみを採用する。テストも追加。
Round 6 (final) 対応サマリレビュー指摘 6 件にすべて対応しました (commits d357e86, c07b1a7)。 修正内容
テスト
deferred / rejected
これで round 6 (最終ラウンド) の対応を完了します。再レビューをお願いします。 |
takemi-ohama
left a comment
There was a problem hiding this comment.
🤖 cross-review | round 1 | gemini | REQUEST_CHANGES
ストレージ操作時のパーミッション管理や、tar 展開・ファイル収集時の例外処理にいくつかの堅牢性の課題が見つかりました。特に暗号化された機密情報を扱う性質上、作成時のパーミッション競合(race condition)の防止は重要です。
takemi-ohama
left a comment
There was a problem hiding this comment.
🤖 cross-review | round 1 | codex | REQUEST_CHANGES
インラインコメントの修正項目を確認してください。
…mpletion) - storage.py: LocalBackend.write_bytes を os.open(mode=0o600, O_CREAT|O_TRUNC|O_WRONLY) で 作成時点から 0600 を強制し、umask に依らない TOCTOU 安全な書き込みに変更 (codex major / gemini minor — 同一指摘)。既存ファイル上書き時も先に chmod で権限を絞る。 read_bytes / write_bytes の OSError を StorageError にラップ (gemini minor)。 - bundle.py: unpack() の tarfile.open / getmembers / extractfile で発生する tarfile.TarError / OSError を BundleError にラップ (gemini major)。 make_entries_from_disk の exists() を is_file() に変更し、対象パスが ディレクトリだった場合の IsADirectoryError を防止 (gemini minor)。 _validate_manifest に manifest.files の path 重複検出を追加 (codex minor)。 - cli.py: SUBCMD_PREFIX_PREFERENCES を追加し、`devbase env e` が引き続き edit に 解決されるように prefix 解決の後方互換を維持 (codex minor)。 - etc/devbase-completion.bash, etc/_devbase: env export サブコマンドと 各オプションを補完に追加 (codex minor)。 - tests: storage の TOCTOU / OSError ラップ / 既存ファイル 0600 上書き、 bundle の path 重複 / 壊れた tar / is_file 切替、CLI prefix の後方互換テストを追加。 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
/ndf:fix サマリ (round 1)対応件数: critical=0 / major=3 / minor=5 / nit=0 (合計 8 件)
詳細
テスト: |
takemi-ohama
left a comment
There was a problem hiding this comment.
🤖 cross-review | round 2 | codex | APPROVE
修正必須の追加指摘はありません。
takemi-ohama
left a comment
There was a problem hiding this comment.
🤖 cross-review | round 2 | gemini | APPROVE
全体として env export の実装は堅牢で、機密情報の取り扱い(作成時点での 0o600 強制、デフォルト暗号化)やバンドルの再現性(mtime=0, 決定論的 gzip)など、セキュリティと移植性の両面でベストプラクティスに従っています。エラーハンドリングも一貫しており、完成度の高い実装です。
- io_export.py: `_resolve_recipients` の docstring を更新し、既定鍵が `id_ed25519.pub` → `id_rsa.pub` の優先順で探索される実態に合わせる - cli.py: `--recipient` の help を `Default: ~/.ssh/id_ed25519.pub, then ~/.ssh/id_rsa.pub (first existing one)` に修正 - io_export.py: `--passphrase-stdin` で `sys.stdin.isatty()` の場合に `passphrase: ` プロンプトを stderr に表示し、対話実行時のハング感を解消 - 暗号化キー未指定エラーメッセージも ed25519 優先を反映 - tests/cli/test_env_export.py: tty / 非 tty 双方の挙動を検証する 2 ケース追加 Refs: PR #14 review comments 3280597873 / 3280597877 / 3280597881
/ndf:fix サマリ (round 2 advisory followup)対応件数: minor=3 / nit=0 (合計 3 件) 詳細
テスト
3 件全 thread (#3280597873 / #3280597877 / #3280597881) を Resolve 済みです。 |
Summary
issues/PLAN03-1.mdlib/devbase/env/bundle.py— manifest 生成 / tar.gz パック / sha256 検証lib/devbase/env/cipher.py— age 暗号化 (pyrage: recipient / passphrase)lib/devbase/env/storage.py— Local + Stdio backendlib/devbase/env/io_export.py— export 高レベル実装commands/env.pyにexportハンドラ追加 +cli.pyのサブコマンド登録pyrageを依存に追加Test plan
bundle.pyラウンドトリップ (dict→bundle→dict 内容一致 + sha256 検証)cipher.pypassphrase / recipient ラウンドトリップ + 破損データのエラーstorage.pyLocal / Stdio ラウンドトリップtests/cli/test_env_export.py統合テスト (擬似 DEVBASE_ROOT でdevbase env exportを実行)--force-unencrypted未指定で平文 export が拒否されることDEST='-'と--passphrase-stdinの併用がエラーになること0600であること