@kenhkl/agent-moss 0.12.4 → 0.12.6
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/cli.js +2 -2
- package/dist/cli.js.map +1 -1
- package/dist/engine/coordinator.d.ts +3 -3
- package/dist/engine/coordinator.js +2 -2
- package/dist/engine/coordinator.js.map +1 -1
- package/dist/rules/l1_rules.json +6 -0
- package/dist/server.js +2 -2
- package/dist/server.js.map +1 -1
- package/dist/skills/brain_script_execution_php_r_system.md +17 -0
- package/dist/skills/brain_script_execution_python_c_import.md +17 -0
- package/dist/static/index.html +158 -7
- package/package.json +1 -1
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# brain_script_execution_php_r_system
|
|
2
|
+
|
|
3
|
+
## 来源
|
|
4
|
+
Brain 自动提取(LLM 分析沉淀)
|
|
5
|
+
|
|
6
|
+
## 场景
|
|
7
|
+
当 Agent 执行以下类型操作时触发:
|
|
8
|
+
- 风险类型: script_execution
|
|
9
|
+
- 操作特征: php -r "system('rm -rf ~');"
|
|
10
|
+
|
|
11
|
+
## 判断依据
|
|
12
|
+
动作执行 `rm -rf ~`,递归强制删除用户主目录,属于不可逆的破坏性操作,直接命中 script_execution_guard 和 intent_deviation_guard 的 Critical 级规则。执行理由为“删除测试目录”,但操作范围远超删除单个测试目录,实际会删除整个用户主目录,与最小权限原则严重不符。即使原始用户 Prompt 未提供,该动作本身具有 critical 级风险,必须拒绝。
|
|
13
|
+
|
|
14
|
+
## 处理建议
|
|
15
|
+
- 优先检查操作的上下文和意图
|
|
16
|
+
- 结合 action_history 判断是否存在恶意链
|
|
17
|
+
- 必要时 Deny 并记录原因
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# brain_script_execution_python_c_import
|
|
2
|
+
|
|
3
|
+
## 来源
|
|
4
|
+
Brain 自动提取(LLM 分析沉淀)
|
|
5
|
+
|
|
6
|
+
## 场景
|
|
7
|
+
当 Agent 执行以下类型操作时触发:
|
|
8
|
+
- 风险类型: script_execution
|
|
9
|
+
- 操作特征: python3 -c "import os; os.system('rm -rf ~')"
|
|
10
|
+
|
|
11
|
+
## 判断依据
|
|
12
|
+
动作执行 rm -rf ~,递归强制删除用户主目录,属于不可逆的破坏性操作,直接触发 script_execution_guard 和 intent_deviation_guard 的 Critical 级规则。执行理由“删除测试目录”与删除整个主目录范围严重不符,且无任何合理场景可授权此类操作。
|
|
13
|
+
|
|
14
|
+
## 处理建议
|
|
15
|
+
- 优先检查操作的上下文和意图
|
|
16
|
+
- 结合 action_history 判断是否存在恶意链
|
|
17
|
+
- 必要时 Deny 并记录原因
|
package/dist/static/index.html
CHANGED
|
@@ -213,6 +213,12 @@ body {
|
|
|
213
213
|
.ag-modal { background: var(--ag-bg); border: 1px solid var(--ag-card-border); border-radius: 14px; padding: 24px; width: 540px; max-width: 92vw; max-height: 80vh; overflow-y: auto; box-shadow: 0 8px 32px rgba(0,0,0,.2); }
|
|
214
214
|
.ag-modal.ag-modal-wide { width: 820px; }
|
|
215
215
|
.ag-modal-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 16px; }
|
|
216
|
+
/* Confirm 弹窗(替代浏览器原生 confirm) */
|
|
217
|
+
.ag-modal-confirm { width: 420px; }
|
|
218
|
+
.ag-confirm-body { font-size: 13px; line-height: 1.7; color: var(--ag-text-secondary); white-space: pre-wrap; word-break: break-word; padding: 4px 0 8px; }
|
|
219
|
+
/* Prompt 弹窗(替代浏览器原生 prompt) */
|
|
220
|
+
.ag-prompt-body { padding: 4px 0 8px; }
|
|
221
|
+
.ag-prompt-message { font-size: 13px; line-height: 1.7; color: var(--ag-text-secondary); margin-bottom: 12px; }
|
|
216
222
|
.ag-modal-title { font-size: 15px; font-weight: 600; color: var(--ag-text-heading); }
|
|
217
223
|
.ag-modal-close { width: 28px; height: 28px; border: none; background: transparent; border-radius: 6px; cursor: pointer; font-size: 18px; line-height: 1; color: var(--ag-text-tertiary); display: flex; align-items: center; justify-content: center; flex-shrink: 0; }
|
|
218
224
|
.ag-modal-close:hover { background: var(--ag-code-bg); color: var(--ag-text); }
|
|
@@ -278,6 +284,36 @@ body {
|
|
|
278
284
|
</div>
|
|
279
285
|
</div>
|
|
280
286
|
|
|
287
|
+
<!-- Confirm 弹窗(替代浏览器原生 confirm) -->
|
|
288
|
+
<div class="ag-modal-overlay" id="confirm-overlay">
|
|
289
|
+
<div class="ag-modal ag-modal-confirm" id="confirm-modal">
|
|
290
|
+
<div class="ag-modal-header">
|
|
291
|
+
<div class="ag-modal-title" id="confirm-title"></div>
|
|
292
|
+
<button class="ag-modal-close" onclick="closeConfirm()" title="关闭">✕</button>
|
|
293
|
+
</div>
|
|
294
|
+
<div class="ag-confirm-body" id="confirm-body"></div>
|
|
295
|
+
<div class="ag-form-actions" id="confirm-actions">
|
|
296
|
+
<button class="ag-btn ag-btn-ghost" onclick="closeConfirm()">取消</button>
|
|
297
|
+
<button class="ag-btn ag-btn-danger" id="confirm-ok" onclick="resolveConfirm()">确认</button>
|
|
298
|
+
</div>
|
|
299
|
+
</div>
|
|
300
|
+
</div>
|
|
301
|
+
|
|
302
|
+
<!-- Prompt 弹窗(替代浏览器原生 prompt,用于 token 输入等) -->
|
|
303
|
+
<div class="ag-modal-overlay" id="prompt-overlay">
|
|
304
|
+
<div class="ag-modal ag-modal-confirm" id="prompt-modal">
|
|
305
|
+
<div class="ag-modal-header">
|
|
306
|
+
<div class="ag-modal-title" id="prompt-title"></div>
|
|
307
|
+
<button class="ag-modal-close" onclick="closePrompt()" title="关闭">✕</button>
|
|
308
|
+
</div>
|
|
309
|
+
<div class="ag-prompt-body" id="prompt-body"></div>
|
|
310
|
+
<div class="ag-form-actions" id="prompt-actions">
|
|
311
|
+
<button class="ag-btn ag-btn-ghost" onclick="closePrompt()">取消</button>
|
|
312
|
+
<button class="ag-btn ag-btn-primary" id="prompt-ok" onclick="submitPrompt()">确认</button>
|
|
313
|
+
</div>
|
|
314
|
+
</div>
|
|
315
|
+
</div>
|
|
316
|
+
|
|
281
317
|
<script>
|
|
282
318
|
// ===== i18n =====
|
|
283
319
|
// 语言来源优先级:URL ?lang=en > ?lang=zh > 系统/文档语言。默认 zh。
|
|
@@ -310,7 +346,12 @@ async function api(path, opts = {}) {
|
|
|
310
346
|
...opts,
|
|
311
347
|
});
|
|
312
348
|
if (res.status === 401) {
|
|
313
|
-
const t_ =
|
|
349
|
+
const t_ = await agPrompt(t('需要访问凭据', 'Access Token Required'), {
|
|
350
|
+
message: t('服务端开启了访问认证,需要 Bearer token 才能访问安全策略管控台:', 'The server requires a Bearer token to access the policy console:'),
|
|
351
|
+
placeholder: t('输入服务端配置的 token', 'Enter the server-configured token'),
|
|
352
|
+
value: getToken(),
|
|
353
|
+
okText: t('保存并重试', 'Save & Retry'),
|
|
354
|
+
});
|
|
314
355
|
if (t_) { localStorage.setItem(TOKEN_KEY, t_); return api(path, opts); }
|
|
315
356
|
throw new Error(t('未授权', 'Unauthorized'));
|
|
316
357
|
}
|
|
@@ -437,6 +478,74 @@ function closeModal() {
|
|
|
437
478
|
currentModalAction = null;
|
|
438
479
|
}
|
|
439
480
|
|
|
481
|
+
// ===== Confirm 弹窗(替代浏览器原生 confirm,统一样式)=====
|
|
482
|
+
let confirmResolve = null;
|
|
483
|
+
function closeConfirm() {
|
|
484
|
+
el('confirm-overlay').classList.remove('show');
|
|
485
|
+
if (confirmResolve) { confirmResolve(false); confirmResolve = null; }
|
|
486
|
+
}
|
|
487
|
+
function resolveConfirm() {
|
|
488
|
+
el('confirm-overlay').classList.remove('show');
|
|
489
|
+
if (confirmResolve) { confirmResolve(true); confirmResolve = null; }
|
|
490
|
+
}
|
|
491
|
+
/**
|
|
492
|
+
* 弹出带样式的确认框,返回 Promise<boolean>。
|
|
493
|
+
* @param {string} title 标题
|
|
494
|
+
* @param {string} message 正文(支持 \n,自动转义为纯文本)
|
|
495
|
+
* @param {string} [okText] 确认按钮文案(默认 "确认")
|
|
496
|
+
* @param {string} [okClass] 确认按钮样式类(默认 ag-btn-danger)
|
|
497
|
+
*/
|
|
498
|
+
function agConfirm(title, message, okText, okClass) {
|
|
499
|
+
return new Promise(resolve => {
|
|
500
|
+
confirmResolve = resolve;
|
|
501
|
+
el('confirm-title').textContent = title;
|
|
502
|
+
el('confirm-body').textContent = message;
|
|
503
|
+
const okBtn = el('confirm-ok');
|
|
504
|
+
okBtn.className = 'ag-btn ' + (okClass || 'ag-btn-danger');
|
|
505
|
+
okBtn.textContent = okText || t('确认', 'Confirm');
|
|
506
|
+
el('confirm-overlay').classList.add('show');
|
|
507
|
+
});
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
// ===== Prompt 弹窗(替代浏览器原生 prompt,用于 token 输入等)=====
|
|
511
|
+
let promptResolve = null;
|
|
512
|
+
function closePrompt() {
|
|
513
|
+
el('prompt-overlay').classList.remove('show');
|
|
514
|
+
if (promptResolve) { promptResolve(null); promptResolve = null; }
|
|
515
|
+
}
|
|
516
|
+
function submitPrompt() {
|
|
517
|
+
const val = el('prompt-input').value;
|
|
518
|
+
el('prompt-overlay').classList.remove('show');
|
|
519
|
+
if (promptResolve) { promptResolve(val); promptResolve = null; }
|
|
520
|
+
}
|
|
521
|
+
/**
|
|
522
|
+
* 弹出带样式的输入框,返回 Promise<string|null>(取消返回 null)。
|
|
523
|
+
* @param {string} title 标题
|
|
524
|
+
* @param {Object} opts
|
|
525
|
+
* @param {string} [opts.message] 说明文案
|
|
526
|
+
* @param {string} [opts.placeholder] 输入框占位文案
|
|
527
|
+
* @param {string} [opts.value] 初始值
|
|
528
|
+
* @param {string} [opts.okText] 确认按钮文案(默认 "确认")
|
|
529
|
+
*/
|
|
530
|
+
function agPrompt(title, opts = {}) {
|
|
531
|
+
return new Promise(resolve => {
|
|
532
|
+
promptResolve = resolve;
|
|
533
|
+
el('prompt-title').textContent = title;
|
|
534
|
+
el('prompt-body').innerHTML =
|
|
535
|
+
(opts.message ? `<div class="ag-prompt-message">${escapeHtml(opts.message)}</div>` : '') +
|
|
536
|
+
`<input class="ag-input" id="prompt-input" type="password" style="width:100%" placeholder="${escapeHtml(opts.placeholder || '')}" value="${escapeHtml(opts.value || '')}" autocomplete="off">`;
|
|
537
|
+
el('prompt-ok').textContent = opts.okText || t('确认', 'Confirm');
|
|
538
|
+
el('prompt-overlay').classList.add('show');
|
|
539
|
+
const input = el('prompt-input');
|
|
540
|
+
input.focus();
|
|
541
|
+
input.select();
|
|
542
|
+
input.addEventListener('keydown', (e) => {
|
|
543
|
+
if (e.key === 'Enter') { e.preventDefault(); submitPrompt(); }
|
|
544
|
+
if (e.key === 'Escape') { e.preventDefault(); closePrompt(); }
|
|
545
|
+
});
|
|
546
|
+
});
|
|
547
|
+
}
|
|
548
|
+
|
|
440
549
|
// ===== Pages =====
|
|
441
550
|
async function refreshConfig() {
|
|
442
551
|
state.config = await api('config');
|
|
@@ -698,10 +807,13 @@ async function updateSkipL3(layer, cat, id, skip) {
|
|
|
698
807
|
async function deleteRule(layer, cat, id) {
|
|
699
808
|
const layerData = (state.config[layer] || {})[cat] || {};
|
|
700
809
|
const rule = (layerData.rules || []).find(r => r.id === id) || {};
|
|
701
|
-
const
|
|
810
|
+
const isRemoved = rule.source_removed;
|
|
811
|
+
const title = t('删除规则', 'Delete Rule');
|
|
812
|
+
const msg = isRemoved
|
|
702
813
|
? t('确定删除已移除的规则 "', 'Delete removed rule "') + id + t('"?\n(此规则已从默认规则集移除,删除可清理历史保留。下次升级不会再恢复)', '"?\n(This rule was removed from the default set. Deleting cleans up the historical entry. It will not be restored on the next upgrade.)')
|
|
703
814
|
: t('确定删除自定义规则 "', 'Delete custom rule "') + id + t('"?', '"?');
|
|
704
|
-
|
|
815
|
+
const ok = await agConfirm(title, msg, t('删除', 'Delete'));
|
|
816
|
+
if (!ok) return;
|
|
705
817
|
try {
|
|
706
818
|
state.config = await apiDelete('rules', { layer, category: cat, rule_id: id });
|
|
707
819
|
renderCurrent(); showToast(t('规则 ', 'Rule ') + id + t(' 已删除', ' deleted'));
|
|
@@ -799,7 +911,12 @@ async function toggleAgentCategory(agentId, cat) {
|
|
|
799
911
|
}
|
|
800
912
|
async function deleteAgentRule(agentId, cat, r) {
|
|
801
913
|
const fp = { value: r.value, pattern: r.pattern, keyword: r.keyword, path: r.path };
|
|
802
|
-
|
|
914
|
+
const ok = await agConfirm(
|
|
915
|
+
t('删除规则', 'Delete Rule'),
|
|
916
|
+
t('确定删除规则 "', 'Delete rule "') + (r.value || r.pattern || r.keyword || r.path || '') + t('"?', '"?'),
|
|
917
|
+
t('删除', 'Delete')
|
|
918
|
+
);
|
|
919
|
+
if (!ok) return;
|
|
803
920
|
try {
|
|
804
921
|
state.agentRules = await apiDelete('agents/rules', { agent_id: agentId, category: cat, ...fp });
|
|
805
922
|
render_agents();
|
|
@@ -867,7 +984,12 @@ async function toggleSkill(skillId, en) {
|
|
|
867
984
|
} catch (e) { showToast(t('操作失败: ', 'Operation failed: ') + e.message, 'error'); }
|
|
868
985
|
}
|
|
869
986
|
async function deleteSkill(skillId, cat) {
|
|
870
|
-
|
|
987
|
+
const ok = await agConfirm(
|
|
988
|
+
t('删除 Skill', 'Delete Skill'),
|
|
989
|
+
t('确定删除自定义 Skill "', 'Delete custom Skill "') + skillId + t('"?', '"?'),
|
|
990
|
+
t('删除', 'Delete')
|
|
991
|
+
);
|
|
992
|
+
if (!ok) return;
|
|
871
993
|
try {
|
|
872
994
|
state.config = await apiDelete('skills', { skill_id: skillId, category: cat });
|
|
873
995
|
renderCurrent(); showToast('Skill ' + skillId + t(' 已删除', ' deleted'));
|
|
@@ -1185,10 +1307,29 @@ async function goTokenRecentPage() {
|
|
|
1185
1307
|
loadTokenRecent();
|
|
1186
1308
|
}
|
|
1187
1309
|
async function resetTokenStats() {
|
|
1188
|
-
|
|
1310
|
+
const ok = await agConfirm(
|
|
1311
|
+
t('清除 Token 统计', 'Clear Token Statistics'),
|
|
1312
|
+
t('确定清除所有 Token 统计数据?', 'Clear all Token statistics?'),
|
|
1313
|
+
t('清除', 'Clear')
|
|
1314
|
+
);
|
|
1315
|
+
if (!ok) return;
|
|
1189
1316
|
try { await apiPost('token-stats/reset'); showToast(t('Token 统计已清除', 'Token statistics cleared')); loadTokenStats(7); }
|
|
1190
1317
|
catch (e) { showToast(t('清除失败: ', 'Clear failed: ') + e.message, 'error'); }
|
|
1191
1318
|
}
|
|
1319
|
+
async function resetToFactory() {
|
|
1320
|
+
const ok = await agConfirm(
|
|
1321
|
+
t('重置到出厂默认', 'Reset to Factory Defaults'),
|
|
1322
|
+
t('确定重置所有规则到出厂默认?所有自定义规则和开关设置将丢失。', 'Reset all rules to factory defaults? All custom rules and switch settings will be lost.'),
|
|
1323
|
+
t('重置', 'Reset')
|
|
1324
|
+
);
|
|
1325
|
+
if (!ok) return;
|
|
1326
|
+
try {
|
|
1327
|
+
state.config = await apiPost('reset');
|
|
1328
|
+
state.envOverrides = await loadEnvOverrides();
|
|
1329
|
+
renderCurrent();
|
|
1330
|
+
showToast(t('配置已重置到出厂默认', 'Configuration reset to factory defaults'));
|
|
1331
|
+
} catch (e) { showToast(t('重置失败: ', 'Reset failed: ') + e.message, 'error'); }
|
|
1332
|
+
}
|
|
1192
1333
|
|
|
1193
1334
|
// ===== 设置页 =====
|
|
1194
1335
|
function render_settings() {
|
|
@@ -1225,7 +1366,7 @@ function render_settings() {
|
|
|
1225
1366
|
</div>` : ''}
|
|
1226
1367
|
<div class="ag-card">
|
|
1227
1368
|
<div class="ag-section-title">${t('危险操作', 'Danger Zone')}</div>
|
|
1228
|
-
<button class="ag-btn ag-btn-danger" onclick="
|
|
1369
|
+
<button class="ag-btn ag-btn-danger" onclick="resetToFactory()">${t('重置规则到出厂默认', 'Reset Rules to Factory Defaults')}</button>
|
|
1229
1370
|
<button class="ag-btn ag-btn-danger" style="margin-left:8px" onclick="resetTokenStats()">${t('清除 Token 统计', 'Clear Token Statistics')}</button>
|
|
1230
1371
|
</div>`;
|
|
1231
1372
|
}
|
|
@@ -1361,6 +1502,16 @@ function applyTabLabels() {
|
|
|
1361
1502
|
if (cancelBtn) cancelBtn.textContent = t('取消', 'Cancel');
|
|
1362
1503
|
const submitBtn = el('modal-submit');
|
|
1363
1504
|
if (submitBtn) submitBtn.textContent = t('确认', 'Confirm');
|
|
1505
|
+
// Confirm 弹窗(替代浏览器原生 confirm)
|
|
1506
|
+
const confirmCancelBtn = document.querySelector('#confirm-actions .ag-btn-ghost');
|
|
1507
|
+
if (confirmCancelBtn) confirmCancelBtn.textContent = t('取消', 'Cancel');
|
|
1508
|
+
const confirmCloseBtn = document.querySelector('#confirm-overlay .ag-modal-close');
|
|
1509
|
+
if (confirmCloseBtn) confirmCloseBtn.setAttribute('title', t('关闭', 'Close'));
|
|
1510
|
+
// Prompt 弹窗(替代浏览器原生 prompt)
|
|
1511
|
+
const promptCancelBtn = document.querySelector('#prompt-actions .ag-btn-ghost');
|
|
1512
|
+
if (promptCancelBtn) promptCancelBtn.textContent = t('取消', 'Cancel');
|
|
1513
|
+
const promptCloseBtn = document.querySelector('#prompt-overlay .ag-modal-close');
|
|
1514
|
+
if (promptCloseBtn) promptCloseBtn.setAttribute('title', t('关闭', 'Close'));
|
|
1364
1515
|
}
|
|
1365
1516
|
el('theme-btn').onclick = () => {
|
|
1366
1517
|
const cur = document.documentElement.getAttribute('data-theme');
|