-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathagent_text.cpp
More file actions
201 lines (178 loc) · 7.19 KB
/
Copy pathagent_text.cpp
File metadata and controls
201 lines (178 loc) · 7.19 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
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
// agent_text.cpp —— 纯文本/安全判定(不依赖 Windows API,可独立单测)
#include "agent_internal.h"
// 统一的工具失败判定:只认前缀。原先用 find("Tool Error") 判失败,
// 导致命令输出/文件内容里恰好含该字样时被误判为失败并清空。
bool isToolError(const std::string &text)
{
return text.rfind("Tool Error", 0) == 0 || text.rfind("FAIL", 0) == 0;
}
// 字节串是否为合法 UTF-8
bool isValidUtf8(const std::string &s)
{
size_t i = 0;
while (i < s.size())
{
const unsigned char c = static_cast<unsigned char>(s[i]);
size_t need = 0;
if (c < 0x80) { ++i; continue; }
else if ((c & 0xE0) == 0xC0) need = 2;
else if ((c & 0xF0) == 0xE0) need = 3;
else if ((c & 0xF8) == 0xF0) need = 4;
else return false;
if (i + need > s.size()) return false;
for (size_t k = 1; k < need; ++k)
if ((static_cast<unsigned char>(s[i + k]) & 0xC0) != 0x80) return false;
i += need;
}
return true;
}
// 判断字符串是否包含会破坏 cmd 命令拼接的元字符。
// 注意:* 与 ? 是 dir/findstr 的合法通配符,放行;这里只拦截能逃逸引号或执行命令的字符。
bool hasShellMetacharacter(const std::string &value)
{
for (char c : value)
{
if (c == '"' || c == '&' || c == '|' || c == ';' || c == '<' ||
c == '>' || c == '^' || c == '%' || c == '!' ||
c == '\n' || c == '\r')
return true;
}
return false;
}
// ============================================================
// 危险命令识别 — execute_command 的安全护栏
// ============================================================
bool containsIgnoreCase(const std::string &value, const std::string &needle)
{
if (needle.empty()) return true;
if (value.size() < needle.size()) return false;
for (size_t i = 0; i + needle.size() <= value.size(); ++i)
{
bool match = true;
for (size_t j = 0; j < needle.size(); ++j)
{
if (std::tolower((unsigned char)value[i + j]) != std::tolower((unsigned char)needle[j]))
{
match = false;
break;
}
}
if (match) return true;
}
return false;
}
bool equalsIgnoreCase(const std::string &a, const std::string &b)
{
if (a.size() != b.size()) return false;
for (size_t i = 0; i < a.size(); ++i)
if (std::tolower((unsigned char)a[i]) != std::tolower((unsigned char)b[i]))
return false;
return true;
}
// 归一化命令 token:去掉包裹引号、展开 caret 转义、只取路径末段、去掉扩展名。
// 用于堵住 "\"del\" x"、"d^el x"、"C:\Windows\System32\del.exe x" 这类绕过写法。
std::string normalizeCommandToken(const std::string &token)
{
std::string t = token;
while (!t.empty() && (t.front() == '"' || t.front() == '\'' || t.front() == '('))
t.erase(t.begin());
while (!t.empty() && (t.back() == '"' || t.back() == '\'' || t.back() == ')'))
t.pop_back();
std::string unescaped;
unescaped.reserve(t.size());
for (size_t i = 0; i < t.size(); ++i)
{
if (t[i] == '^' && i + 1 < t.size())
unescaped.push_back(t[++i]);
else
unescaped.push_back(t[i]);
}
// 只在"看起来是路径"时才取末段:C:\Windows\System32\del.exe → del.exe。
// 不能见斜杠就截,否则把 cmd 开关 "/c"、"/k" 截成 "c"、"k",wrapper 识别会失效。
size_t slash = unescaped.find_last_of("\\/");
if (slash != std::string::npos && slash + 1 < unescaped.size() &&
(unescaped.find(':') != std::string::npos || unescaped.find('\\') != std::string::npos))
{
unescaped = unescaped.substr(slash + 1);
}
size_t dot = unescaped.find_last_of('.');
if (dot != std::string::npos && dot > 0)
unescaped = unescaped.substr(0, dot);
return unescaped;
}
bool isDangerousCommand(const std::string &command)
{
// PowerShell 删除 cmdlet,出现在命令任何位置都视为危险
if (containsIgnoreCase(command, "remove-item"))
return true;
static const std::vector<std::string> dangerousTokens = {
"del", "erase", "rmdir", "rd", "rm", "format", "diskpart",
"shutdown", "taskkill", "reg", "setx", "bcdedit", "takeown",
"icacls", "cacls"
};
static const std::vector<std::string> wrapperTokens = {
"cmd", "cmd.exe", "powershell", "powershell.exe", "pwsh",
"call", "start", "/c", "/k", "-c", "-command", "/command"
};
// 流程关键字:其后的真实命令位置无法静态定位(如 for %i in (1) do del x、if exist x del y),
// 这类语句改为整句扫描,宁可多拦也不漏放。
static const std::vector<std::string> flowKeywords = {
"for", "if", "while", "until", "do", "else", "then", "goto", "call", "start"
};
auto inSet = [](const std::string &token, const std::vector<std::string> &set) {
for (const std::string &s : set)
if (equalsIgnoreCase(token, s)) return true;
return false;
};
// 按语句分隔符拆分,逐条定位命令位置
std::string statement;
for (size_t i = 0; i <= command.size(); ++i)
{
char c = (i < command.size()) ? command[i] : '&';
bool isSep = (c == '&' || c == '|' || c == ';' || c == '\n' || c == '\r' || c == '(' || c == ')');
if (!isSep)
{
statement.push_back(c);
continue;
}
std::vector<std::string> tokens;
std::istringstream tok(statement);
std::string token;
while (tok >> token)
tokens.push_back(normalizeCommandToken(token));
statement.clear();
size_t idx = 0;
while (idx < tokens.size() && (tokens[idx].empty() || inSet(tokens[idx], wrapperTokens)))
++idx;
if (idx >= tokens.size())
continue;
if (inSet(tokens[idx], flowKeywords))
{
for (size_t k = idx + 1; k < tokens.size(); ++k)
if (inSet(tokens[k], dangerousTokens)) return true;
continue;
}
if (inSet(tokens[idx], dangerousTokens))
return true;
}
return false;
}
// 裁剪对话历史(原为 DeepSeekAgent 的私有成员,抽成自由函数以便单测):
// 只能在 user 消息处切开,避免留下孤立的 tool 消息导致 API 400。
size_t trimConversationHistory(std::vector<json> &history, size_t maxMessages, size_t maxLookback)
{
if (history.size() <= maxMessages)
return 0;
const size_t cut = history.size() - maxMessages;
const size_t lowest = cut > maxLookback ? cut - maxLookback : 0;
size_t start = cut;
while (start > lowest && history[start].value("role", std::string()) != "user")
--start;
if (history[start].value("role", std::string()) != "user")
return 0; // 回看范围内没有 user 边界:不裁剪,也不构造非法消息序列
const size_t removed = start;
std::vector<json> trimmed(history.begin() + static_cast<std::vector<json>::difference_type>(start),
history.end());
history.swap(trimmed);
return removed;
}