@comate/zulu 1.3.3 → 1.3.4-beta.2

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.
Files changed (32) hide show
  1. package/comate-engine/assets/skills/auto-commit-comate/SKILL.md +42 -62
  2. package/comate-engine/assets/skills/auto-commit-comate/references/data_structures.md +69 -75
  3. package/comate-engine/assets/skills/auto-commit-comate/references/interaction_instruction.md +220 -0
  4. package/comate-engine/assets/skills/auto-commit-comate/scripts/build_git_commit_payload.py +195 -0
  5. package/comate-engine/assets/skills/auto-commit-comate/scripts/build_icafe_cards_payload.py +80 -0
  6. package/comate-engine/assets/skills/auto-commit-comate/scripts/cache_manager.py +69 -0
  7. package/comate-engine/assets/skills/auto-commit-comate/scripts/git_diff_cli.py +5 -0
  8. package/comate-engine/assets/skills/auto-commit-comate/scripts/match_card_cli.py +5 -1
  9. package/comate-engine/assets/skills/auto-commit-comate/scripts/payload_validators.py +309 -0
  10. package/comate-engine/assets/skills/code-security-comate/SKILL.md +2 -1
  11. package/comate-engine/assets/skills/code-security-comate/references/vul_repair-go_sql_injection.md +627 -5
  12. package/comate-engine/assets/skills/code-security-comate/references/vul_repair-java_sql_injection.md +545 -21
  13. package/comate-engine/assets/skills/code-security-comate/references/vul_repair-php_sql_injection.md +596 -13
  14. package/comate-engine/assets/skills/code-security-comate/references/vul_repair-python_sql_injection.md +480 -82
  15. package/comate-engine/assets/skills/code-security-comate/scripts/http_client.py +10 -2
  16. package/comate-engine/assets/skills/code-security-comate/scripts/repair_vulnerability.py +12 -10
  17. package/comate-engine/assets/skills/code-security-comate/scripts/report_chat.py +1 -1
  18. package/comate-engine/assets/skills/comate-docs-comate/SKILL.md +70 -105
  19. package/comate-engine/assets/skills/comate-docs-comate/references/doc-map-extended.md +52 -7
  20. package/comate-engine/assets/skills/comate-docs-comate/references/models-and-billing.md +45 -26
  21. package/comate-engine/assets/skills/comate-docs-comate/references/product-overview.md +60 -14
  22. package/comate-engine/assets/skills/create-image-comate/SKILL.md +7 -7
  23. package/comate-engine/assets/skills/get-ugate-token-comate/SKILL.md +2 -0
  24. package/comate-engine/node_modules/@comate/plugin-engine/dist/index.js +1 -1
  25. package/comate-engine/node_modules/@comate/plugin-host/dist/index-7HvPXRep.js +1 -0
  26. package/comate-engine/node_modules/@comate/plugin-host/dist/index.js +1 -1
  27. package/comate-engine/node_modules/@comate/plugin-host/dist/main.js +1 -1
  28. package/comate-engine/node_modules/@comate/plugin-host/dist/user-DVjAG3wH.js +44 -0
  29. package/comate-engine/node_modules/@comate/plugin-shared-internals/dist/index.js +8 -8
  30. package/comate-engine/server.js +378 -266
  31. package/dist/bundle/index.js +3 -3
  32. package/package.json +1 -1
@@ -38,8 +38,8 @@ public void processFileToSql(MultipartFile file, int recordId) throws Exception
38
38
  1. 基于当前数据流代码文件中进行漏洞修复,不要创建新的用于代码过滤的工具类文件,例如:在需要过滤表名列名的情况下,直接在当前 java 文件中建立通用的过滤函数,并在需要过滤的地方使用。(过滤方式参考过滤方式规则)
39
39
  2. 同个文件进行参数过滤时,可以在文件头定义过滤正则或者过滤函数,如果定义已经存在,直接复用。
40
40
  3. 当修复需要对**函数签名修改时,需要对函数进行重载**,避免修改函数签名导致原业务编译使用异常。
41
- 4. 污点表名、列名、视图名称、DataBase 名称、order 信息、orderby 信息等拼接进入SQL 语句时需要进行过滤的方式修复漏洞,过滤使用的正则:"^[\\w\\s\\[\\]\"`$.,*]+$"。(XML/JSON配置中可能为"^[\\w\\s\\[\\]\\\"`$.,*]+$", 注意这里的引号转义问题,避免转义失败导致语法错误)
42
- 5. show、create 、drop、grant 语句场景不支持预编译参数方式修复漏洞,需要使用对污点参数进行过滤的方法进行修复,过滤使用的正则:"^[\\w\\s\\[\\]\"`$.,*]+$"。(XML/JSON配置中可能为"^[\\w\\s\\[\\]\\\"`$.,*]+$", 注意这里的引号转义问题,避免转义失败导致语法错误)
41
+ 4. 污点表名、列名、视图名称、DataBase 名称、order 信息、orderby 信息等拼接进入SQL 语句时需要进行过滤的方式修复漏洞。
42
+ 5. show、create 、drop、grant 语句场景不支持预编译参数方式修复漏洞,需要使用对污点参数进行过滤的方法进行修复。
43
43
  6. SQL 语句拼接 in 的参数时,需要对其中的每个子项进行过滤或者预编译,如果适合修改成预编译,则**按照参数个数的对 in () 中的内容进行 ? 替换**(**即每个子项均进行参数预编译,NamedParameterJdbcTemplate和**`JPQL`**支持 in (:ids) 的除外**),将每个参数放入查询参数中,优先进行预编译参数绑定。
