@dcrays/mobook-security-plugin 2.0.8 → 2.0.11

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);
@@ -487,9 +553,16 @@ function installFetchInterceptor(api) {
487
553
  alert(`LLM_RESPONSE_SANITIZED [RESP #${thisCallId}]: 脱敏类型=${matches.join(", ")} | 原文长度=${respText.length} | 脱敏后长度=${sanitized.length} | 耗时=${durationMs}ms`);
488
554
  log(`[LLM-RESP-SANITIZE #${thisCallId}] sanitized ${matches.join(", ")} (${respText.length}→${sanitized.length} chars)`, "WARN");
489
555
 
490
- // 直接用脱敏后的文本 + 安全提示构造替换响应
491
- const finalText = sanitized + `\n\n[🛡️ 安全提示:以上内容包含敏感信息,已自动脱敏(${matches.join("、")})]`;
492
- 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
+ });
493
566
  }
494
567
 
495
568
  return resp;
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "mobook-security-plugin",
3
3
  "name": "MoBook 安全防护插件",
4
- "version": "2.0.8",
4
+ "version": "2.0.11",
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.8",
3
+ "version": "2.0.11",
4
4
  "type": "module",
5
5
  "description": "MoBook 企业级安全插件 - 危险命令分级拦截、敏感信息全渠道脱敏(身份证GB11643/银行卡Luhn校验)、LLM响应拦截、操作审计",
6
6
  "main": "index.js",