fix: 无连字符 KEY 变量 + COBOL 专家 10 大攻击面测试

COBOL 专家对抗性审查发现:
- 老式 COBOL 的 WSKEY1/WSKEY2(无连字符)未被 L1 关键词检测
- 结构性检测信号 4 和 5 覆盖不全

修复:
- L1 增加 re:WS[A-Z0-9]*KEY[A-Z0-9]* 覆盖无连字符 KEY 命名
- _matches_key_comparison 扩展支持无连字符变量
- has_key_var 注入扩展支持无连字符
- 结构性检测信号 4 增加 WS\w+ 比较模式
- 结构性检测信号 5 增加两个单独 OPEN 的支持

新测试:
- test_cobol_expert_attacks — 4 个内联攻击测试
  (跨行AT END, 无连字符WSKEY, GO TO风格, NOT=比较)
- test-adversarial: 8 个样本文件攻击测试

全回归: 767 passed (+3 new, 0 failures)
This commit is contained in:
NB-076
2026-06-21 15:35:52 +08:00
parent da5d1058e7
commit 4b22c3754e
11 changed files with 352 additions and 65 deletions
+6 -3
View File
@@ -24,6 +24,8 @@ L1_RULES: list[tuple[str, list[str], float]] = [
("文件编成", ["ORGANIZATION IS"], 0.99),
("替代索引", ["ALTERNATE RECORD KEY"], 0.99),
("マッチング", ["re:WS-[\\w-]*KEY"], 0.65),
# 无连字符 KEY 变量: WSKEY, WSKEY1, WSKEYCD 等(老式 COBOL 命名)
("マッチング", ["re:WS[A-Z0-9]*KEY[A-Z0-9]*"], 0.65),
# 旧式命名: K01-KEY, KS-KEY, MTCH-KEY 等(无 WS- 前缀)
# 低确信度,需要实际 KEY 比较上下文验证
("マッチング", ["re:[A-Z]\\d{0,2}-\\w*KEY"], 0.55),
@@ -73,7 +75,7 @@ def _matches_key_comparison(source_upper: str) -> bool:
"""
# 模式 1: KEY 变量出现在比较上下文中(= < > 后跟变量)
# 注意: 不能用 \s 代替 [=<>],否则「WS-KEY PIC」中的空格也会误匹配
if re.search(r'WS-[\w-]*KEY[A-Z0-9-]*\s*[=<>]', source_upper):
if re.search(r'(?:WS-[\w-]*KEY[A-Z0-9-]*|WS[A-Z0-9]*KEY[A-Z0-9]*)\s*[=<>]', source_upper):
return True
# 模式 2: 非 WS- 前缀的 KEY 变量(旧式命名 K01-KEY 等)
if re.search(r'\b[A-Z]\d{0,2}-[\w-]*KEY\s*[=<>]', source_upper):
@@ -117,8 +119,9 @@ def _detect_matching_structure(source_upper: str) -> float:
# 信号 3: ELSE 体内 READ(条件性读取)
if re.search(r'ELSE\s+.*READ\s+', source_upper):
signals += 1
# 信号 4: IF 比较两个连字号字段(跨文件字段比较)
if re.search(r'IF\s+\w+-\w+\s*[=<>]\s*\w+-\w+', source_upper):
# 信号 4: IF 比较两个字段(跨文件字段比较,可有/无连字号
if (re.search(r'IF\s+\w+-\w+\s*[=<>]\s*\w+-\w+', source_upper) # 标准命名 CUST-CODE
or re.search(r'IF\s+WS\w+\s*[=<>]\s+WS\w+', source_upper)): # 无连字符 WSKEY1
signals += 1
# 信号 5: 2+ 文件 OPEN INPUT
if re.search(r'OPEN\s+INPUT\s+\w+\s+\w+', source_upper):