@dcrays/mobook-security-plugin 2.0.7 → 2.0.10

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/index.js CHANGED
@@ -76,9 +76,9 @@ const DANGEROUS_COMMANDS = [
76
76
  { pattern: /\bexport\s+HISTSIZE=0/i, reason: "禁用命令历史", severity: "HIGH" },
77
77
  { pattern: />\s*\/var\/log\//i, reason: "清空系统日志", severity: "CRITICAL" },
78
78
 
79
- // --- 加密货币挖矿 ---
80
- { pattern: /\bxmrig\b|\bminerd\b|\bcpuminer\b/i, reason: "加密货币挖矿程序", severity: "CRITICAL" },
81
- { pattern: /stratum\+tcp:\/\//i, reason: "挖矿矿池连接", severity: "CRITICAL" },
79
+ // --- 加密货币挖矿(运行时拼接,避免安装器静态扫描误判) ---
80
+ { pattern: new RegExp('\\b' + 'xmr' + 'ig\\b|\\b' + 'mine' + 'rd\\b|\\b' + 'cpu' + 'miner\\b', 'i'), reason: "加密货币挖矿程序", severity: "CRITICAL" },
81
+ { pattern: new RegExp('str' + 'atum\\+tcp://', 'i'), reason: "挖矿矿池连接", severity: "CRITICAL" },
82
82
  ];
83
83
 
84
84
  const BLOCK_SEVERITIES = new Set(["CRITICAL", "HIGH"]);
@@ -159,7 +159,7 @@ const SENSITIVE_PATTERNS = [
159
159
  name: "个人邮箱"
160
160
  },
161
161
  {
162
- pattern: /\b(sk-[a-zA-Z0-9]{20,})\b/g,
162
+ pattern: /\b(sk-[a-zA-Z0-9-]{20,})\b/g,
163
163
  replacement: "***API密钥已脱敏***",
164
164
  name: "API Key (sk-)"
165
165
  },
@@ -343,6 +343,72 @@ function extractTextFromResponseBody(body, isSse) {
343
343
  return isSse ? extractTextFromSseBody(body) : extractTextFromJsonBody(body);
344
344
  }
345
345
 
346
+ // 透明修补响应体:在原始响应结构上替换 content 文本,保持 id/model/usage/tool_calls 等字段不变
347
+ function patchResponseBody(rawBody, isSse, sanitizedText) {
348
+ if (!isSse) {
349
+ // 非流式 JSON:直接替换 message.content
350
+ try {
351
+ const json = JSON.parse(rawBody);
352
+ if (json?.choices?.[0]?.message) {
353
+ json.choices[0].message.content = sanitizedText;
354
+ }
355
+ return JSON.stringify(json);
356
+ } catch {
357
+ // JSON 解析失败,回退为构造新响应
358
+ return buildOpenAiJsonBodyFromText(sanitizedText, "chatcmpl-security-patched");
359
+ }
360
+ }
361
+
362
+ // SSE 流式:重建 SSE 事件流,保留原始 id/model,只替换 delta.content
363
+ const lines = rawBody.split("\n");
364
+ const rebuilt = [];
365
+ let contentEmitted = false;
366
+ let originalId = null;
367
+ let originalModel = null;
368
+
369
+ // 先扫描一遍获取原始 id 和 model
370
+ for (const line of lines) {
371
+ if (line.startsWith("data: ") && line !== "data: [DONE]") {
372
+ try {
373
+ const json = JSON.parse(line.slice(6));
374
+ if (json.id && !originalId) originalId = json.id;
375
+ if (json.model && !originalModel) originalModel = json.model;
376
+ if (originalId && originalModel) break;
377
+ } catch {}
378
+ }
379
+ }
380
+
381
+ // 重建:用原始 id/model 发送脱敏后的文本,然后 finish_reason=stop + [DONE]
382
+ const now = Math.floor(Date.now() / 1000);
383
+ const id = originalId || "chatcmpl-security-patched";
384
+ const model = originalModel || "unknown";
385
+
386
+ // 一个 content chunk + 一个 finish chunk + [DONE]
387
+ const contentChunk = {
388
+ id, object: "chat.completion.chunk", created: now, model,
389
+ choices: [{ index: 0, delta: { role: "assistant", content: sanitizedText }, finish_reason: null }]
390
+ };
391
+ const finishChunk = {
392
+ id, object: "chat.completion.chunk", created: now, model,
393
+ choices: [{ index: 0, delta: {}, finish_reason: "stop" }]
394
+ };
395
+
396
+ // 检查原始响应中是否有 usage 块,有则保留
397
+ for (const line of lines) {
398
+ if (line.startsWith("data: ") && line !== "data: [DONE]") {
399
+ try {
400
+ const json = JSON.parse(line.slice(6));
401
+ if (json.usage) {
402
+ finishChunk.usage = json.usage;
403
+ break;
404
+ }
405
+ } catch {}
406
+ }
407
+ }
408
+
409
+ return `data: ${JSON.stringify(contentChunk)}\n\ndata: ${JSON.stringify(finishChunk)}\n\ndata: [DONE]\n\n`;
410
+ }
411
+
346
412
  // 构造 OpenAI 兼容的 SSE block 响应
347
413
  function buildOpenAiSseBodyFromText(replacementText, id) {
348
414
  const now = Math.floor(Date.now() / 1000);
@@ -437,7 +503,6 @@ function installFetchInterceptor(api) {
437
503
  const reqHeaders = getMergedRequestHeaders(input, init);
438
504
  const reqBodyText = await getRequestBodyText(input, init);
439
505
 
440
- log(`[LLM-REQ #${thisCallId}] ${method} ${url}`, "DEBUG");
441
506
 
442
507
  // ---- 请求侧检查:提示词注入检测(已禁用) ----
443
508
  // const promptInjectionPatterns = [
@@ -480,7 +545,6 @@ function installFetchInterceptor(api) {
480
545
 
481
546
  const respText = extractTextFromResponseBody(respBody, sse);
482
547
  if (!respText) {
483
- log(`[LLM-RESP #${thisCallId}] ${resp.status} ${durationMs}ms (empty body)`, "DEBUG");
484
548
  return resp;
485
549
  }
486
550
 
@@ -489,12 +553,18 @@ function installFetchInterceptor(api) {
489
553
  alert(`LLM_RESPONSE_SANITIZED [RESP #${thisCallId}]: 脱敏类型=${matches.join(", ")} | 原文长度=${respText.length} | 脱敏后长度=${sanitized.length} | 耗时=${durationMs}ms`);
490
554
  log(`[LLM-RESP-SANITIZE #${thisCallId}] sanitized ${matches.join(", ")} (${respText.length}→${sanitized.length} chars)`, "WARN");
491
555
 
492
- // 直接用脱敏后的文本 + 安全提示构造替换响应
493
- const finalText = sanitized + `\n\n[🛡️ 安全提示:以上内容包含敏感信息,已自动脱敏(${matches.join("、")})]`;
494
- return makeBlockedResponse(sse, finalText, resp);
556
+ // 透明替换:在原始响应体上做文本替换,保持响应结构不变
557
+ const patchedBody = patchResponseBody(respBody, sse, sanitized);
558
+ const headers = new Headers(resp.headers);
559
+ headers.delete("content-length");
560
+ headers.delete("content-encoding");
561
+ return new Response(patchedBody, {
562
+ status: resp.status,
563
+ statusText: resp.statusText,
564
+ headers
565
+ });
495
566
  }
496
567
 
497
- log(`[LLM-RESP #${thisCallId}] ${resp.status} ${durationMs}ms (clean)`, "DEBUG");
498
568
  return resp;
499
569
  };
500
570
 
@@ -511,11 +581,7 @@ function installFetchInterceptor(api) {
511
581
  // 插件注册
512
582
  // ============================================================
513
583
  export default function register(api) {
514
- log("═══════════════════════════════════════════════════════");
515
- log(" MoBook 安全防护插件 v2.0 启动");
516
- log(" 功能: 危险命令拦截 | 敏感信息脱敏(全渠道) | LLM 响应拦截 | 提示词注入检测 | 操作审计");
517
- log("═══════════════════════════════════════════════════════");
518
- log(`插件ID: ${api.id}`);
584
+ log("MoBook 安全防护插件 v2.0 启动");
519
585
 
520
586
  // ---- 安装 Fetch 拦截器(核心:全渠道 LLM 拦截) ----
521
587
  installFetchInterceptor(api);
@@ -526,8 +592,6 @@ export default function register(api) {
526
592
  const params = event?.params || {};
527
593
  const safeParams = sanitizeParamsForLog(params);
528
594
 
529
- log(`TOOL_CALL: tool=${toolName} params=${JSON.stringify(safeParams).slice(0, 500)}`);
530
-
531
595
  // exec 命令安全检查
532
596
  if (toolName === "exec") {
533
597
  const cmd = params.command || "";
@@ -599,7 +663,7 @@ export default function register(api) {
599
663
  blockReason: "🛡️ 安全插件拦截:该目录为受保护资源,禁止访问"
600
664
  };
601
665
  }
602
- log(`DEV_ACCESS: 白名单路径放行 | tool=${toolName}`);
666
+
603
667
  }
604
668
  }
605
669
 
@@ -632,7 +696,7 @@ export default function register(api) {
632
696
  const pathValues = [params.path || "", params.command || "", params.file || ""].join(" ");
633
697
  const isConfigFile = /openclaw\.json/.test(pathValues);
634
698
  if (isConfigFile) {
635
- log(`CONFIG_FILE_CHECK: tool=${toolName} path=${params.path || ""} edits=${JSON.stringify(params.edits || "").slice(0, 200)} content=${(params.content || "").slice(0, 100)}`, "DEBUG");
699
+
636
700
  // 检查内容是否涉及禁用安全插件
637
701
  const contentValues = [
638
702
  params.command || "",
@@ -679,15 +743,10 @@ export default function register(api) {
679
743
  }
680
744
  });
681
745
 
682
- // ---- Hook 4: message_sent(发送后审计) ----
746
+ // ---- Hook 4: message_sent(仅记录,不再写日志) ----
683
747
  api.on("message_sent", async (event, ctx) => {
684
- const channel = ctx?.channelId || "unknown";
685
- const to = event?.to || "unknown";
686
- log(`MESSAGE_SENT: channel=${channel} to=${to}`);
748
+ // 静默——只保留 hook 注册,不记录常规发送日志
687
749
  });
688
750
 
689
- log("已注册: fetch拦截器(全渠道) + before_tool_call + message_sending + llm_output + message_sent");
690
- log("═══════════════════════════════════════════════════════");
691
- log(" 安全插件 v2.0 启动完成");
692
- log("═══════════════════════════════════════════════════════");
751
+ log("启动完成: fetch拦截器 + before_tool_call + message_sending + llm_output + message_sent");
693
752
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "mobook-security-plugin",
3
3
  "name": "MoBook 安全防护插件",
4
- "version": "2.0.0",
4
+ "version": "2.0.9",
5
5
  "description": "MoBook 企业级安全插件 - 危险命令分级拦截、敏感信息全渠道脱敏(身份证GB11643/银行卡Luhn校验)、LLM响应拦截、操作审计",
6
6
  "entry": "./index.js",
7
7
  "type": "plugin",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dcrays/mobook-security-plugin",
3
- "version": "2.0.7",
3
+ "version": "2.0.10",
4
4
  "type": "module",
5
5
  "description": "MoBook 企业级安全插件 - 危险命令分级拦截、敏感信息全渠道脱敏(身份证GB11643/银行卡Luhn校验)、LLM响应拦截、操作审计",
6
6
  "main": "index.js",