Skip to content

Commit cf00209

Browse files
committed
fix(env): PR #13 round3 codex 指摘対応
- _import_merge.py の project 名正規表現を厳格化し、`./..`/隠しディレクトリを 弾く (path traversal: `env/projects/./.env` が `$DEVBASE_ROOT/projects/.env` に解決される問題への対策)。 - 上記の回帰テストを `tests/cli/test_env_import.py` に追加。 - zsh/bash 補完に `--unsafe-allow-unencrypted-bucket` (env export) を追加し CLI 定義と同期。
1 parent 8f8f5c3 commit cf00209

4 files changed

Lines changed: 60 additions & 3 deletions

File tree

etc/_devbase

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,8 @@ _devbase() {
162162
'*--recipient[age / OpenSSH public key (repeatable)]:key:' \
163163
'--passphrase-env[Read passphrase from env var]:var:' \
164164
'--passphrase-stdin[Read passphrase from stdin]' \
165-
'--force-unencrypted[Write as plaintext tar.gz]'
165+
'--force-unencrypted[Write as plaintext tar.gz]' \
166+
'--unsafe-allow-unencrypted-bucket[Allow uploading unencrypted tar.gz to S3 (off by default)]'
166167
;;
167168
import)
168169
_arguments \

etc/devbase-completion.bash

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ _devbase_completions() {
8383
;;
8484
export)
8585
if [[ "$cur" == -* ]]; then
86-
COMPREPLY=($(compgen -W "--include-project --exclude-project --no-global --no-metadata --recipient --passphrase-env --passphrase-stdin --force-unencrypted" -- "$cur"))
86+
COMPREPLY=($(compgen -W "--include-project --exclude-project --no-global --no-metadata --recipient --passphrase-env --passphrase-stdin --force-unencrypted --unsafe-allow-unencrypted-bucket" -- "$cur"))
8787
fi
8888
;;
8989
import)

lib/devbase/env/_import_merge.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,15 @@
2323

2424
logger = get_logger(__name__)
2525

26-
_PROJECT_ENV_RE = re.compile(r'^env/projects/([^/]+)/\.env$')
26+
# project 名は通常のディレクトリ名のみ許容する。
27+
# - 先頭文字: 英数字 / `_` (`.` を許可すると `env/projects/./.env` が
28+
# `$DEVBASE_ROOT/projects/.env` に正規化され、グローバル .env を上書きする
29+
# path traversal 系の問題になる — PR #13 codex round 3 指摘)
30+
# - 2文字目以降: 英数字 / `_` / `-` / `.`
31+
# - `.` / `..` のような特殊セグメント、空文字、`/` を含む値は弾く
32+
# bundle._validate_manifest や tar 展開側 (`..` のみ拒否) では塞ぎきれないため、
33+
# arcname を path に解決する側で project 名を制限する。
34+
_PROJECT_ENV_RE = re.compile(r'^env/projects/([A-Za-z0-9_][A-Za-z0-9_.\-]*)/\.env$')
2735

2836
# import_bundle が許容する --merge モード一覧。CLI の choices と一致させる。
2937
MERGE_MODES: Tuple[str, ...] = ('keep-existing', 'prefer-incoming')

tests/cli/test_env_import.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1006,3 +1006,51 @@ def test_env_import_filter_members_rejects_unknown_arcname():
10061006
include_projects=None,
10071007
exclude_projects=(),
10081008
)
1009+
1010+
1011+
@pytest.mark.parametrize(
1012+
"bad_arcname",
1013+
[
1014+
'env/projects/./.env', # `.` で global .env 領域に抜ける
1015+
'env/projects/../.env', # `..` での親ディレクトリ脱出
1016+
'env/projects/.hidden/.env', # 隠しディレクトリ名
1017+
'env/projects/ /.env', # 空白だけのプロジェクト名
1018+
],
1019+
)
1020+
def test_env_import_rejects_unsafe_project_names(bad_arcname):
1021+
"""``_PROJECT_ENV_RE`` が ``.`` / ``..`` / ``.hidden`` 等の特殊セグメントを
1022+
project 名として受け入れず、未対応 arcname として ``MergeError`` で止めること
1023+
(PR #13 codex round 3 指摘の path traversal 対策)。
1024+
1025+
特に ``env/projects/./.env`` は正規表現 ``[^/]+`` だと match してしまい、
1026+
``target_for`` で ``$DEVBASE_ROOT/projects/.env`` に正規化されてグローバル
1027+
``.env`` を上書きする経路が成立する。ここで明示的に拒否する。
1028+
"""
1029+
from devbase.env._import_merge import MergeError, filter_members, target_for
1030+
1031+
# filter_members 経路: 未対応 arcname として MergeError
1032+
members = {bad_arcname: b'PWNED=1\n'}
1033+
with pytest.raises(MergeError, match="未対応の arcname"):
1034+
filter_members(
1035+
members,
1036+
include_global=True,
1037+
include_metadata=True,
1038+
include_projects=None,
1039+
exclude_projects=(),
1040+
)
1041+
1042+
# target_for 経路: 直接呼ばれても MergeError
1043+
with pytest.raises(MergeError, match="未対応のバンドルエントリ"):
1044+
target_for(bad_arcname, Path('/tmp/fake-root'))
1045+
1046+
1047+
def test_env_import_accepts_normal_project_names():
1048+
"""正常な project 名 (英数字 / `_` / `-` / `.` を含む) は受け入れること。
1049+
上記の安全性チェックで実用ケースを壊していないことの回帰テスト。
1050+
"""
1051+
from devbase.env._import_merge import target_for
1052+
1053+
root = Path('/tmp/fake-root')
1054+
for name in ['alpha', 'beta_1', 'my-app', 'svc.v2', 'a']:
1055+
arc = f'env/projects/{name}/.env'
1056+
assert target_for(arc, root) == root / 'projects' / name / '.env'

0 commit comments

Comments
 (0)