128 lines
4.6 KiB
Python
128 lines
4.6 KiB
Python
|
|
#!/usr/bin/env python3
|
|||
|
|
# -*- coding: utf-8 -*-
|
|||
|
|
"""审计能力参数 v2:catalog 字段 vs 实现读取。
|
|||
|
|
|
|||
|
|
策略:
|
|||
|
|
- 直接读取:handler 函数体内 text(args,["k"]) / args.k / pick(args,[...]) 等模式
|
|||
|
|
- 间接读取:body 中把 args 整体传给函数(xxx(args)),或必填 key 出现在同文件其他位置(兜底)
|
|||
|
|
- A 类(假必填)只在「直接+间接+全文兜底都读不到」时报告,保证高置信
|
|||
|
|
- B 类(隐藏参数)只报直接读取的 key(保守)
|
|||
|
|
"""
|
|||
|
|
import json
|
|||
|
|
import re
|
|||
|
|
import os
|
|||
|
|
|
|||
|
|
ROOT = r"D:\code\PRA-only-demo"
|
|||
|
|
CAP_DIRS = [
|
|||
|
|
os.path.join(ROOT, "packages", "rpa-core", "src", "capabilities"),
|
|||
|
|
os.path.join(ROOT, "src", "main", "capabilities"),
|
|||
|
|
]
|
|||
|
|
|
|||
|
|
with open(os.path.join(ROOT, "resources", "catalog.json"), encoding="utf-8") as f:
|
|||
|
|
catalog = {c["id"]: c for c in json.load(f)["components"]}
|
|||
|
|
|
|||
|
|
src_blobs = []
|
|||
|
|
for d in CAP_DIRS:
|
|||
|
|
for fn in os.listdir(d):
|
|||
|
|
if fn.endswith(".ts") and not fn.endswith(".test.ts"):
|
|||
|
|
with open(os.path.join(d, fn), encoding="utf-8") as f:
|
|||
|
|
src_blobs.append((fn, f.read()))
|
|||
|
|
|
|||
|
|
def find_register_bodies(src):
|
|||
|
|
out = []
|
|||
|
|
for m in re.finditer(r'register\(\s*(\[[^\]]*\]|"[A-Za-z_.0-9]+")\s*,\s*(async)?\s*\(', src):
|
|||
|
|
ids = re.findall(r'"([A-Za-z_.0-9]+)"', m.group(1))
|
|||
|
|
i = m.end() - 1
|
|||
|
|
depth = 0
|
|||
|
|
start_body = None
|
|||
|
|
j = i
|
|||
|
|
while j < len(src):
|
|||
|
|
ch = src[j]
|
|||
|
|
if ch == "(":
|
|||
|
|
depth += 1
|
|||
|
|
elif ch == ")":
|
|||
|
|
depth -= 1
|
|||
|
|
if depth == 0:
|
|||
|
|
break
|
|||
|
|
elif ch == "{" and start_body is None and depth >= 2:
|
|||
|
|
start_body = j
|
|||
|
|
j += 1
|
|||
|
|
if start_body is None:
|
|||
|
|
body = src[i + 1:j]
|
|||
|
|
else:
|
|||
|
|
d2 = 0
|
|||
|
|
k = start_body
|
|||
|
|
while k < len(src):
|
|||
|
|
if src[k] == "{":
|
|||
|
|
d2 += 1
|
|||
|
|
elif src[k] == "}":
|
|||
|
|
d2 -= 1
|
|||
|
|
if d2 == 0:
|
|||
|
|
break
|
|||
|
|
k += 1
|
|||
|
|
body = src[start_body:k]
|
|||
|
|
out.append((ids, body))
|
|||
|
|
return out
|
|||
|
|
|
|||
|
|
PAT_DIRECT = [
|
|||
|
|
re.compile(r'\w+\(\s*args\s*,\s*\[([^\]]*)\)'),
|
|||
|
|
re.compile(r'pick\(\s*args\s*,\s*\[([^\]]*)\)'),
|
|||
|
|
re.compile(r'\bargs\.([a-zA-Z_][a-zA-Z0-9_]*)'),
|
|||
|
|
re.compile(r"args\[['\"]([^'\"]+)['\"]\]"),
|
|||
|
|
]
|
|||
|
|
INTERNAL = {"nodeId", "componentId", "timeoutMs", "time_out", "timeout", "evidenceDir"}
|
|||
|
|
|
|||
|
|
info = {} # cid -> dict(direct=set, indirect=bool)
|
|||
|
|
for fn, src in src_blobs:
|
|||
|
|
for ids, body in find_register_bodies(src):
|
|||
|
|
direct = set()
|
|||
|
|
for pat in PAT_DIRECT:
|
|||
|
|
for mm in pat.finditer(body):
|
|||
|
|
g = mm.group(1)
|
|||
|
|
if g is None:
|
|||
|
|
continue
|
|||
|
|
if "," in g or '"' in g or "'" in g:
|
|||
|
|
direct.update(re.findall(r"['\"]([^'\"]+)['\"]", g))
|
|||
|
|
else:
|
|||
|
|
direct.add(g)
|
|||
|
|
# args 整体传走 → 间接(keys 未知)
|
|||
|
|
indirect = bool(re.search(r'\(\s*args\s*[,)]|\bargs\s*\)', body)) and not direct
|
|||
|
|
# selectorOf(args) 这种:args 作为参数出现在调用里
|
|||
|
|
if re.search(r'\w+\(\s*args\s*\)|\w+\(\s*ctx\s*,\s*node\s*,\s*args', body):
|
|||
|
|
indirect = True
|
|||
|
|
for cid in ids:
|
|||
|
|
rec = info.setdefault(cid, {"direct": set(), "indirect": False, "file": fn})
|
|||
|
|
rec["direct"] |= direct
|
|||
|
|
rec["indirect"] = rec["indirect"] or indirect
|
|||
|
|
|
|||
|
|
A, B = [], []
|
|||
|
|
for cid, comp in sorted(catalog.items()):
|
|||
|
|
rec = info.get(cid)
|
|||
|
|
if not rec:
|
|||
|
|
continue
|
|||
|
|
fields = comp.get("fields") or []
|
|||
|
|
cat_keys = {f["key"] for f in fields}
|
|||
|
|
required = {f["key"] for f in fields if f.get("required")}
|
|||
|
|
direct = rec["direct"]
|
|||
|
|
if rec["indirect"]:
|
|||
|
|
# keys 未知:不判 A(无法确定),不判 B
|
|||
|
|
continue
|
|||
|
|
a = {k for k in required if k not in direct}
|
|||
|
|
# 兜底:全目录源码找 key(带引号形式,或 .key 词边界的直接访问)
|
|||
|
|
if a:
|
|||
|
|
all_src = "\n".join(s for _, s in src_blobs)
|
|||
|
|
a = {k for k in a if not (re.search(r'["\']' + re.escape(k) + r'["\']', all_src) or re.search(r'\.' + re.escape(k) + r'\b', all_src))}
|
|||
|
|
if a:
|
|||
|
|
A.append((cid, sorted(a)))
|
|||
|
|
b = {k for k in direct if k not in cat_keys and k not in INTERNAL and len(k) > 2}
|
|||
|
|
if b:
|
|||
|
|
B.append((cid, sorted(b)))
|
|||
|
|
|
|||
|
|
print(f"=== A 类:必填但实现全链路都不读(高置信假必填) {len(A)} 个 ===")
|
|||
|
|
for cid, keys in A:
|
|||
|
|
print(f" {cid}: {keys}")
|
|||
|
|
print()
|
|||
|
|
print(f"=== B 类:实现直接读取但目录未定义(隐藏参数,保守列表) {len(B)} 个 ===")
|
|||
|
|
for cid, keys in B:
|
|||
|
|
print(f" {cid}: {keys}")
|