Skip to content

Commit b33cf4f

Browse files
takemi-ohamaclaude
andcommitted
fix(env): recipient と passphrase 同時指定を拒否 + docs 表現修正
- io_export._encrypt_payload で passphrase ありかつ opts.recipients 指定時に ExportError を送出。従来は recipients=[] に上書きされて cipher.encrypt 側の同時指定チェックを silently バイパスし、ユーザ が明示した --recipient が無視されていた (gemini round 6 指摘)。 - docs/user/env-export-import.md L201 の制約説明を修正。round 2 で export 側の DEST='-' x --passphrase-stdin 排他チェックを撤廃した ため、SOURCE='-' (import) のみ併用不可、export は併用可能であること を明記 (codex round 6 指摘)。 - recipient + passphrase 併用拒否の回帰テストを追加。 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent dfa0113 commit b33cf4f

3 files changed

Lines changed: 27 additions & 1 deletion

File tree

docs/user/env-export-import.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,7 @@ devbase env export - --force-unencrypted | gpg --encrypt -r alice@example.com >
198198
gpg --decrypt bundle.gpg | devbase env import -
199199
```
200200

201-
> **制約**: `DEST='-'` / `SOURCE='-'` と `--passphrase-stdin` は **併用不可** (stdin/stdout が衝突するため明示的にエラーにします)
201+
> **制約**: `SOURCE='-'` と `--passphrase-stdin` は **併用不可** (どちらも stdin を要求するため衝突します。import 側のみエラーになります)。`DEST='-'` (export) は stdin (passphrase) と stdout (bundle) で別ストリームを使うため `--passphrase-stdin` と併用できます
202202

203203
## `devbase env export` リファレンス
204204

lib/devbase/env/io_export.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,13 @@ def _validate_options(opts: ExportOptions) -> None:
8686
def _encrypt_payload(tar_blob: bytes, opts: ExportOptions) -> bytes:
8787
"""``opts`` の鍵指定に従って tar.gz を暗号化する。鍵が無ければ既定鍵を試す"""
8888
passphrase = _read_passphrase(opts)
89+
if passphrase is not None and opts.recipients:
90+
# 明示指定の --recipient と --passphrase-* を黙って捨てると意図と異なる
91+
# 暗号化が行われるため、ここで明示的にエラーにする
92+
# (cipher.encrypt 側にも同等チェックがあるが、recipients=[] に上書きする前に弾く)
93+
raise ExportError(
94+
"--recipient と --passphrase-env/--passphrase-stdin は併用できません"
95+
)
8996
recipients = (
9097
[] if passphrase is not None
9198
else _io_common.resolve_recipient_specs(opts.recipients)

tests/cli/test_env_export.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,25 @@ def test_export_rejects_both_passphrase_env_and_stdin(fake_root):
110110
dest="/dev/null", passphrase_env="X", passphrase_stdin=True))
111111

112112

113+
def test_export_rejects_recipient_and_passphrase_combo(
114+
fake_root, age_keys, tmp_path, monkeypatch
115+
):
116+
"""--recipient と --passphrase-* を同時指定したら ExportError を上げる。
117+
黙って recipients=[] に上書きしてパスフレーズだけで暗号化するのは
118+
ユーザの意図と異なるため明示的に拒否する (cipher.encrypt 側のチェックに
119+
到達する前にここで弾く)。"""
120+
pub_file, _ = age_keys
121+
monkeypatch.setenv("DEVBASE_TEST_PASS", "s3cr3t")
122+
dest = tmp_path / "out.dbenv"
123+
with pytest.raises(ExportError, match="--recipient"):
124+
export(fake_root, ExportOptions(
125+
dest=str(dest),
126+
recipients=[f"@{pub_file}"],
127+
passphrase_env="DEVBASE_TEST_PASS",
128+
))
129+
assert not dest.exists()
130+
131+
113132
def test_read_passphrase_uses_getpass_on_tty(monkeypatch):
114133
"""tty 入力時は getpass.getpass を使い stdin.readline は呼ばない (エコー抑止)"""
115134
fake_stdin = io.StringIO("should-not-be-read\n")

0 commit comments

Comments
 (0)