44
44
  7. 在 where 语句场景 field 操作符 value,如果 field 或者 操作符为污点信息,则进行过滤,如果 value 为污点数据,则进行预编译操作。
45
45
  8. 当污点拼接进入 sql 语句时,在分析确认需要通过预编译修复的时候,需要考虑 sql 语句的所在位置,例如有时 sql 语句为全局变量赋值到局部变量,改成预编译执行时,需要将当前函数变更为预编译方式,且需要变更全局 sql 语句为预编译方式,避免遗漏导致用户执行出错。
@@ -106,20 +106,22 @@ public void processFileToSql(MultipartFile file, int recordId) throws Exception
106
106
  ```
107
107
  3、**MyBatis/ORM 场景下对象属性过滤的特殊处理**:当污点数据通过对象属性传递到 MyBatis XML(如 `${page.orderBy}`、`${entity.tableName}` 等)时,需要根据赋值和读取方式选择正确的过滤位置:
108
108
 
109
- | 赋值方式 | 读取方式 | 过滤位置 |
110
- |---------|---------|---------|
111
- | 调用 setter | MyBatis `${obj.field}` 隐式访问 | **setter 中过滤** |
112
- | 调用 setter | 代码显式调用 getter | **setter 和 getter 都过滤** |
113
- | 构造函数/反射赋值 | 代码显式调用 getter | **getter 中过滤**(构造函数也建议加) |
114
- | 构造函数/反射赋值 | MyBatis `${obj.field}` 隐式访问 | **构造函数中过滤** |
109
+ |赋值方式|读取方式|过滤位置|
110
+ |-|-|-|
111
+ |调用 setter|MyBatis `${obj.field}` 隐式访问|**setter 中过滤**|
112
+ |调用 setter|代码显式调用 getter|**setter 和 getter 都过滤**|
113
+ |构造函数/反射赋值|代码显式调用 getter|**getter 中过滤**(构造函数也建议加)|
114
+ |构造函数/反射赋值|MyBatis `${obj.field}` 隐式访问|**构造函数中过滤**|
115
115
 
116
116
  **原理说明**:静态分析器存在两个追踪限制:
117
+
117
118
  1. **隐式调用不可见**:无法追踪 MyBatis OGNL 表达式对 getter 的隐式调用(如 `${obj.field}`),因此 getter 中的过滤对 MyBatis 隐式访问无效,必须在 setter/构造函数等赋值入口过滤。
118
119
  2. **对象级污点追踪**:当整个对象被标记为污点(如 Spring Controller 的 `@RequestBody` 参数),即使 setter 中已对某个字段做了过滤,后续通过显式 getter 调用获取该字段时,分析器仍可能因对象级污点而认为返回值是污点。此时需要在 getter 中也加过滤,让分析器在追踪 getter 方法体时命中清洗规则。
119
120
 
120
121
  **最佳实践:在 setter 和 getter 中同时过滤**,确保无论是 MyBatis 隐式访问还是代码显式调用 getter,都能被静态分析器识别到防护措施。构造函数中如有直接赋值也需过滤。
121
122
 
122
123
  **示例1:setter 赋值 + MyBatis 隐式访问 / 显式 getter 调用(setter 和 getter 都过滤)**
124
+
123
125
  ```java
124
126
  // 原始代码(有漏洞)
