fix(security): chặn mở khoá Sandbox Security bằng ô trống

DEFAULT_CONFIG ship agent_security.sandbox_pw = "" kể từ commit 3827552, và
cấu hình đưa tới dialog luôn được deep-merge với defaults đó. Nghĩa là trên
mọi bản cài không đặt COWORK_SANDBOX_PASSWORD, mật khẩu đã lưu là chuỗi rỗng
— và phép so `pw == self._sandbox_pw` nhận luôn ô nhập trống. Bấm Unlock với
ô trống là mở được toàn bộ nhóm Sandbox Security.

Commit 3827552 chỉ sửa config.py nên bỏ sót bản sao thứ hai của literal nằm
trong ui/settings_dialog.py, và chính nó tạo ra lỗ hổng rỗng này.

Sửa:
- gỡ literal credential khỏi mã nguồn (nó vốn là code chết: deep-merge làm
  tham số mặc định của .get() không bao giờ chạy);
- chặn rỗng trước khi so, theo đúng mẫu mà MS365 unlock đã dùng;
- so sánh timing-safe trên BYTES, không trên str — secrets.compare_digest
  ném TypeError với str ngoài ASCII, mà app mặc định tiếng Việt và phục vụ
  khách Nhật nên mật khẩu có dấu là input bình thường;
- chưa đặt mật khẩu thì báo đúng trạng thái đó, không báo "sai mật khẩu" —
  người dùng sẽ gõ lại mãi một thứ không tồn tại.

Root cause: ui/settings_dialog.py:104 (bản cũ)
Test: tests/ui/test_sandbox_unlock_security.py

LƯU Ý CHO REVIEWER: literal cũ vẫn nằm trong 6 commit của Git history. Gỡ ở
đây không gỡ khỏi lịch sử — cần xoay credential trên các máy còn giá trị đó
trong config.json. Không rewrite history (SECURITY.md).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-07 19:55:02 +09:00
co-authored by Claude Opus 5
parent 7bd2b95a57
commit 39df2c6ed1
3 changed files with 300 additions and 7 deletions
+22
View File
@@ -153,6 +153,7 @@ STRINGS: Dict[str, Dict[str, str]] = {
"settings.group.general": {"en": "General", "ja": "一般", "vi": "Chung"},
"settings.group.provider": {"en": "AI Provider", "ja": "AI プロバイダー", "vi": "Nhà cung cấp AI"},
"settings.group.parameter": {"en": "Parameter", "ja": "Parameter", "vi": "Parameter"},
"settings.group.about": {"en": "About", "ja": "このアプリについて", "vi": "Giới thiệu"},
"settings.param_section_pricing": {
"en": "Model pricing", "ja": "モデル価格", "vi": "Bảng giá model"},
"settings.pricing_url_label": {
@@ -328,6 +329,27 @@ STRINGS: Dict[str, Dict[str, str]] = {
"settings.group.sandbox": {
"en": "Sandbox Security Layer", "ja": "サンドボックス セキュリティ層",
"vi": "Sandbox Security Layer"},
"settings.sec_enabled": {
"en": "Enable Agent Security (command validation)",
"ja": "エージェントセキュリティを有効化(コマンド検証)",
"vi": "Bật Agent Security (kiểm tra lệnh)"},
"settings.sec_enabled_tooltip": {
"en": "Turn the whole Agent Security layer on or off.",
"ja": "エージェントセキュリティ層全体をオン/オフします。",
"vi": "Bật/tắt toàn bộ tầng Agent Security."},
"settings.ai_check": {
"en": "AI check commands", "ja": "AIによるコマンド検査",
"vi": "AI kiểm tra lệnh"},
"settings.ai_check_tooltip": {
"en": "Let the control agent review a command with AI before it runs.",
"ja": "実行前に制御エージェントがAIでコマンドを確認します。",
"vi": "Cho control-agent dùng AI xét lệnh trước khi chạy."},
"settings.sandbox_pw_unset_title": {
"en": "Sandbox Security", "ja": "サンドボックスセキュリティ", "vi": "Bảo mật Sandbox"},
"settings.sandbox_pw_unset_body": {
"en": "No sandbox password is set yet, so these settings stay locked. Set COWORK_SANDBOX_PASSWORD, or ask your administrator.",
"ja": "サンドボックスのパスワードが未設定のため、この設定はロックされたままです。COWORK_SANDBOX_PASSWORD を設定するか、管理者にお問い合わせください。",
"vi": "Chưa đặt mật khẩu sandbox nên nhóm thiết lập này vẫn khóa. Hãy đặt COWORK_SANDBOX_PASSWORD, hoặc liên hệ quản trị viên."},
"settings.sandbox_confirm_commands": {
"en": "Confirm before Cowork runs a command",
"ja": "Cowork がコマンドを実行する前に確認する",