@kenhkl/agent-moss 0.12.6 → 0.12.8
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/agents/__pycache__/base.cpython-312.pyc +0 -0
- package/dist/agents/base.py +41 -4
- package/dist/cli.js +4475 -916
- package/dist/cli.js.map +1 -1
- package/dist/engine/coordinator.d.ts +16 -6
- package/dist/engine/coordinator.js +3378 -708
- package/dist/engine/coordinator.js.map +1 -1
- package/dist/rules/l1_rules.json +21 -0
- package/dist/rules/l2_rules.json +136 -1
- package/dist/scenarios/__init__.py +119 -0
- package/dist/scenarios/__pycache__/__init__.cpython-312.pyc +0 -0
- package/dist/scenarios/bank/rules.json +23 -0
- package/dist/server.js +4180 -742
- package/dist/server.js.map +1 -1
- package/dist/static/index.html +119 -1
- package/package.json +2 -1
- package/dist/skills/brain_script_execution_php_r_system.md +0 -17
- package/dist/skills/brain_script_execution_python_c_import.md +0 -17
package/dist/static/index.html
CHANGED
|
@@ -254,6 +254,7 @@ body {
|
|
|
254
254
|
<div class="ag-tab" data-page="l2">L2 规则</div>
|
|
255
255
|
<div class="ag-tab" data-page="l3">L3 Skills</div>
|
|
256
256
|
<div class="ag-tab" data-page="agents">Agent 定制</div>
|
|
257
|
+
<div class="ag-tab" data-page="scenarios">场景定制</div>
|
|
257
258
|
<div class="ag-tab" data-page="token">Token</div>
|
|
258
259
|
<div class="ag-tab" data-page="settings">设置</div>
|
|
259
260
|
<div class="ag-tab-spacer"></div>
|
|
@@ -265,6 +266,7 @@ body {
|
|
|
265
266
|
<div class="ag-panel hidden" id="page-l2"></div>
|
|
266
267
|
<div class="ag-panel hidden" id="page-l3"></div>
|
|
267
268
|
<div class="ag-panel hidden" id="page-agents"></div>
|
|
269
|
+
<div class="ag-panel hidden" id="page-scenarios"></div>
|
|
268
270
|
<div class="ag-panel hidden" id="page-token"></div>
|
|
269
271
|
<div class="ag-panel hidden" id="page-settings"></div>
|
|
270
272
|
</div>
|
|
@@ -551,6 +553,8 @@ async function refreshConfig() {
|
|
|
551
553
|
state.config = await api('config');
|
|
552
554
|
state.envOverrides = await loadEnvOverrides();
|
|
553
555
|
state.agentRules = await api('agents');
|
|
556
|
+
// 场景定制规则:后端按 AGENT_MOSS_SCENARIO env 过滤(无 env 返回空 → 场景页显示"未启用"提示)
|
|
557
|
+
state.scenarioRules = await api('scenarios');
|
|
554
558
|
// 探测当前进程的 instance(AGENT_MOSS_INSTANCE,如 opendesk/xiaoo)。
|
|
555
559
|
// 有 instance 时 Agent Rules 页只显示当前 agent 的定制规则,避免出现别的 agent 的规则造成困惑;
|
|
556
560
|
// 无 instance(本地独立起服务/非 agent 调用)则显示全部。
|
|
@@ -573,7 +577,7 @@ async function refreshConfig() {
|
|
|
573
577
|
renderCurrent();
|
|
574
578
|
}
|
|
575
579
|
function renderCurrent() {
|
|
576
|
-
const pages = ['overview','search','l1','l2','l3','agents','token','settings'];
|
|
580
|
+
const pages = ['overview','search','l1','l2','l3','agents','scenarios','token','settings'];
|
|
577
581
|
pages.forEach(p => {
|
|
578
582
|
const panel = el('page-' + p);
|
|
579
583
|
if (panel) panel.classList.toggle('hidden', p !== state.currentPage);
|
|
@@ -932,6 +936,112 @@ function showAddAgentRuleModal(agentId, cat) {
|
|
|
932
936
|
currentModalAction = { type: 'agent_rule', agentId, cat, isValueCat };
|
|
933
937
|
}
|
|
934
938
|
|
|
939
|
+
// ===== 场景定制规则页 =====
|
|
940
|
+
// 场景规则(agent_moss/scenarios/<id>/rules.json)与 agent 规则同构(都是
|
|
941
|
+
// AgentExtensions 结构),复用 agent 卡片的类别模板/开关/增删交互;唯一区别是
|
|
942
|
+
// 启用由 AGENT_MOSS_SCENARIO env 决定(后端 /api/scenarios 已过滤,无 env 返回空)。
|
|
943
|
+
function render_scenarios() {
|
|
944
|
+
const resp = state.scenarioRules || {};
|
|
945
|
+
const scenarios = resp.scenarios || {};
|
|
946
|
+
const enabled = resp.enabled || [];
|
|
947
|
+
const noEnv = enabled.length === 0;
|
|
948
|
+
if (noEnv) {
|
|
949
|
+
el('page-scenarios').innerHTML = `<div class="ag-card"><div class="ag-empty">${t(
|
|
950
|
+
'未启用任何场景(未设置 AGENT_MOSS_SCENARIO 环境变量)。设置后此处显示场景定制规则并可在线编辑。',
|
|
951
|
+
'No scenario enabled (AGENT_MOSS_SCENARIO env not set). Set it to view and edit scenario rules here.'
|
|
952
|
+
)}</div></div>`;
|
|
953
|
+
return;
|
|
954
|
+
}
|
|
955
|
+
if (!Object.keys(scenarios).length) {
|
|
956
|
+
el('page-scenarios').innerHTML = `<div class="ag-card"><div class="ag-empty">${t(
|
|
957
|
+
'已设置 AGENT_MOSS_SCENARIO,但没有对应的场景规则(检查 agent_moss/scenarios/ 目录)。',
|
|
958
|
+
'AGENT_MOSS_SCENARIO is set but no matching scenario rules found (check agent_moss/scenarios/).'
|
|
959
|
+
)}</div></div>`;
|
|
960
|
+
return;
|
|
961
|
+
}
|
|
962
|
+
let html = '';
|
|
963
|
+
for (const scenarioId of Object.keys(scenarios)) {
|
|
964
|
+
const cats = scenarios[scenarioId] || {};
|
|
965
|
+
html += `<div class="ag-card"><div class="ag-section-title">${t('场景', 'Scenario')}: ${escapeHtml(scenarioId)}</div>`;
|
|
966
|
+
const ordered = [
|
|
967
|
+
...Object.keys(AGENT_CATEGORY_NAMES).filter(k => k in cats).map(k => [k, cats[k]]),
|
|
968
|
+
...Object.entries(cats).filter(([k]) => !(k in AGENT_CATEGORY_NAMES)),
|
|
969
|
+
];
|
|
970
|
+
for (const [catName, catData] of ordered) {
|
|
971
|
+
const rules = catData.rules || [];
|
|
972
|
+
const catEnabled = catData.category_enabled !== false;
|
|
973
|
+
const expanded = expandedCats.has(`scenarios-${scenarioId}-${catName}`);
|
|
974
|
+
const enCount = catEnabled ? rules.filter(r => r.enabled).length : 0;
|
|
975
|
+
html += `<div class="ag-cat-header" onclick="toggleExpand('scenarios-${scenarioId}-${catName}')">
|
|
976
|
+
<div class="ag-cat-name">${escapeHtml(AGENT_CATEGORY_NAMES[catName] || catName)} <span class="ag-cat-count">${enCount}/${rules.length} ${t('启用', 'enabled')}</span></div>
|
|
977
|
+
<div class="ag-cat-controls"><div onclick="event.stopPropagation();toggleScenarioCategory('${scenarioId}','${catName}')">${toggle(catEnabled, false, '')}</div><span class="ag-expand-icon ${expanded ? 'open' : ''}">▼</span></div>
|
|
978
|
+
</div>
|
|
979
|
+
<div class="ag-rule-list" id="list-scenarios-${scenarioId}-${catName}" style="display:${expanded ? '' : 'none'}">`;
|
|
980
|
+
if (!rules.length) {
|
|
981
|
+
html += `<div class="ag-empty">${t('无规则', 'No rules')}</div>`;
|
|
982
|
+
} else {
|
|
983
|
+
for (const r of rules) html += renderScenarioRuleItem(scenarioId, catName, r);
|
|
984
|
+
}
|
|
985
|
+
html += `<div style="padding:8px 0"><button class="ag-btn ag-btn-ghost" onclick="showAddScenarioRuleModal('${scenarioId}','${catName}')">+ ${t('新增', 'Add')}</button></div>`;
|
|
986
|
+
html += '</div>';
|
|
987
|
+
}
|
|
988
|
+
html += '</div>';
|
|
989
|
+
}
|
|
990
|
+
el('page-scenarios').innerHTML = html;
|
|
991
|
+
}
|
|
992
|
+
function renderScenarioRuleItem(scenarioId, catName, r) {
|
|
993
|
+
const patternDisplay = r.value || r.pattern || r.keyword || r.path || r.id || JSON.stringify(r).slice(0, 60);
|
|
994
|
+
const reasonDisplay = r.reason || r.desc || r.lang || '';
|
|
995
|
+
const tagLine = (r.builtin !== false ? `<span class="ag-badge ag-badge-builtin" title="${t('源码内置规则', 'Built-in source rule')}">${t('内置', 'Built-in')}</span>` : `<span class="ag-badge ag-badge-custom">${t('自定义', 'Custom')}</span>`)
|
|
996
|
+
+ (r.source_removed ? `<span class="ag-badge ag-badge-removed" title="${t('此规则已从默认规则集移除,仅作历史保留', 'This rule was removed from the default set and is kept as history')}">${t('已移除', 'Removed')}</span>` : '')
|
|
997
|
+
+ (r.pattern ? riskBadge(r.risk_level) + (r.risk_type ? riskTypeBadge(r.risk_type) : '') : '');
|
|
998
|
+
const patternLabel = r.path ? t('敏感路径', 'Sensitive Path') : (r.keyword ? t('触发关键词', 'Trigger Keywords') : t('规则', 'Rule'));
|
|
999
|
+
const tooltip = `${patternLabel}: ${patternDisplay}\n${t('原因', 'Reason')}: ${reasonDisplay || t('(无)', '(none)')}`;
|
|
1000
|
+
const delBtn = r.builtin ? '' : `<button class="ag-btn ag-btn-ghost ag-btn-sm" onclick="deleteScenarioRule('${scenarioId}','${catName}',${JSON.stringify(r).replace(/"/g,'"')})">${t('删除', 'Delete')}</button>`;
|
|
1001
|
+
return `<div class="ag-rule-item" title="${escapeHtml(tooltip)}">
|
|
1002
|
+
<div class="ag-rule-body">
|
|
1003
|
+
<div class="ag-rule-tags">${tagLine}</div>
|
|
1004
|
+
<div class="ag-rule-pattern"><span class="ag-rule-field-label">${patternLabel}:</span> ${escapeHtml(patternDisplay)}</div>
|
|
1005
|
+
${reasonDisplay ? `<div class="ag-rule-reason"><span class="ag-rule-field-label">${t('原因', 'Reason')}:</span> ${escapeHtml(reasonDisplay)}</div>` : ''}
|
|
1006
|
+
</div>
|
|
1007
|
+
<div class="ag-rule-controls">${toggle(r.enabled, false, `toggleScenarioRule('${scenarioId}','${catName}',${JSON.stringify(r).replace(/"/g,'"')},${!r.enabled})`)}${delBtn}</div>
|
|
1008
|
+
</div>`;
|
|
1009
|
+
}
|
|
1010
|
+
async function toggleScenarioRule(scenarioId, cat, r, en) {
|
|
1011
|
+
const fp = { value: r.value, pattern: r.pattern, keyword: r.keyword, path: r.path };
|
|
1012
|
+
try {
|
|
1013
|
+
state.scenarioRules = await apiPut('scenarios/rules/enabled', { scenario_id: scenarioId, category: cat, ...fp, enabled: en });
|
|
1014
|
+
render_scenarios();
|
|
1015
|
+
} catch (e) { showToast(String(e), 'error'); }
|
|
1016
|
+
}
|
|
1017
|
+
async function toggleScenarioCategory(scenarioId, cat) {
|
|
1018
|
+
const cats = (state.scenarioRules?.scenarios || {})[scenarioId] || {};
|
|
1019
|
+
const en = cats[cat]?.category_enabled === false;
|
|
1020
|
+
try {
|
|
1021
|
+
state.scenarioRules = await apiPut('scenarios/categories/enabled', { scenario_id: scenarioId, category: cat, enabled: en });
|
|
1022
|
+
render_scenarios();
|
|
1023
|
+
} catch (e) { showToast(String(e), 'error'); }
|
|
1024
|
+
}
|
|
1025
|
+
async function deleteScenarioRule(scenarioId, cat, r) {
|
|
1026
|
+
const fp = { value: r.value, pattern: r.pattern, keyword: r.keyword, path: r.path };
|
|
1027
|
+
const ok = await agConfirm(
|
|
1028
|
+
t('删除规则', 'Delete Rule'),
|
|
1029
|
+
t('确定删除规则 "', 'Delete rule "') + (r.value || r.pattern || r.keyword || r.path || '') + t('"?', '"?'),
|
|
1030
|
+
t('删除', 'Delete')
|
|
1031
|
+
);
|
|
1032
|
+
if (!ok) return;
|
|
1033
|
+
try {
|
|
1034
|
+
state.scenarioRules = await apiDelete('scenarios/rules', { scenario_id: scenarioId, category: cat, ...fp });
|
|
1035
|
+
render_scenarios();
|
|
1036
|
+
} catch (e) { showToast(String(e), 'error'); }
|
|
1037
|
+
}
|
|
1038
|
+
function showAddScenarioRuleModal(scenarioId, cat) {
|
|
1039
|
+
const body = `<input class="ag-input" id="ar-pattern" placeholder="${t('正则/关键词/路径', 'Regex/keyword/path')}" style="width:100%;margin-bottom:8px">
|
|
1040
|
+
<input class="ag-input" id="ar-risk_level" placeholder="risk_level (high/medium/...)" style="width:100%">`;
|
|
1041
|
+
openModal(t('新增场景规则 ', 'Add scenario rule ') + scenarioId + ' / ' + cat, body, true);
|
|
1042
|
+
currentModalAction = { type: 'scenario_rule', scenarioId, cat };
|
|
1043
|
+
}
|
|
1044
|
+
|
|
935
1045
|
// ===== L3 Skills 页 =====
|
|
936
1046
|
function render_l3() {
|
|
937
1047
|
const l3 = state.config.L3_skills || {};
|
|
@@ -1464,6 +1574,14 @@ async function submitModal() {
|
|
|
1464
1574
|
}
|
|
1465
1575
|
state.agentRules = await apiPost('agents/rules', payload);
|
|
1466
1576
|
showToast(t('Agent 规则已添加', 'Agent rule added'));
|
|
1577
|
+
} else if (currentModalAction.type === 'scenario_rule') {
|
|
1578
|
+
const { scenarioId, cat } = currentModalAction;
|
|
1579
|
+
const payload = { scenario_id: scenarioId, category: cat };
|
|
1580
|
+
payload.pattern = el('ar-pattern').value.trim();
|
|
1581
|
+
if (!payload.pattern) { showToast(t('规则不能为空', 'Rule cannot be empty'), 'error'); return; }
|
|
1582
|
+
payload.risk_level = el('ar-risk_level').value.trim() || undefined;
|
|
1583
|
+
state.scenarioRules = await apiPost('scenarios/rules', payload);
|
|
1584
|
+
showToast(t('场景规则已添加', 'Scenario rule added'));
|
|
1467
1585
|
}
|
|
1468
1586
|
renderCurrent();
|
|
1469
1587
|
closeModal();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kenhkl/agent-moss",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.8",
|
|
4
4
|
"description": "Multi-layer security analysis engine for AI agents (TypeScript)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/server.js",
|
|
@@ -30,6 +30,7 @@
|
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
32
|
"@hono/node-server": "^1.13.0",
|
|
33
|
+
"@lezer/python": "^1.1.19",
|
|
33
34
|
"hono": "^4.6.0",
|
|
34
35
|
"openai": "^4.104.0",
|
|
35
36
|
"pino": "^9.5.0",
|
|
@@ -1,17 +0,0 @@
|
|
|
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 并记录原因
|
|
@@ -1,17 +0,0 @@
|
|
|
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 并记录原因
|