125
127
  public class Page<T> {
@@ -137,7 +139,7 @@ public class Page<T> {
137
139
  // 修复后(setter 和 getter 都过滤)
138
140
  public class Page<T> {
139
141
  private String orderBy;
140
- private static final Pattern SAFE_PATTERN = Pattern.compile("^[\\w\\s\\[\\]\"`$.,*]+$");
142
+ private static final Pattern SAFE_PATTERN = Pattern.compile("^(\\*|((`[^`]+`|\"[^\"]+\"|\\w+\\.\\w+|\\w+)(,\\s*(`[^`]+`|\"[^\"]+\"|\\w+\\.\\w+|\\w+))*))$");
141
143
 
142
144
  private static String safeFilterOrderBy(String input) {
143
145
  if (input == null || input.isEmpty()) return "";
@@ -154,8 +156,8 @@ public class Page<T> {
154
156
  }
155
157
  }
156
158
  ```
157
-
158
159
  **示例2:构造函数赋值 + 显式 getter 调用(构造函数、setter、getter 都过滤)**
160
+
159
161
  ```java
160
162
  // 原始代码(有漏洞)
161
163
  public class Page<T> {
@@ -169,7 +171,7 @@ public class Page<T> {
169
171
  // 修复后(所有入口和出口都过滤)
170
172
  public class Page<T> {
171
173
  private String orderBy;
172
- private static final Pattern SAFE_PATTERN = Pattern.compile("^[\\w\\s\\[\\]\"`$.,*]+$");
174
+ private static final Pattern SAFE_PATTERN = Pattern.compile("^(\\*|((`[^`]+`|\"[^\"]+\"|\\w+\\.\\w+|\\w+)(,\\s*(`[^`]+`|\"[^\"]+\"|\\w+\\.\\w+|\\w+))*))$");
173
175
 
174
176
  private static String safeFilterOrderBy(String input) {
175
177
  if (input == null || input.isEmpty()) return "";
@@ -190,9 +192,532 @@ public class Page<T> {
190
192
  }
191
193
  }
192
194
  ```
193
-
194
195
  **注意**:这种模式适用于所有通过 ORM/模板引擎(MyBatis、Hibernate、Freemarker 等)访问的对象属性。setter 和 getter 都加过滤虽然看似冗余,但因为静态分析器的两种限制(隐式调用不可见 + 对象级污点追踪),这是确保所有场景都能被识别到防护的最可靠方式。
195
196
 
197
+ ##### SQLi 正则过滤规范
198
+ ###### 核心决策树
199
+ ```
200
+ 用户输入需要过滤?
201
+
202
+ ├─ 是 SQL 标识符(表名/列名/数据库名/索引名)?
203
+ │ └─ → 正则验证格式 + 白名单验证(双重保障)
204
+
205
+ ├─ 是 SELECT 列表?
206
+ │ └─ → 正则验证格式 + 提取列名后白名单验证
207
+
208
+ ├─ 是 WHERE 条件/IN 子句?
209
+ │ └─ → 使用参数化查询(强烈推荐)
210
+
211
+ ├─ 是 ORDER BY/GROUP BY?
212
+ │ └─ → 正则验证格式 + 提取列名后白名单验证
213
+
214
+ ├─ 是 LIMIT/OFFSET?
215
+ │ └─ → 正则验证纯数字
216
+
217
+ └─ 其他场景
218
+ └─ → 根据具体需求选择白名单或精确正则
219
+ ```
220
+ ---
221
+
222
+ ###### 白名单数据来源与处理策略
223
+ ###### 白名单数据从哪里来?
224
+ |来源|说明|示例|
225
+ |-|-|-|
226
+ |**数据库元数据查询**|从数据库系统表查询实际存在的表/列名|`SELECT column_name FROM information_schema.columns WHERE table_name = 'users'`|
227
+ |**代码静态分析**|扫描代码中定义的实体类、ORM 模型|JPA `@Entity`、MyBatis mapper、SQLAlchemy Model|
228
+ |**配置文件/注解**|从配置文件或注解中提取允许的字段列表|`@AllowedFields({"id", "name", "email"})`|
229
+ |**API 文档/接口定义**|从 Swagger/OpenAPI 等文档中提取字段定义||
230
+
231
+ ###### 白名单不完整时的处理策略
232
+ ```
233
+ 白名单验证失败?
234
+
235
+ ├─ 白名单数据来源明确(如元数据查询、ORM 模型)?
236
+ │ └─ → 拒绝输入,记录日志,提示用户
237
+
238
+ ├─ 白名单数据来源不明确(如硬编码、不完整)?
239
+ │ └─ → 降级为仅正则验证,记录警告日志
240
+
241
+ └─ 无法获取白名单(如动态表名)?
242
+ └─ → 仅正则验证格式,加强输入长度限制
243
+ ```
244
+ **AI 修复决策原则**:
245
+
246
+ 1. **优先获取白名单**:如果代码中有 ORM 模型或实体类定义,优先从中提取字段列表
247
+ 2. **正则兜底**:当白名单获取困难或不完整时,使用严格正则作为安全底线
248
+ 3. **记录决策原因**:在代码注释中说明为什么选择正则而非白名单
249
+
250
+ ---
251
+
252
+ ###### 不同数据库的差异说明
253
+ |数据库|标识符引用符|参数占位符|特殊注意事项|
254
+ |-|-|-|-|
255
+ |**MySQL**|反引号 ```|`?` 或 `:name`|支持反引号包裹标识符,如 `user_name`|
256
+ |**PostgreSQL**|双引号 `"`|`$1, $2, ...` 或 `?`|支持双引号包裹标识符,如 `"user_name"`|
257
+ |**SQL Server**|方括号 `[` `]`|`@param` 或 `?`|支持方括号包裹标识符,如 `[user_name]`|
258
+ |**Oracle**|双引号 `"`|`:name` 或 `?`|标识符默认大写,双引号区分大小写|
259
+ |**SQLite**|双引号 `"` 或方括号 `[` `]`|`?` 或 `:name`|兼容多种引用符|
260
+
261
+ **AI 修复原则**:根据当前使用的数据库类型,选择对应的引用符和参数占位符。
262
+
263
+ ---
264
+
265
+ ###### 选择策略对比
266
+ |场景|推荐策略|原因|
267
+ |-|-|-|
268
+ |SQL 标识符(表名/列名/数据库名/索引名)|正则 + 白名单|格式验证 + 值验证,双重保障|
269
+ |SELECT 列表|正则 + 白名单|允许通配符 `*` 和多列,列名需白名单验证|
270
+ |ORDER BY/GROUP BY|正则 + 白名单|格式验证 + 列名白名单 + 方向关键字(ASC/DESC)|
271
+ |LIMIT/OFFSET|严格正则|只允许数字,不需要白名单|
272
+ |WHERE 条件|**参数化查询**|正则过滤仍有风险,参数化最安全|
273
+ |IN 子句|参数化查询优先|优先参数化,其次严格正则|
274
+
275
+ ---
276
+
277
+ ###### 各场景正则与实现
278
+ ###### 1. SELECT 列名列表 `__SELECT_FILTER__`
279
+ **适用场景**:SELECT 后面的列名列表(如 `SELECT id,name,email FROM users` 或 `SELECT users.id,users.name FROM users`)
280
+
281
+ **正则(通用版,支持引用符)**:
282
+
283
+ ```
284
+ ^(\*|((`[^`]+`|"[^"]+"|\w+\.\w+|\w+)(,\s*(`[^`]+`|"[^"]+"|\w+\.\w+|\w+))*))$
285
+ ```
286
+ **支持格式**:
287
+
288
+ * `*` (通配符)
289
+ * `id` (单列)
290
+ * `users.id` (表名.列名)
291
+ * `user_name` (MySQL 反引号)
292
+ * `"user_name"` (PostgreSQL/Oracle 双引号)
293
+ * `id,name,email` (多列,逗号分隔)
294
+ * `users.id, users.name, users.email` (带表名前缀的多列)
295
+ * `id, name, email` (允许逗号前后空格)
296
+
297
+ **AI 修复指引**:
298
+
299
+ ```java
300
+ import java.util.regex.Pattern;
301
+ import java.util.Set;
302
+
303
+ // 定义过滤函数
304
+ public static String safeFilterSelect(String select, Set<String> allowedColumns) {
305
+ // 1. 先 trim() 去除首尾空白
306
+ select = select.trim();
307
+ // 2. 正则验证格式(支持引用符)
308
+ String regex = "^(\\*|((`[^`]+`|\"[^\"]+\"|\\w+\\.\\w+|\\w+)(,\\s*(`[^`]+`|\"[^\"]+\"|\\w+\\.\\w+|\\w+))*))$";
309
+ if (!select.matches(regex)) {
310
+ return null; // 或抛出异常
311
+ }
312
+ // 3. 如果是通配符 *,直接允许
313
+ if ("*".equals(select)) {
314
+ return select;
315
+ }
316
+ // 4. 提取列名并验证白名单
317
+ String[] parts = select.split("\\s*,\\s*");
318
+ for (String part : parts) {
319
+ // 去除引用符
320
+ String cleanPart = part.replaceAll("^[`\"]|[`\"]$", "");
321
+ // 处理表名.列名格式,提取列名部分
322
+ String column = cleanPart.contains(".") ? cleanPart.substring(cleanPart.lastIndexOf(".") + 1) : cleanPart;
323
+ // 白名单验证(如果白名单不完整,跳过此步仅使用正则)
324
+ if (allowedColumns != null && !allowedColumns.isEmpty()) {
325
+ if (!allowedColumns.contains(column.toLowerCase().trim())) {
326
+ return null; // 或抛出异常
327
+ }
328
+ }
329
+ }
330
+ return select;
331
+ }
332
+ ```
333
+ ---
334
+
335
+ ###### 2. SQL 标识符(表名/列名/数据库名/索引名)`__IDENTIFIER_FILTER__`
336
+ **适用场景**:表名、列名、数据库名、索引名、视图名等 SQL 标识符
337
+
338
+ **正则(通用版,支持引用符)**:
339
+
340
+ ```
341
+ ^[A-Za-z_][A-Za-z0-9_]{0,127}$|^`[^`]{1,128}`$|^"[^"]{1,128}"$
342
+ ```
343
+ **支持格式**:
344
+
345
+ * `user` (小写字母开头)
346
+ * `User123` (字母数字)
347
+ * `user_name` (下划线)
348
+ * `_private` (下划线开头,部分数据库支持)
349
+ * `user_name` (MySQL 反引号)
350
+ * `"user_name"` (PostgreSQL/Oracle 双引号)
351
+ * `[user_name]` (SQL Server 方括号)
352
+ * 长度限制:1-128 字符
353
+
354
+ **AI 修复指引**:
355
+
356
+ ```java
357
+ import java.util.regex.Pattern;
358
+ import java.util.Set;
359
+
360
+ // 定义过滤函数
361
+ public static String safeFilterIdentifier(String identifier, Set<String> allowedIdentifiers) {
362
+ // 1. 先 trim() 去除首尾空白
363
+ identifier = identifier.trim();
364
+ // 2. 正则验证格式(支持引用符)
365
+ String regex = "^[A-Za-z_][A-Za-z0-9_]{0,127}$|^`[^`]{1,128}`$|^\"[^\"]{1,128}\"$|^\\[[^\\]]{1,128}\\]$";
366
+ if (!identifier.matches(regex)) {
367
+ return null; // 或抛出异常
368
+ }
369
+ // 3. 去除引用符
370
+ String cleanIdentifier = identifier.replaceAll("^[`\"\\[]|[`\"\\]]$", "");
371
+ // 4. 白名单验证(如果白名单不完整,跳过此步仅使用正则)
372
+ if (allowedIdentifiers != null && !allowedIdentifiers.isEmpty()) {
373
+ if (!allowedIdentifiers.contains(cleanIdentifier.toLowerCase())) {
374
+ return null; // 或抛出异常
375
+ }
376
+ }
377
+ return identifier;
378
+ }
379
+ ```
380
+ **说明**:
381
+
382
+ * 表名、列名、数据库名、索引名等标识符都遵循相同的命名规范
383
+ * 正则验证格式 + 白名单验证允许的值,双重保障
384
+ * 如果白名单不完整或无法获取,可省略步骤 4,仅使用正则验证
385
+
386
+ ---
387
+
388
+ ###### 3. ORDER BY `__ORDER_FILTER__`
389
+ **适用场景**:ORDER BY 后的字段(如 `ORDER BY name ASC, id DESC` 或 `ORDER BY users.name ASC, users.id DESC`)
390
+
391
+ **正则(通用版,支持引用符)**:
392
+
393
+ ```
394
+ ^((`[^`]+`|"[^"]+"|\w+\.\w+|\w+)(\s+(ASC|DESC))?(,\s*(`[^`]+`|"[^"]+"|\w+\.\w+|\w+)(\s+(ASC|DESC))?)*)$
395
+ ```
396
+ **支持格式**:
397
+
398
+ * `name` (单列)
399
+ * `users.name` (表名.列名)
400
+ * `user_name` (MySQL 反引号)
401
+ * `"user_name"` (PostgreSQL/Oracle 双引号)
402
+ * `name ASC` / `name DESC` (带方向)
403
+ * `users.name ASC` / `users.name DESC` (带表名前缀和方向)
404
+ * `name ASC, id DESC` (多列)
405
+ * `users.name ASC, users.id DESC` (带表名前缀的多列)
406
+
407
+ **AI 修复指引**:
408
+
409
+ ```java
410
+ import java.util.regex.Pattern;
411
+ import java.util.Set;
412
+
413
+ // 定义过滤函数
414
+ public static String safeFilterOrderBy(String order, Set<String> allowedColumns) {
415
+ // 1. 正则验证格式(不区分大小写)
416
+ String regex = "^((`[^`]+`|\"[^\"]+\"|\\w+\\.\\w+|\\w+)(\\s+(ASC|DESC))?(,\\s*(`[^`]+`|\"[^\"]+\"|\\w+\\.\\w+|\\w+)(\\s+(ASC|DESC))?)*)$";
417
+ Pattern pattern = Pattern.compile(regex, Pattern.CASE_INSENSITIVE);
418
+ if (!pattern.matcher(order).matches()) {
419
+ return null; // 或抛出异常
420
+ }
421
+ // 2. 提取列名并验证白名单
422
+ String[] parts = order.split("\\s*,\\s*");
423
+ for (String part : parts) {
424
+ // 提取列名部分(处理表名.列名格式)
425
+ String columnPart = part.split("\\s+")[0];
426
+ // 去除引用符
427
+ String cleanPart = columnPart.replaceAll("^[`\"]|[`\"]$", "");
428
+ String column = cleanPart.contains(".") ? cleanPart.substring(cleanPart.lastIndexOf(".") + 1) : cleanPart;
429
+ // 白名单验证(如果白名单不完整,跳过此步仅使用正则)
430
+ if (allowedColumns != null && !allowedColumns.isEmpty()) {
431
+ if (!allowedColumns.contains(column.toLowerCase())) {
432
+ return null; // 或抛出异常
433
+ }
434
+ }
435
+ }
436
+ return order;
437
+ }
438
+ ```
439
+ ---
440
+
441
+ ###### 4. GROUP BY `__GROUP_FILTER__`
442
+ **适用场景**:GROUP BY 后的字段(如 `GROUP BY name, age` 或 `GROUP BY users.name, users.age`)
443
+
444
+ **正则(通用版,支持引用符)**:
445
+
446
+ ```
447
+ ^((`[^`]+`|"[^"]+"|\w+\.\w+|\w+)(,\s*(`[^`]+`|"[^"]+"|\w+\.\w+|\w+))*)$
448
+ ```
449
+ **支持格式**:
450
+
451
+ * `name` (单列)
452
+ * `users.name` (表名.列名)
453
+ * `user_name` (MySQL 反引号)
454
+ * `"user_name"` (PostgreSQL/Oracle 双引号)
455
+ * `name, age` (多列)
456
+ * `users.name, users.age` (带表名前缀的多列)
457
+
458
+ **AI 修复指引**:
459
+
460
+ ```java
461
+ import java.util.Set;
462
+
463
+ // 定义过滤函数
464
+ public static String safeFilterGroupBy(String group, Set<String> allowedColumns) {
465
+ // 1. 正则验证格式
466
+ String regex = "^((`[^`]+`|\"[^\"]+\"|\\w+\\.\\w+|\\w+)(,\\s*(`[^`]+`|\"[^\"]+\"|\\w+\\.\\w+|\\w+))*)$";
467
+ if (!group.matches(regex)) {
468
+ return null; // 或抛出异常
469
+ }
470
+ // 2. 提取列名并验证白名单
471
+ String[] parts = group.split("\\s*,\\s*");
472
+ for (String part : parts) {
473
+ // 去除引用符
474
+ String cleanPart = part.replaceAll("^[`\"]|[`\"]$", "");
475
+ // 提取列名部分(处理表名.列名格式)
476
+ String column = cleanPart.contains(".") ? cleanPart.substring(cleanPart.lastIndexOf(".") + 1) : cleanPart;
477
+ // 白名单验证(如果白名单不完整,跳过此步仅使用正则)
478
+ if (allowedColumns != null && !allowedColumns.isEmpty()) {
479
+ if (!allowedColumns.contains(column.toLowerCase().trim())) {
480
+ return null; // 或抛出异常
481
+ }
482
+ }
483
+ }
484
+ return group;
485
+ }
486
+ ```
487
+ ---
488
+
489
+ ###### 5. LIMIT/OFFSET `__LIMIT_FILTER__`
490
+ **适用场景**:LIMIT、OFFSET 后的数值(如 `LIMIT 10`、`LIMIT 1,10`)
491
+
492
+ **正则**:`^\d+\s*,\s*\d+$|^\d+$`
493
+
494
+ **支持格式**:
495
+
496
+ * `10` (单数字)
497
+ * `1,10` (带偏移量)
498
+ * `1, 10` (允许逗号前后空格)
499
+
500
+ **AI 修复指引**:
501
+
502
+ ```java
503
+ // 定义过滤函数
504
+ public static String safeFilterLimit(String limit) {
505
+ // 1. 先 trim() 去除首尾空白
506
+ limit = limit.trim();
507
+ // 2. 正则验证纯数字
508
+ if (limit.matches("^\\d+\\s*,\\s*\\d+$|^\\d+$")) {
509
+ return limit;
510
+ }
511
+ return null; // 或抛出异常
512
+ }
513
+ ```
514
+ ---
515
+
516
+ ###### 6. WHERE 条件 `__WHERE_FILTER__`
517
+ **适用场景**:WHERE 子句、条件判断
518
+
519
+ **策略**:**放弃正则,使用参数化查询**
520
+
521
+ **AI 修复指引**:
522
+
523
+ ```java
524
+ // ❌ 错误:字符串拼接
525
+ String sql = "SELECT * FROM users WHERE name = '" + name + "'";
526
+
527
+ // ✅ 正确:参数化查询(MySQL/SQLite 使用 ?)
528
+ String sql = "SELECT * FROM users WHERE name = ?";
529
+ PreparedStatement stmt = conn.prepareStatement(sql);
530
+ stmt.setString(1, name);
531
+
532
+ // ✅ 正确:参数化查询(PostgreSQL 使用 $1, $2)
533
+ String sql = "SELECT * FROM users WHERE name = $1";
534
+ PreparedStatement stmt = conn.prepareStatement(sql);
535
+ stmt.setString(1, name);
536
+
537
+ // ✅ 正确:参数化查询(SQL Server 使用 @param)
538
+ String sql = "SELECT * FROM users WHERE name = @name";
539
+ PreparedStatement stmt = conn.prepareStatement(sql);
540
+ stmt.setString("@name", name);
541
+ ```
542
+ ---
543
+
544
+ ###### 7. IN 子句 `__IN_FILTER__`
545
+ **适用场景**:IN 后面的值列表(如 `IN (1,2,3)`)
546
+
547
+ **策略**:优先参数化查询,其次严格正则
548
+
549
+ **正则**:
550
+
551
+ * 数字:`^\(\s*\d+(\s*,\s*\d+)*\s*\)$`
552
+ * 字符串:`^\(\s*'[^']*'(\s*,\s*'[^']*')*\s*\)$`
553
+
554
+ **支持格式**:
555
+
556
+ * `(1,2,3)` (数字列表)
557
+ * `('a','b','c')` (字符串列表)
558
+ * `(1, 2, 3)` (允许逗号前后空格)
559
+
560
+ **AI 修复指引**:
561
+
562
+ ```java
563
+ // ✅ 优先:参数化查询(MySQL/SQLite 使用 ?)
564
+ String sql = "SELECT * FROM users WHERE id IN (?, ?, ?)";
565
+ PreparedStatement stmt = conn.prepareStatement(sql);
566
+ stmt.setInt(1, id1);
567
+ stmt.setInt(2, id2);
568
+ stmt.setInt(3, id3);
569
+
570
+ // ✅ 优先:参数化查询(PostgreSQL 使用 $1, $2)
571
+ String sql = "SELECT * FROM users WHERE id IN ($1, $2, $3)";
572
+ PreparedStatement stmt = conn.prepareStatement(sql);
573
+ stmt.setInt(1, id1);
574
+ stmt.setInt(2, id2);
575
+ stmt.setInt(3, id3);
576
+
577
+ // ⚠️ 备选:严格正则验证(仅当无法参数化时)
578
+ String inClause = inClause.trim();
579
+ if (!inClause.matches("^\\(\\s*\\d+(\\s*,\\s*\\d+)*\\s*\\)$")) {
580
+ return false;
581
+ }
582
+ ```
583
+ ---
584
+
585
+ ###### 8. 方向关键字 `__DIRECTION_FILTER__`
586
+ **适用场景**:ORDER BY 后的 ASC/DESC
587
+
588
+ **正则**:`^(asc|desc)$`
589
+
590
+ **支持格式**:
591
+
592
+ * `ASC` / `DESC` / `asc` / `desc` (不区分大小写)
593
+
594
+ **AI 修复指引**:
595
+
596
+ ```java
597
+ import java.util.regex.Pattern;
598
+
599
+ // 定义过滤函数
600
+ public static String safeFilterDirection(String direction) {
601
+ // 正则验证(不区分大小写)
602
+ Pattern pattern = Pattern.compile("^(asc|desc)$", Pattern.CASE_INSENSITIVE);
603
+ if (pattern.matcher(direction).matches()) {
604
+ return direction.toUpperCase();
605
+ }
606
+ return null; // 或抛出异常
607
+ }
608
+ ```
609
+ ---
610
+
611
+ ###### 9. 纯字母数字标识符 `__W_FILTER__`
612
+ **适用场景**:简单的表名、列名(无特殊字符)
613
+
614
+ **正则**:`^[\w-]+$`
615
+
616
+ **支持格式**:
617
+
618
+ * `user_name` (下划线)
619
+ * `User123` (字母数字)
620
+ * `table-1` (连字符)
621
+
622
+ **AI 修复指引**:
623
+
624
+ ```java
625
+ // 定义过滤函数
626
+ public static String safeFilterSimpleIdentifier(String identifier) {
627
+ if (identifier.matches("^[\\w-]+$")) {
628
+ return identifier;
629
+ }
630
+ return null; // 或抛出异常
631
+ }
632
+ ```
633
+ ---
634
+
635
+ ###### 10. 路径过滤 `__PATH_FILTER__`
636
+ **适用场景**:文件路径、URL 路径
637
+
638
+ **正则**:`^[\w/-]+$`
639
+
640
+ **支持格式**:
641
+
642
+ * `/path/to/file` (绝对路径)
643
+ * `path/to/file` (相对路径)
644
+
645
+ **AI 修复指引**:
646
+
647
+ ```java
648
+ // 定义过滤函数
649
+ public static String safeFilterPath(String path) {
650
+ if (path.matches("^[\\w/-]+$")) {
651
+ return path;
652
+ }
653
+ return null; // 或抛出异常
654
+ }
655
+ ```
656
+ ---
657
+
658
+ ###### 11. 运算符 `__OP_FILTER__`
659
+ **适用场景**:算术运算符(如 `score+1`)
660
+
661
+ **正则**:`[+\-*/%]`
662
+
663
+ **支持格式**:
664
+
665
+ * `+` `-` `*` `/` `%` (基本算术运算符)
666
+
667
+ **AI 修复指引**:
668
+
669
+ ```java
670
+ // 定义过滤函数
671
+ public static String safeFilterOperator(String operator) {
672
+ if (operator.matches("^[+\\-*/%]$")) {
673
+ return operator;
674
+ }
675
+ return null; // 或抛出异常
676
+ }
677
+ ```
678
+ ---
679
+
680
+ ###### 正则速查表
681
+ |过滤器|正则|说明|
682
+ |-|-|-|
683
+ |`__IDENTIFIER_FILTER__`|`^[A-Za-z_][A-Za-z0-9_]{0,127}$\|^`[^`]{1,128}`$\|^"[^"]{1,128}"$`|SQL 标识符,支持反引号/双引号,1-128字符|
684
+ |`__SELECT_FILTER__`|`^(\*\|((`[^`]+`\|"[^"]+"\|\w+\.\w+\|\w+)(,\s*(`[^`]+`\|"[^"]+"\|\w+\.\w+\|\w+))*))$`|允许通配符 `*` 和逗号分隔的列名|
685
+ |`__ORDER_FILTER__`|`^((`[^`]+`\|"[^"]+"\|\w+\.\w+\|\w+)(\s+(ASC\|DESC))?(,\s*(`[^`]+`\|"[^"]+"\|\w+\.\w+\|\w+)(\s+(ASC\|DESC))?)*)$`|列名 + 方向关键字(不区分大小写)|
686
+ |`__GROUP_FILTER__`|`^((`[^`]+`\|"[^"]+"\|\w+\.\w+\|\w+)(,\s*(`[^`]+`\|"[^"]+"\|\w+\.\w+\|\w+))*)$`|多列,逗号分隔|
687
+ |`__LIMIT_FILTER__`|`^\d+\s*,\s*\d+$\|^\d+$`|纯数字,允许逗号|
688
+ |`__WHERE_FILTER__`|**参数化查询**|不要用正则|
689
+ |`__IN_FILTER__`|`^\(\s*\d+(\s*,\s*\d+)*\s*\)$`|数字列表|
690
+ |`__DIRECTION_FILTER__`|`^(asc\|desc)$`|ASC 或 DESC(不区分大小写)|
691
+ |`__W_FILTER__`|`^[\w-]+$`|字母数字+下划线+连字符|
692
+ |`__PATH_FILTER__`|`^[\w/-]+$`|路径字符|
693
+ |`__OP_FILTER__`|`^[+\-*/%]$`|算术运算符|
694
+
695
+ **Java 正则使用注意事项**:
696
+ ```java
697
+ // 1. Java 字符串中反斜杠需要双重转义
698
+ String regex = "^\\w+$"; // 匹配 \w+
699
+
700
+ // 2. String.matches() 不支持传入 Pattern 标志,需使用 Pattern.compile()
701
+ // 错误写法:
702
+ // str.matches(regex, Pattern.CASE_INSENSITIVE); // 编译错误!
703
+ // 正确写法:
704
+ Pattern pattern = Pattern.compile(regex, Pattern.CASE_INSENSITIVE);
705
+ boolean matched = pattern.matcher(str).matches();
706
+
707
+ // 3. 反引号 ` 在 Java 正则中不需要转义
708
+ String regex = "^`[^`]+`$"; // 匹配反引号包裹的标识符
709
+ ```
710
+
711
+ ---
712
+
713
+ ###### 重要提示
714
+ 1. **所有正则验证前,务必先 **`trim()`** 去除首尾空白字符**
715
+ 2. **白名单优先**:列名/表名等固定值优先用白名单
716
+ 3. **白名单不完整时降级**:当白名单获取困难或不完整时,使用严格正则作为安全底线
717
+ 4. **参数化优先**:WHERE 条件、IN 子句优先用参数化查询
718
+ 5. **数据库适配**:根据使用的数据库类型选择对应的引用符和参数占位符
719
+ 6. **最小化改动**:只修复漏洞点,不重构其他代码
720
+
196
721
  ### 修复后的检查点
197
722
  1. 检查补丁代码其中参数是否在当前函数范围。
198
723
  2. 检查补丁代码是否存在语法异常问题,例如正则中引号转义失败,导致非预期字符串闭合问题。
@@ -286,15 +811,16 @@ public class TestServiceImp implements TestService {
286
811
 
287
812
 
288
813
  ```java
