@dcrays/mobook-security-plugin 2.0.11 → 2.0.14

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
@@ -135,62 +135,87 @@ function verifyLuhn(cardNumber) {
135
135
  const ID_CARD_REGEX = /\b[1-9]\d{5}(?:19|20)\d{2}(?:0[1-9]|1[0-2])(?:0[1-9]|[12]\d|3[01])\d{3}[\dXx]\b/g;
136
136
  const BANK_CARD_REGEX = /\b[3-6]\d{15,18}\b/g;
137
137
 
138
+ // ---- 脱敏辅助:部分保留 + 星号遮盖 ----
139
+ function maskKeep(str, keepHead, keepTail = 0, minStars = 4) {
140
+ if (str.length <= keepHead + keepTail) return "\\*".repeat(str.length);
141
+ const stars = Math.max(minStars, str.length - keepHead - keepTail);
142
+ return str.slice(0, keepHead) + "\\*".repeat(stars) + (keepTail > 0 ? str.slice(-keepTail) : "");
143
+ }
144
+
145
+ // ---- 智能识别:检测文本是否已脱敏 ----
146
+ function looksLikeMasked(text) {
147
+ // 只识别"明显的脱敏模式":数字/字母 + 连续4个以上星号 + 数字/字母/@
148
+ // 例如:138****5678、1**********@163.com
149
+ // 不误判 Markdown 格式(---、**粗体**)
150
+ return /[a-zA-Z0-9]\*{4,}[a-zA-Z0-9@]/.test(text);
151
+ }
152
+
138
153
  const SENSITIVE_PATTERNS = [
154
+ // 优先级1:邮箱(避免被手机号规则破坏)
155
+ {
156
+ pattern: /\b([a-zA-Z0-9._%+-]+)@((?:qq|163|126|sina|sohu|gmail|outlook|hotmail|yahoo)\.[a-z]{2,})\b/gi,
157
+ replacer: (_m, local, domain) => maskKeep(local, 2, 0) + "@" + domain, // jq*****@gmail.com
158
+ name: "个人邮箱"
159
+ },
160
+ // 优先级2:身份证
139
161
  {
140
162
  pattern: ID_CARD_REGEX,
141
163
  validator: verifyIdCard,
142
- replacementText: "***身份证号已脱敏***",
164
+ replacer: (m) => maskKeep(m, 3, 4), // 310***********1234
143
165
  name: "身份证号"
144
166
  },
167
+ // 优先级3:手机号(排除邮箱上下文)
145
168
  {
146
- pattern: /\b1[3-9]\d{9}\b/g,
147
- replacement: "***手机号已脱敏***",
169
+ pattern: /\b1[3-9]\d{9}\b(?!@)/g, // 负向前瞻:后面不能是 @
170
+ replacer: (m) => maskKeep(m, 3, 4), // 138****5678
148
171
  name: "手机号"
149
172
  },
173
+ // 优先级4:银行卡
150
174
  {
151
175
  pattern: BANK_CARD_REGEX,
152
176
  validator: verifyLuhn,
153
- replacementText: "***银行卡号已脱敏***",
177
+ replacer: (m) => maskKeep(m, 4, 4), // 6222***********1234
154
178
  name: "银行卡号"
155
179
  },
156
- {
157
- pattern: /\b[a-zA-Z0-9._%+-]+@(qq|163|126|sina|sohu|gmail|outlook|hotmail|yahoo)\.[a-z]{2,}\b/gi,
158
- replacement: "***邮箱已脱敏***",
159
- name: "个人邮箱"
160
- },
161
180
  {
162
181
  pattern: /\b(sk-[a-zA-Z0-9-]{20,})\b/g,
163
- replacement: "***API密钥已脱敏***",
182
+ replacer: (m) => maskKeep(m, 5, 0), // sk-pr**************
164
183
  name: "API Key (sk-)"
165
184
  },
166
185
  {
167
186
  pattern: /\b(AKIA[0-9A-Z]{16})\b/g,
168
- replacement: "***AWS密钥已脱敏***",
187
+ replacer: (m) => maskKeep(m, 4, 0), // AKIA************
169
188
  name: "AWS Access Key"
170
189
  },
171
190
  {
172
191
  pattern: /\b(ghp_[a-zA-Z0-9]{36,})\b/g,
173
- replacement: "***GitHub Token已脱敏***",
192
+ replacer: (m) => maskKeep(m, 4, 0), // ghp_****...
174
193
  name: "GitHub Token"
175
194
  },
176
195
  {
177
196
  pattern: /(password|passwd|pwd|secret_key|secret|token|api_key|apikey|access_key|private_key)\s*[=:]\s*["']?[^\s"',]{6,}/gi,
178
- replacement: "$1=***已脱敏***",
197
+ replacer: (_m, key) => key + "=***", // password=***
179
198
  name: "密码/密钥字段"
180
199
  },
181
200
  {
182
201
  pattern: /\b(10\.\d{1,3}\.\d{1,3}\.\d{1,3}|172\.(1[6-9]|2\d|3[01])\.\d{1,3}\.\d{1,3}|192\.168\.\d{1,3}\.\d{1,3})\b/g,
183
- replacement: "***内网IP已脱敏***",
202
+ replacer: (m) => m.split(".")[0] + ".*.*.*", // 10.*.*.*
184
203
  name: "内网IP"
185
204
  },
186
205
  {
187
206
  pattern: /[^\s"']*\/extensions\/mobook-security-plugin[^\s"']*/g,
188
- replacement: "***插件路径已脱敏***",
207
+ replacer: () => "***插件路径已脱敏***",
189
208
  name: "安全插件路径"
190
209
  },
191
210
  ];
192
211
 
193
212
  function sanitizeContent(content) {
213
+ // 智能识别:如果整体内容看起来已脱敏,跳过处理
214
+ if (looksLikeMasked(content)) {
215
+ log(`SKIP: 检测到已脱敏文本,跳过二次脱敏 | 长度=${content.length}`);
216
+ return { sanitized: content, matches: [] };
217
+ }
218
+
194
219
  let sanitized = content;
195
220
  const matches = [];
196
221
 
@@ -198,19 +223,30 @@ function sanitizeContent(content) {
198
223
  rule.pattern.lastIndex = 0;
199
224
 
200
225
  if (rule.validator) {
226
+ // 有校验器的规则(身份证、银行卡等)
201
227
  let hasMatch = false;
202
- sanitized = sanitized.replace(rule.pattern, (match) => {
228
+ sanitized = sanitized.replace(rule.pattern, (...args) => {
229
+ const match = args[0];
230
+ // 跳过已脱敏的匹配
231
+ if (looksLikeMasked(match)) return match;
232
+
203
233
  if (rule.validator(match)) {
204
234
  hasMatch = true;
205
- return rule.replacementText;
235
+ return rule.replacer(...args);
206
236
  }
207
237
  return match;
208
238
  });
209
239
  if (hasMatch) matches.push(rule.name);
210
- } else if (rule.replacement) {
240
+ } else if (rule.replacer) {
241
+ // 使用 replacer 函数
211
242
  rule.pattern.lastIndex = 0;
212
- const repl = rule.replacement; // 闭包捕获
213
- const replaced = sanitized.replace(rule.pattern, () => repl);
243
+ const replaced = sanitized.replace(rule.pattern, (...args) => {
244
+ const match = args[0];
245
+ // 跳过已脱敏的匹配
246
+ if (looksLikeMasked(match)) return match;
247
+
248
+ return rule.replacer(...args);
249
+ });
214
250
  if (replaced !== sanitized) {
215
251
  matches.push(rule.name);
216
252
  sanitized = replaced;
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "id": "mobook-security-plugin",
3
3
  "name": "MoBook 安全防护插件",
4
- "version": "2.0.11",
4
+ "version": "2.0.14",
5
5
  "description": "MoBook 企业级安全插件 - 危险命令分级拦截、敏感信息全渠道脱敏(身份证GB11643/银行卡Luhn校验)、LLM响应拦截、操作审计",
6
6
  "entry": "./index.js",
7
7
  "type": "plugin",
8
8
  "configSchema": {}
9
- }
9
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dcrays/mobook-security-plugin",
3
- "version": "2.0.11",
3
+ "version": "2.0.14",
4
4
  "type": "module",
5
5
  "description": "MoBook 企业级安全插件 - 危险命令分级拦截、敏感信息全渠道脱敏(身份证GB11643/银行卡Luhn校验)、LLM响应拦截、操作审计",
6
6
  "main": "index.js",
@@ -20,4 +20,4 @@
20
20
  "./index.js"
21
21
  ]
22
22
  }
23
- }
23
+ }