@tyhld/conductor 0.12.71 → 0.12.72
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/headless.js +1 -0
- package/package.json +1 -1
- package/scripts/hooks/policy.py +29 -4
- package/scripts/hooks/test_policy.py +83 -0
package/dist/headless.js
CHANGED
|
@@ -427,6 +427,7 @@ export function spawnClaudeProcess(cfg, cmd, sessionId, attempt, isResume) {
|
|
|
427
427
|
delete env.ANTHROPIC_API_KEY;
|
|
428
428
|
env.CONDUCTOR_SITE = cfg.site;
|
|
429
429
|
env.CONDUCTOR_MACHINE = cfg.machine;
|
|
430
|
+
env.CONDUCTOR_WORK_DIR = cfg.workDir;
|
|
430
431
|
// ★テスト用: CONDUCTOR_CLAUDE_CONFIG_DIR を設定すると、claude の設定フォルダを差し替える。
|
|
431
432
|
// 本物のログインを壊さずに認証エラーを再現できる(空のフォルダを指す → not logged in)。
|
|
432
433
|
// 本番では設定しない(claude は既定の ~/.claude を使う)。
|
package/package.json
CHANGED
package/scripts/hooks/policy.py
CHANGED
|
@@ -2066,8 +2066,13 @@ def _prod_db_change_result(reason):
|
|
|
2066
2066
|
_SAFE_ABS_TOP = frozenset({'tmp'})
|
|
2067
2067
|
|
|
2068
2068
|
|
|
2069
|
-
def rm_path_risk(path: str) -> str:
|
|
2070
|
-
"""rm の対象パスの危険度: 'dangerous' / 'safe' / 'unknown'。
|
|
2069
|
+
def rm_path_risk(path: str, work_dir: str = None) -> str:
|
|
2070
|
+
"""rm の対象パスの危険度: 'dangerous' / 'safe' / 'unknown'。
|
|
2071
|
+
|
|
2072
|
+
work_dir が渡されたとき、絶対パスが work_dir の中を指すなら 'safe'(相対パスと同じ扱い)。
|
|
2073
|
+
ただし work_dir 自体・.git を含むパス・.. やシンボリックリンクで外に出るパスは 'dangerous'。
|
|
2074
|
+
work_dir が None のとき(非 conductor 環境)は従来どおり。
|
|
2075
|
+
"""
|
|
2071
2076
|
p = path.strip().strip('"').strip("'")
|
|
2072
2077
|
if p == '':
|
|
2073
2078
|
return 'unknown'
|
|
@@ -2086,14 +2091,34 @@ def rm_path_risk(path: str) -> str:
|
|
|
2086
2091
|
comps = [c for c in p.split('/') if c]
|
|
2087
2092
|
if not comps:
|
|
2088
2093
|
return 'dangerous'
|
|
2094
|
+
# ★workDir 内の絶対パスは相対パスと同じ扱い(便 24c9b6b6)。
|
|
2095
|
+
# workDir 自体・.git・.. やシンボリックリンクで外に出るパスは引き続き dangerous。
|
|
2096
|
+
# workDir を知れないときは従来どおり止める(Fail Closed)。
|
|
2097
|
+
# ★秘密(.env 等)と設定(.claude 等)は別の検査(_secret_danger / _guard_write_scan)が
|
|
2098
|
+
# 引き続き止めるので、ここでは .git だけを追加で守る。
|
|
2099
|
+
if work_dir:
|
|
2100
|
+
try:
|
|
2101
|
+
resolved = os.path.realpath(p)
|
|
2102
|
+
work_resolved = os.path.realpath(work_dir)
|
|
2103
|
+
if resolved == work_resolved:
|
|
2104
|
+
pass # workDir 自体 → fall through to 'dangerous'
|
|
2105
|
+
elif resolved.startswith(work_resolved + os.sep):
|
|
2106
|
+
rel_comps = resolved[len(work_resolved) + 1:].split(os.sep)
|
|
2107
|
+
if '.git' not in rel_comps:
|
|
2108
|
+
return 'safe'
|
|
2109
|
+
# .git を含む → fall through to 'dangerous'
|
|
2110
|
+
except (OSError, ValueError):
|
|
2111
|
+
pass # 解決できない → fall through(Fail Closed)
|
|
2089
2112
|
if comps[0] in _SAFE_ABS_TOP and len(comps) >= 2:
|
|
2090
2113
|
return 'safe'
|
|
2091
2114
|
return 'dangerous'
|
|
2092
2115
|
return 'safe'
|
|
2093
2116
|
|
|
2094
2117
|
|
|
2095
|
-
def _rm_danger(command: str):
|
|
2118
|
+
def _rm_danger(command: str, work_dir: str = None):
|
|
2096
2119
|
"""再帰的 rm の対象パスを全部見て、危険なものがあれば理由を返す。"""
|
|
2120
|
+
if work_dir is None:
|
|
2121
|
+
work_dir = os.environ.get('CONDUCTOR_WORK_DIR') or None
|
|
2097
2122
|
for m in re.finditer(r'\brm\b', command):
|
|
2098
2123
|
seg = _SPLIT_RE.split(command[m.end():], maxsplit=1)[0]
|
|
2099
2124
|
toks = seg.split()
|
|
@@ -2101,7 +2126,7 @@ def _rm_danger(command: str):
|
|
|
2101
2126
|
recursive = any(('r' in f.lstrip('-').lower()) or f in ('--recursive',) for f in flags)
|
|
2102
2127
|
paths = [t for t in toks if not t.startswith('-')]
|
|
2103
2128
|
for p in paths:
|
|
2104
|
-
risk = rm_path_risk(p)
|
|
2129
|
+
risk = rm_path_risk(p, work_dir=work_dir)
|
|
2105
2130
|
if risk == 'dangerous':
|
|
2106
2131
|
return f'rm on dangerous path ({p})'
|
|
2107
2132
|
if risk == 'unknown' and recursive:
|
|
@@ -1742,6 +1742,89 @@ try:
|
|
|
1742
1742
|
finally:
|
|
1743
1743
|
shutil.rmtree(_secret_dir, ignore_errors=True)
|
|
1744
1744
|
|
|
1745
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
1746
|
+
# workDir 内の rm 絶対パス判定(便 24c9b6b6)
|
|
1747
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
1748
|
+
print('\n=== workDir 内の rm 絶対パス: 作業ファイルの絶対パスは safe ===')
|
|
1749
|
+
_WD = '/home/goto/projects/goto-renshu'
|
|
1750
|
+
check('workDir 内の作業ファイル (絶対パス)',
|
|
1751
|
+
policy.rm_path_risk('/home/goto/projects/goto-renshu/goto-renshu-check.txt', _WD), 'safe')
|
|
1752
|
+
check('workDir 内の深いパス',
|
|
1753
|
+
policy.rm_path_risk('/home/goto/projects/goto-renshu/sub/dir/file.txt', _WD), 'safe')
|
|
1754
|
+
# git リポジトリでない作業フォルダでも通る
|
|
1755
|
+
_WD2 = '/home/user/projects/work'
|
|
1756
|
+
check('workDir 内の作業ファイル (別のユーザー)',
|
|
1757
|
+
policy.rm_path_risk('/home/user/projects/work/output.log', _WD2), 'safe')
|
|
1758
|
+
|
|
1759
|
+
print('\n=== workDir 内の rm 絶対パス: 引き続き止まるもの ===')
|
|
1760
|
+
# workDir 自体
|
|
1761
|
+
check('workDir 自体の削除',
|
|
1762
|
+
policy.rm_path_risk('/home/goto/projects/goto-renshu', _WD), 'dangerous')
|
|
1763
|
+
check('workDir/ (末尾にスラッシュ)',
|
|
1764
|
+
policy.rm_path_risk('/home/goto/projects/goto-renshu/', _WD), 'dangerous')
|
|
1765
|
+
# workDir の外
|
|
1766
|
+
check('workDir の外 (親)',
|
|
1767
|
+
policy.rm_path_risk('/home/goto/projects/other-repo/file.txt', _WD), 'dangerous')
|
|
1768
|
+
check('workDir の外 (/etc)',
|
|
1769
|
+
policy.rm_path_risk('/etc/passwd', _WD), 'dangerous')
|
|
1770
|
+
# .. で外に出るパス
|
|
1771
|
+
check('.. で workDir の外',
|
|
1772
|
+
policy.rm_path_risk('/home/goto/projects/goto-renshu/../other/file', _WD), 'dangerous')
|
|
1773
|
+
# .git
|
|
1774
|
+
check('workDir 内の .git',
|
|
1775
|
+
policy.rm_path_risk('/home/goto/projects/goto-renshu/.git', _WD), 'dangerous')
|
|
1776
|
+
check('workDir 内の .git/ 配下',
|
|
1777
|
+
policy.rm_path_risk('/home/goto/projects/goto-renshu/.git/config', _WD), 'dangerous')
|
|
1778
|
+
check('workDir 内の sub/.git',
|
|
1779
|
+
policy.rm_path_risk('/home/goto/projects/goto-renshu/sub/.git/HEAD', _WD), 'dangerous')
|
|
1780
|
+
# / ~ /home はそもそも dangerous(workDir に関係なく)
|
|
1781
|
+
check('/ (ルート)',
|
|
1782
|
+
policy.rm_path_risk('/', _WD), 'dangerous')
|
|
1783
|
+
check('~ (ホーム)',
|
|
1784
|
+
policy.rm_path_risk('~', _WD), 'dangerous')
|
|
1785
|
+
check('/home',
|
|
1786
|
+
policy.rm_path_risk('/home', _WD), 'dangerous')
|
|
1787
|
+
|
|
1788
|
+
# workDir を知れないとき → 従来どおり dangerous
|
|
1789
|
+
check('workDir なし → dangerous',
|
|
1790
|
+
policy.rm_path_risk('/home/goto/projects/goto-renshu/file.txt'), 'dangerous')
|
|
1791
|
+
check('workDir なし → dangerous (None)',
|
|
1792
|
+
policy.rm_path_risk('/home/goto/projects/goto-renshu/file.txt', None), 'dangerous')
|
|
1793
|
+
|
|
1794
|
+
print('\n=== workDir 内の rm: decide_bash 経由で秘密・設定は引き続き止まる ===')
|
|
1795
|
+
# ★ .env 等は _secret_danger が止める(rm_path_risk の変更とは別の検査)
|
|
1796
|
+
_wd_env = os.environ.get('CONDUCTOR_WORK_DIR')
|
|
1797
|
+
try:
|
|
1798
|
+
os.environ['CONDUCTOR_WORK_DIR'] = _WD
|
|
1799
|
+
check('rm .env (workDir 内・絶対パス) → deny',
|
|
1800
|
+
policy.decide_bash(f'rm {_WD}/.env')['decision'], policy.DENY)
|
|
1801
|
+
check('rm .dev.vars (workDir 内) → deny',
|
|
1802
|
+
policy.decide_bash(f'rm {_WD}/.dev.vars')['decision'], policy.DENY)
|
|
1803
|
+
check('rm .npmrc (workDir 内) → deny',
|
|
1804
|
+
policy.decide_bash(f'rm {_WD}/.npmrc')['decision'], policy.DENY)
|
|
1805
|
+
check('rm id_rsa (workDir 内) → deny',
|
|
1806
|
+
policy.decide_bash(f'rm {_WD}/id_rsa')['decision'], policy.DENY)
|
|
1807
|
+
check('rm server.key (workDir 内) → deny',
|
|
1808
|
+
policy.decide_bash(f'rm {_WD}/server.key')['decision'], policy.DENY)
|
|
1809
|
+
check('rm server.pem (workDir 内) → deny',
|
|
1810
|
+
policy.decide_bash(f'rm {_WD}/server.pem')['decision'], policy.DENY)
|
|
1811
|
+
# .git は rm_path_risk 自体が止める
|
|
1812
|
+
check('rm -rf .git (workDir 内) → deny',
|
|
1813
|
+
policy.decide_bash(f'rm -rf {_WD}/.git')['decision'], policy.DENY)
|
|
1814
|
+
# .claude/settings.json は _guard_write_scan が止める
|
|
1815
|
+
check('rm .claude/settings.json (workDir 内) → deny',
|
|
1816
|
+
policy.decide_bash(f'rm {_WD}/.claude/settings.json')['decision'], policy.DENY)
|
|
1817
|
+
# 作業ファイルは通る
|
|
1818
|
+
check('rm 作業ファイル (workDir 内・絶対パス) → allow',
|
|
1819
|
+
policy.decide_bash(f'rm {_WD}/goto-renshu-check.txt')['decision'], policy.ALLOW)
|
|
1820
|
+
check('rm 作業ファイル (workDir 内・相対パス) → allow',
|
|
1821
|
+
policy.decide_bash('rm goto-renshu-check.txt')['decision'], policy.ALLOW)
|
|
1822
|
+
finally:
|
|
1823
|
+
if _wd_env is None:
|
|
1824
|
+
os.environ.pop('CONDUCTOR_WORK_DIR', None)
|
|
1825
|
+
else:
|
|
1826
|
+
os.environ['CONDUCTOR_WORK_DIR'] = _wd_env
|
|
1827
|
+
|
|
1745
1828
|
# ─────────────────────────────────────────────────────────────────────────────
|
|
1746
1829
|
print('\n' + '=' * 72)
|
|
1747
1830
|
if failures:
|