289
- if (!tableName.matches("^[\\w\\s\\[\\]\"`$.,*]+$")) {
814
+ if (!tableName.matches("^(\\*|((`[^`]+`|\"[^\"]+\"|\\w+\\.\\w+|\\w+)(,\\s*(`[^`]+`|\"[^\"]+\"|\\w+\\.\\w+|\\w+))*))$")) {
290
815
  throw new IllegalArgumentException("Invalid tableName");
291
816
  }
292
- if (!order.matches("^[\\w\\s\\[\\]\"`$.,*]+$")) {
817
+ if (!order.matches("^((`[^`]+`|\"[^\"]+\"|\\w+\\.\\w+|\\w+)(\\s+(ASC|DESC))?(,\\s*(`[^`]+`|\"[^\"]+\"|\\w+\\.\\w+|\\w+)(\\s+(ASC|DESC))?)*)$")) {
293
818
  throw new IllegalArgumentException("Invalid order");
294
819
  }
295
820
  ```
296
- * 使用 `#` 绑定参数传递普通值, 例如,这里的${username} 改为 #{username} 。
297
-
821
+ ```
822
+ * 使用 `#` 绑定参数传递普通值, 例如,这里的${username} 改为 #{username} 。
823
+ ```
298
824
  ```java
299
825
  <select id="getUserByName" resultType="User">
300
826
  SELECT * FROM ${tableName} WHERE username = #{username} order by ${order}
@@ -379,7 +905,7 @@ List<User> users = entityManager.createQuery(cq).getResultList();
379
905
  CriteriaBuilder cb = entityManager.getCriteriaBuilder();
380
906
  CriteriaQuery<User> cq = cb.createQuery(User.class);
381
907
  Root<User> user = cq.from(User.class);
382
- if (!username.matches("^[\\w\\s\\[\\]\"`$.,*]+$")) {
908
+ if (!username.matches("^[A-Za-z_][A-Za-z0-9_]{0,127}$")) {
383
909
  throw new IllegalArgumentException("Invalid username");
384
910
  }
385
911
  cq.where(cb.equal(user.get("username"), username));
@@ -443,10 +969,10 @@ ResultSet rs = stmt.executeQuery(baseQuery);
443
969
  * **修复**:对表名、列名进行正则过滤,对条件直使用 `PreparedStatement` 动态绑定参数:
444
970
 
445
971
  ```java
446
- if (!addr.matches("^[\\w\\s\\[\\]\"`$.,*]+$")) {
972
+ if (!addr.matches("^(\\*|((`[^`]+`|\"[^\"]+\"|\\w+\\.\\w+|\\w+)(,\\s*(`[^`]+`|\"[^\"]+\"|\\w+\\.\\w+|\\w+))*))$")) {
447
973
  throw new IllegalArgumentException("Invalid column addr");
448
974
  }
449
- if (!userTable.matches("^[\\w\\s\\[\\]\"`$.,*]+$")) {
975
+ if (!userTable.matches("^[A-Za-z_][A-Za-z0-9_]{0,127}$")) {
450
976
  throw new IllegalArgumentException("Invalid userTable");
451
977
  }
452
978
  StringBuilder sb = new StringBuilder("SELECT id,username,email," + addr + " FROM " + userTable + " WHERE 1=1");
@@ -465,8 +991,6 @@ for (int i = 0; i < params.size(); i++) {
465
991
  }
466
992
  ResultSet rs = pstmt.executeQuery();
467
993
  ```
468
-
469
-
470
994
  ### 9. **组合条件查询中的动态拼接3**
471
995
  * **场景**:在复杂查询中,动态拼接条件时未使用参数化查询,且被其他函数使用。
472
996
  * **示例**: