-
Notifications
You must be signed in to change notification settings - Fork 4
Expand file tree
/
Copy pathpermissions_test.cpp
More file actions
172 lines (146 loc) · 7.28 KB
/
Copy pathpermissions_test.cpp
File metadata and controls
172 lines (146 loc) · 7.28 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
// 覆盖 src/permissions.hpp 的决策逻辑:
// - 三种模式(Default / AcceptEdits / Yolo)下哪些工具自动放行
// - 用户自定义 PermissionRule(按 tool/path/command 匹配 + priority)
// - 会话级 always-allow(/permissions 的 "always" 记忆)
// 任何回归都会让用户在 TUI 上要么多出现确认弹窗、要么危险命令被静默放行,
// 体感非常直接,所以这块测试用例密度意图高于别的模块。
#include <gtest/gtest.h>
#include "permissions.hpp"
using acecode::PermissionManager;
using acecode::PermissionMode;
using acecode::PermissionRule;
using acecode::RuleAction;
// 场景:Default 模式下,read-only 工具(is_read_only=true)自动放行,
// 写/执行工具(is_read_only=false)需要提示用户确认。
TEST(Permissions, DefaultModeAllowsReadOnlyTools) {
PermissionManager pm;
pm.set_mode(PermissionMode::Default);
// Read-only: auto-allow without prompt.
EXPECT_TRUE(pm.should_auto_allow("file_read", /*is_read_only=*/true));
EXPECT_TRUE(pm.should_auto_allow("grep", /*is_read_only=*/true));
EXPECT_TRUE(pm.should_auto_allow("TodoWrite", /*is_read_only=*/false));
// Write/exec: require prompt in default mode.
EXPECT_FALSE(pm.should_auto_allow("file_write", /*is_read_only=*/false));
EXPECT_FALSE(pm.should_auto_allow("bash", /*is_read_only=*/false));
}
TEST(Permissions, AllowsOnlyTheNativeAgentBrowserToolSetWithoutPrompt) {
PermissionManager pm;
pm.set_mode(PermissionMode::Default);
EXPECT_TRUE(pm.should_auto_allow("browser_click", false));
EXPECT_TRUE(pm.should_auto_allow("browser_evaluate", false));
EXPECT_TRUE(pm.should_auto_allow("browser_close", false));
EXPECT_FALSE(pm.should_auto_allow("browser_untrusted_plugin_action", false));
}
// 场景:Yolo 模式自动放行全部工具权限,包括 bash、file_write 这类高风险项。
// AgentLoop 不得在此之上追加任何权限确认门。
TEST(Permissions, YoloModeAllowsAll) {
PermissionManager pm;
pm.set_mode(PermissionMode::Yolo);
EXPECT_TRUE(pm.should_auto_allow("bash", false));
EXPECT_TRUE(pm.should_auto_allow("file_write", false));
EXPECT_TRUE(pm.should_auto_allow("file_edit", false));
}
// 场景:AcceptEdits 模式自动放行 file_write / file_edit,但仍然拦截 bash;
// 这个模式是为了让"大段写代码"不被反复打断,但危险命令(shell 执行)
// 还要用户确认,测试锁定这条边界。
TEST(Permissions, AcceptEditsModeAllowsFileWritesButNotBash) {
PermissionManager pm;
pm.set_mode(PermissionMode::AcceptEdits);
EXPECT_TRUE(pm.should_auto_allow("file_write", false));
EXPECT_TRUE(pm.should_auto_allow("file_edit", false));
EXPECT_FALSE(pm.should_auto_allow("bash", false));
}
// 场景:Plan 模式只按 Default 一样自动放行 read-only 工具;普通写工具
// 不能因为之前处在 AcceptEdits/Yolo 就被 PermissionManager 直接放行。
TEST(Permissions, PlanModeAllowsOnlyReadOnlyByDefault) {
PermissionManager pm;
pm.set_mode(PermissionMode::AcceptEdits);
pm.set_mode(PermissionMode::Plan);
EXPECT_TRUE(pm.should_auto_allow("file_read", true));
EXPECT_TRUE(pm.should_auto_allow("TodoWrite", false));
EXPECT_FALSE(pm.should_auto_allow("file_write", false));
EXPECT_FALSE(pm.should_auto_allow("file_edit", false));
EXPECT_FALSE(pm.should_auto_allow("bash", false));
}
// 场景:进入 Plan 模式时保存之前的非 Plan mode,ExitPlanMode 恢复它;
// 如果原来就在 Plan 里重复 set,不能把 pre-plan 覆盖成 Plan。
TEST(Permissions, PlanModeRestoresPreviousNonPlanMode) {
PermissionManager pm;
pm.set_mode(PermissionMode::AcceptEdits);
pm.set_mode(PermissionMode::Plan);
EXPECT_EQ(pm.mode(), PermissionMode::Plan);
EXPECT_EQ(pm.pre_plan_mode(), PermissionMode::AcceptEdits);
pm.set_mode(PermissionMode::Plan);
EXPECT_EQ(pm.pre_plan_mode(), PermissionMode::AcceptEdits);
EXPECT_EQ(pm.restore_pre_plan_mode(), PermissionMode::AcceptEdits);
EXPECT_EQ(pm.mode(), PermissionMode::AcceptEdits);
EXPECT_EQ(pm.pre_plan_mode(), PermissionMode::Default);
}
// 场景:状态栏/快捷键循环包含 Plan,顺序锁定为
// Default -> AcceptEdits -> Yolo -> Plan -> Default。
TEST(Permissions, CycleModeIncludesPlanMode) {
PermissionManager pm;
EXPECT_EQ(pm.cycle_mode(), PermissionMode::AcceptEdits);
EXPECT_EQ(pm.cycle_mode(), PermissionMode::Yolo);
EXPECT_EQ(pm.cycle_mode(), PermissionMode::Plan);
EXPECT_EQ(pm.cycle_mode(), PermissionMode::Default);
}
// 场景:添加一条针对 bash + command 前缀 "ls " 的 Allow 规则后,ls -la
// 自动放行,rm -rf / 不命中规则(走 Default 模式的常规确认)仍被拦截。
TEST(Permissions, GlobRuleMatchingAllowsMatchedCommand) {
PermissionManager pm;
pm.set_mode(PermissionMode::Default);
PermissionRule rule;
rule.tool_pattern = "bash";
rule.command_pattern = "ls "; // prefix match
rule.action = RuleAction::Allow;
rule.priority = 10;
pm.add_rule(rule);
EXPECT_TRUE(pm.should_auto_allow("bash", /*is_read_only=*/false,
/*path=*/"", /*command=*/"ls -la"));
EXPECT_FALSE(pm.should_auto_allow("bash", /*is_read_only=*/false,
/*path=*/"", /*command=*/"rm -rf /"));
}
// 场景:规则的 tool_pattern 必须生效——针对 bash 的规则不能顺带把
// file_write 也一起放行,否则"只给某个工具加 allow"就失去语义。
TEST(Permissions, GlobRuleMatchingRespectsToolPattern) {
PermissionManager pm;
pm.set_mode(PermissionMode::Default);
// Rule targets bash only — file_write should fall back to default prompt.
PermissionRule rule;
rule.tool_pattern = "bash";
rule.action = RuleAction::Allow;
rule.priority = 10;
pm.add_rule(rule);
EXPECT_TRUE(pm.should_auto_allow("bash", false));
EXPECT_FALSE(pm.should_auto_allow("file_write", false));
}
// 场景:即使在 Yolo 这种 baseline 全放行的模式下,一条高优先级的 Deny
// 规则仍能精准拦住 rm -rf;而未命中 Deny 的命令还是按 Yolo 放行。
// 这是"即使开 yolo,也要能黑名单少数危险命令"的守门机制。
TEST(Permissions, HigherPriorityDenyBeatsAllow) {
PermissionManager pm;
pm.set_mode(PermissionMode::Yolo); // baseline allow-all
// Explicit deny on a specific pattern must win over mode-based allow.
PermissionRule deny;
deny.tool_pattern = "bash";
deny.command_pattern = "rm -rf";
deny.action = RuleAction::Deny;
deny.priority = 100;
pm.add_rule(deny);
EXPECT_FALSE(pm.should_auto_allow("bash", false, "", "rm -rf /"));
// Commands that don't match the deny pattern still go through Yolo.
EXPECT_TRUE(pm.should_auto_allow("bash", false, "", "echo hi"));
}
// 场景:用户在确认弹窗选"总是允许"后,该工具在后续会话内自动放行;
// clear_session_allows 调用后 sticky 状态清零——mode 切换时触发。
TEST(Permissions, SessionAllowStickyForTool) {
PermissionManager pm;
pm.set_mode(PermissionMode::Default);
EXPECT_FALSE(pm.should_auto_allow("bash", false));
pm.add_session_allow("bash");
EXPECT_TRUE(pm.should_auto_allow("bash", false));
EXPECT_TRUE(pm.has_session_allow("bash"));
pm.clear_session_allows();
EXPECT_FALSE(pm.has_session_allow("bash"));
}