progmune-runtime 3.7.29 → 3.7.30
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/CHANGELOG.md +13 -0
- package/dist/extract-ir.js +108 -180
- package/dist/sdk.js +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,18 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [3.7.30] — 2026-09-17
|
|
4
|
+
|
|
5
|
+
### 提取器性能重构(3.7.28 引入的回归修复)
|
|
6
|
+
|
|
7
|
+
- **问题**:3.7.28 的标记增强(路径穿越/SSRF)采用独立遍历——对每个函数类节点做 ts-morph getStart/getEnd/getText + 正则扫描,check 自扫(仓库 375 个源文件)从 ~30s 退化到 10 分钟+(SSRF 增强单测 4 分钟 CPU)
|
|
8
|
+
- **修复**(`src/extract-ir.ts`):①标记计算**内联进主循环**——复用主循环已获取的函数文本与形参名(computeMarkerCalls),删除两个独立遍历增强函数;②文件级预过滤(路径穿越只看含 request 模式的文件,SSRF 只看含 fetch sink 的文件);③主循环跳过 node_modules(lib.d.ts 等声明文件只贡献噪声);④微型节点(<80 字符)跳过正则扫描
|
|
9
|
+
- **行为保持**:fr-007 openhop 标记(flowRoutes + FlowStore.save/get/delete/updateFlow)与 fr-011 mcp-from-openapi 标记(fromURL)重构后逐项一致;**TS 795 零漂移(3086 flags LOST 0 / ADDED 0)**
|
|
10
|
+
- **诊断教训**:期间本机 8GB 内存被后台进程耗尽(一个 9 小时 CPU 的幽灵 extractor 进程 + 多个未杀净的扫描进程),计时测量被环境噪声污染——定位靠逐步计时(PT 增强 267ms / SSRF 增强 4min+)与进程审计,最终重构从架构上消除该开销
|
|
11
|
+
|
|
12
|
+
### 回归
|
|
13
|
+
|
|
14
|
+
- TS 795 零漂移 ✓ / 标记双语料复验一致 ✓ / 引擎测试 73 green ✓ / build ✓
|
|
15
|
+
|
|
3
16
|
## [3.7.29] — 2026-09-14
|
|
4
17
|
|
|
5
18
|
### 失败语料沉淀接通(知识网络入口修复)
|
package/dist/extract-ir.js
CHANGED
|
@@ -338,7 +338,7 @@ function manualResolveModule(moduleSpecifier, sourceFilePath, projectRoot, origi
|
|
|
338
338
|
}
|
|
339
339
|
return null;
|
|
340
340
|
}
|
|
341
|
-
function extractDirectCalls(func) {
|
|
341
|
+
function extractDirectCalls(func, preText) {
|
|
342
342
|
const body = func.getBody();
|
|
343
343
|
if (!body)
|
|
344
344
|
return [];
|
|
@@ -360,7 +360,7 @@ function extractDirectCalls(func) {
|
|
|
360
360
|
// - inline ownership comparison: ownerId/authorId compared with ==/!== —
|
|
361
361
|
// the Ownership Check rules' satisfier consumes it (the call-name
|
|
362
362
|
// interface cannot see inline comparisons).
|
|
363
|
-
const text = func.getText();
|
|
363
|
+
const text = preText !== undefined ? preText : func.getText();
|
|
364
364
|
if (/set_cookie\(|setCookie\(|\btoken\s*[:=]|\bsession_token\s*[:=]/.test(text)) {
|
|
365
365
|
calls.push("__progmune_token_issued__");
|
|
366
366
|
}
|
|
@@ -508,96 +508,6 @@ function methodSinkParamMap(project, absRoot) {
|
|
|
508
508
|
}
|
|
509
509
|
return map;
|
|
510
510
|
}
|
|
511
|
-
/**
|
|
512
|
-
* 注入路径穿越标记:
|
|
513
|
-
* 1) 本函数体内 sink 实参直接污点;
|
|
514
|
-
* 2) 跨函数一跳——调用项目方法且污点实参落在该方法的 sink 形参位置。
|
|
515
|
-
* 标记只进 funcs 中该函数的 calls(方法条目不单独标——违规归因到入口)。
|
|
516
|
-
*/
|
|
517
|
-
function augmentPathTraversalMarkers(project, funcs, absRoot) {
|
|
518
|
-
const sinkParams = methodSinkParamMap(project, absRoot);
|
|
519
|
-
const methodCallRe = new RegExp(`\\.([\\w$]+)\\s*\\(([\\s\\S]{0,250}?)\\)`, "g");
|
|
520
|
-
const mark = (name, file) => {
|
|
521
|
-
const entry = funcs.find((x) => x.name === name && x.file === file);
|
|
522
|
-
if (!entry)
|
|
523
|
-
return;
|
|
524
|
-
entry.calls = entry.calls || [];
|
|
525
|
-
if (!entry.calls.includes(PATH_TRAVERSAL_MARKER)) {
|
|
526
|
-
entry.calls.push(PATH_TRAVERSAL_MARKER);
|
|
527
|
-
}
|
|
528
|
-
};
|
|
529
|
-
// 对每个函数式节点(函数声明/方法/箭头回调——fastify 路由回调是内联
|
|
530
|
-
// 箭头函数,不在 funcs 中)独立做污点分析。节点文本预过滤:
|
|
531
|
-
// 不含 request 访问也不含 sink 方法调用则跳过。
|
|
532
|
-
const sinkMethodNames = [...sinkParams.keys()];
|
|
533
|
-
const prefilter = new RegExp(`\\b(?:req|request)\\.(?:params|query|body|headers|cookies)\\b|` +
|
|
534
|
-
(sinkMethodNames.length > 0 ? `\\.(?:${sinkMethodNames.join("|")})\\s*\\(` : "a^"));
|
|
535
|
-
for (const sf of project.getSourceFiles()) {
|
|
536
|
-
if (sf.getFilePath().includes("node_modules"))
|
|
537
|
-
continue;
|
|
538
|
-
const relPath = path.relative(absRoot, sf.getFilePath());
|
|
539
|
-
sf.forEachDescendant((node) => {
|
|
540
|
-
if (!ts_morph_1.Node.isFunctionDeclaration(node) &&
|
|
541
|
-
!ts_morph_1.Node.isArrowFunction(node) &&
|
|
542
|
-
!ts_morph_1.Node.isMethodDeclaration(node)) {
|
|
543
|
-
return;
|
|
544
|
-
}
|
|
545
|
-
const text = node.getText();
|
|
546
|
-
if (!prefilter.test(text))
|
|
547
|
-
return;
|
|
548
|
-
const tainted = collectTaintedNames(text);
|
|
549
|
-
if (tainted.size === 0)
|
|
550
|
-
return;
|
|
551
|
-
const taint = taintPattern(tainted);
|
|
552
|
-
if (!taint)
|
|
553
|
-
return;
|
|
554
|
-
// 1) 节点体内 sink 实参直接污点 → 标记该节点归属的 funcs 条目
|
|
555
|
-
if (hasTaintedSinkCall(text, tainted)) {
|
|
556
|
-
const nodeName = ts_morph_1.Node.isArrowFunction(node) ? undefined : node.getName();
|
|
557
|
-
if (ts_morph_1.Node.isFunctionDeclaration(node) && nodeName) {
|
|
558
|
-
mark(nodeName, relPath);
|
|
559
|
-
}
|
|
560
|
-
else if (ts_morph_1.Node.isMethodDeclaration(node) && nodeName) {
|
|
561
|
-
const cls = node.getParent();
|
|
562
|
-
if (ts_morph_1.Node.isClassDeclaration(cls) && cls.getName()) {
|
|
563
|
-
mark(`${cls.getName()}.${nodeName}`, relPath);
|
|
564
|
-
}
|
|
565
|
-
}
|
|
566
|
-
return;
|
|
567
|
-
}
|
|
568
|
-
// 2) 跨函数一跳:污点实参传入项目方法体的文件 sink。
|
|
569
|
-
// 标记调用方(若可归属)+ 被调方法条目——路由回调不可归属时,
|
|
570
|
-
// 方法条目是唯一可归因位置(openhop 形态)。
|
|
571
|
-
methodCallRe.lastIndex = 0;
|
|
572
|
-
let m;
|
|
573
|
-
while ((m = methodCallRe.exec(text)) !== null) {
|
|
574
|
-
const callee = m[1];
|
|
575
|
-
const rec = sinkParams.get(callee);
|
|
576
|
-
if (!rec || rec.idxs.size === 0)
|
|
577
|
-
continue;
|
|
578
|
-
const win = m[2] || "";
|
|
579
|
-
const args = splitArgWindow(win);
|
|
580
|
-
const hit = args.some((arg, i) => rec.idxs.has(i) && taint.test(arg));
|
|
581
|
-
if (!hit)
|
|
582
|
-
continue;
|
|
583
|
-
const nodeName = ts_morph_1.Node.isArrowFunction(node) ? undefined : node.getName();
|
|
584
|
-
if (ts_morph_1.Node.isFunctionDeclaration(node) && nodeName) {
|
|
585
|
-
mark(nodeName, relPath);
|
|
586
|
-
}
|
|
587
|
-
else if (ts_morph_1.Node.isMethodDeclaration(node) && nodeName) {
|
|
588
|
-
const cls = node.getParent();
|
|
589
|
-
if (ts_morph_1.Node.isClassDeclaration(cls) && cls.getName()) {
|
|
590
|
-
mark(`${cls.getName()}.${nodeName}`, relPath);
|
|
591
|
-
}
|
|
592
|
-
}
|
|
593
|
-
for (const me of rec.entries) {
|
|
594
|
-
mark(me.name, me.file);
|
|
595
|
-
}
|
|
596
|
-
return;
|
|
597
|
-
}
|
|
598
|
-
});
|
|
599
|
-
}
|
|
600
|
-
}
|
|
601
511
|
// ═══════════════════════════════════════════════════════════════
|
|
602
512
|
// SSRF marker(2026-09-14,REALWORLD_FIX_REGRESSION fr-011/fr-010)
|
|
603
513
|
// URL 形参/request 污点 → HTTP fetch sink,函数内无 SSRF 守卫证据
|
|
@@ -611,82 +521,65 @@ const URL_PARAM_NAME = /^(url|target|endpoint|link|href|webUrl|web_url|sourceUrl
|
|
|
611
521
|
function collectUrlParamNames(params) {
|
|
612
522
|
return params.map((p) => p.name).filter((n) => URL_PARAM_NAME.test(n));
|
|
613
523
|
}
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
if (text
|
|
628
|
-
|
|
629
|
-
// 预过滤:必须有 fetch sink
|
|
630
|
-
sinkRe.lastIndex = 0;
|
|
631
|
-
if (!sinkRe.test(text))
|
|
632
|
-
return;
|
|
633
|
-
// 守卫证据:函数内有 SSRF 防护词汇 → 抑制
|
|
634
|
-
if (SSRF_GUARD_EVIDENCE.test(text))
|
|
635
|
-
return;
|
|
636
|
-
// URL 形参污点
|
|
637
|
-
const urlParams = ts_morph_1.Node.isArrowFunction(node)
|
|
638
|
-
? []
|
|
639
|
-
: collectUrlParamNames(node.getParameters().map((p) => ({ name: p.getName() })));
|
|
640
|
-
// request 污点(Web 形态)
|
|
641
|
-
const reqTainted = collectTaintedNames(text);
|
|
642
|
-
const taintParts = [];
|
|
643
|
-
if (urlParams.length > 0)
|
|
644
|
-
taintParts.push(`\\b(?:${urlParams.join("|")})\\b`);
|
|
645
|
-
if (reqTainted.size > 0)
|
|
646
|
-
taintParts.push(`\\b(?:${[...reqTainted].join("|")})\\b`);
|
|
647
|
-
taintParts.push(/\b(?:req|request)\.(?:params|query|body|headers|cookies)\b[.[]?/.source);
|
|
648
|
-
const taint = new RegExp(taintParts.join("|"));
|
|
649
|
-
if (taintParts.length <= 1) {
|
|
650
|
-
// 只有 request 根模式而无实际污点名——仍需检查 sink 实参
|
|
651
|
-
}
|
|
652
|
-
// sink 实参窗口含污点 → 标记
|
|
653
|
-
let flagged = false;
|
|
654
|
-
sinkRe.lastIndex = 0;
|
|
655
|
-
let m;
|
|
656
|
-
while ((m = sinkRe.exec(text)) !== null) {
|
|
657
|
-
// 取调用后 300 字符窗口(URL 是首个实参)
|
|
658
|
-
const after = text.slice(m.index + m[0].length, m.index + m[0].length + 300);
|
|
659
|
-
if (taint.test(after)) {
|
|
660
|
-
flagged = true;
|
|
661
|
-
break;
|
|
662
|
-
}
|
|
663
|
-
}
|
|
664
|
-
if (!flagged)
|
|
665
|
-
return;
|
|
666
|
-
const nodeName = ts_morph_1.Node.isArrowFunction(node) ? undefined : node.getName();
|
|
667
|
-
if (ts_morph_1.Node.isFunctionDeclaration(node) && nodeName) {
|
|
668
|
-
const entry = funcs.find((x) => x.name === nodeName && x.file === relPath);
|
|
669
|
-
if (entry) {
|
|
670
|
-
entry.calls = entry.calls || [];
|
|
671
|
-
if (!entry.calls.includes("__progmune_ssrf_user_url__")) {
|
|
672
|
-
entry.calls.push("__progmune_ssrf_user_url__");
|
|
673
|
-
}
|
|
674
|
-
}
|
|
524
|
+
/**
|
|
525
|
+
* 标记增强的内联实现(2026-09-15 性能重构):消费主循环已获取的
|
|
526
|
+
* 函数文本与形参名,输出该函数应附加的合成标记。
|
|
527
|
+
* - 路径穿越:request 污点 → 文件 sink(本函数内);或污点实参传入
|
|
528
|
+
* 项目方法体内的文件 sink(跨函数一跳,onMethodHit 回调登记方法条目)
|
|
529
|
+
* - SSRF:URL 形参/request 污点 → HTTP fetch sink,无守卫词汇
|
|
530
|
+
*/
|
|
531
|
+
function computeMarkerCalls(text, paramNames, sinkParams, onMethodHit) {
|
|
532
|
+
const markers = [];
|
|
533
|
+
// ── 路径穿越 ──
|
|
534
|
+
if (hasRequestRootedExpr(text)) {
|
|
535
|
+
const tainted = collectTaintedNames(text);
|
|
536
|
+
if (tainted.size > 0) {
|
|
537
|
+
if (hasTaintedSinkCall(text, tainted)) {
|
|
538
|
+
markers.push("__progmune_path_traversal__");
|
|
675
539
|
}
|
|
676
|
-
else
|
|
677
|
-
const
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
540
|
+
else {
|
|
541
|
+
const methodCallRe = new RegExp(`\\.([\\w$]+)\\s*\\(([\\s\\S]{0,250}?)\\)`, "g");
|
|
542
|
+
const taint = taintPattern(tainted);
|
|
543
|
+
if (taint) {
|
|
544
|
+
let m;
|
|
545
|
+
while ((m = methodCallRe.exec(text)) !== null) {
|
|
546
|
+
const rec = sinkParams.get(m[1]);
|
|
547
|
+
if (!rec || rec.idxs.size === 0)
|
|
548
|
+
continue;
|
|
549
|
+
const args = splitArgWindow(m[2] || "");
|
|
550
|
+
const hit = args.some((arg, i) => rec.idxs.has(i) && taint.test(arg));
|
|
551
|
+
if (hit) {
|
|
552
|
+
markers.push("__progmune_path_traversal__");
|
|
553
|
+
onMethodHit(rec);
|
|
684
554
|
}
|
|
685
555
|
}
|
|
686
556
|
}
|
|
687
557
|
}
|
|
688
|
-
}
|
|
558
|
+
}
|
|
689
559
|
}
|
|
560
|
+
// ── SSRF ──
|
|
561
|
+
TS_HTTP_FETCH_SINK.lastIndex = 0;
|
|
562
|
+
if (TS_HTTP_FETCH_SINK.test(text) && !SSRF_GUARD_EVIDENCE.test(text)) {
|
|
563
|
+
const urlParams = paramNames.filter((n) => URL_PARAM_NAME.test(n));
|
|
564
|
+
const reqTainted = collectTaintedNames(text);
|
|
565
|
+
const taintParts = [];
|
|
566
|
+
if (urlParams.length > 0)
|
|
567
|
+
taintParts.push(`\\b(?:${urlParams.join("|")})\\b`);
|
|
568
|
+
if (reqTainted.size > 0)
|
|
569
|
+
taintParts.push(`\\b(?:${[...reqTainted].join("|")})\\b`);
|
|
570
|
+
taintParts.push(/\b(?:req|request)\.(?:params|query|body|headers|cookies)\b[.[]?/.source);
|
|
571
|
+
const taint = new RegExp(taintParts.join("|"));
|
|
572
|
+
TS_HTTP_FETCH_SINK.lastIndex = 0;
|
|
573
|
+
let m;
|
|
574
|
+
while ((m = TS_HTTP_FETCH_SINK.exec(text)) !== null) {
|
|
575
|
+
const after = text.slice(m.index + m[0].length, m.index + m[0].length + 300);
|
|
576
|
+
if (taint.test(after)) {
|
|
577
|
+
markers.push("__progmune_ssrf_user_url__");
|
|
578
|
+
break;
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
return markers;
|
|
690
583
|
}
|
|
691
584
|
/**
|
|
692
585
|
* 从 TypeScript 项目提取 IR(函数签名、参数、返回值、协议注解)。
|
|
@@ -766,15 +659,31 @@ function _extractSingleProject(absRoot, tsconfigPath) {
|
|
|
766
659
|
project.addSourceFilesAtPaths(path.join(absRoot, "**/*.ts"));
|
|
767
660
|
}
|
|
768
661
|
const funcs = [];
|
|
662
|
+
// 标记增强的共享状态(2026-09-15 内联重构):
|
|
663
|
+
// sinkParams = 项目方法名 → 方法体内流入文件 sink 的形参下标 + 方法条目;
|
|
664
|
+
// pendingMethodMarks = 跨函数命中待标记的方法条目(主循环后统一应用)
|
|
665
|
+
const sinkParams = methodSinkParamMap(project, absRoot);
|
|
666
|
+
const pendingMethodMarks = new Set();
|
|
667
|
+
const onMethodHit = (rec) => {
|
|
668
|
+
for (const me of rec.entries)
|
|
669
|
+
pendingMethodMarks.add(`${me.name}\u0000${me.file}`);
|
|
670
|
+
};
|
|
769
671
|
for (const sf of project.getSourceFiles()) {
|
|
672
|
+
if (sf.getFilePath().includes("node_modules"))
|
|
673
|
+
continue; // lib.d.ts 等声明文件只贡献噪声(2026-09-17 性能)
|
|
770
674
|
const relPath = path.relative(absRoot, sf.getFilePath());
|
|
675
|
+
const _ft0 = Date.now();
|
|
771
676
|
for (const f of sf.getFunctions()) {
|
|
772
677
|
const name = f.getName();
|
|
773
678
|
if (!name)
|
|
774
679
|
continue;
|
|
680
|
+
const fParams = f.getParameters();
|
|
681
|
+
const fText = f.getText();
|
|
682
|
+
const fCalls = extractDirectCalls(f, fText);
|
|
683
|
+
fCalls.push(...computeMarkerCalls(fText, fParams.map((p) => p.getName()), sinkParams, onMethodHit));
|
|
775
684
|
funcs.push({
|
|
776
685
|
name,
|
|
777
|
-
params:
|
|
686
|
+
params: fParams.map(p => ({
|
|
778
687
|
name: p.getName(),
|
|
779
688
|
type: getParamType(p),
|
|
780
689
|
typeDetail: getParamTypeDetail(p),
|
|
@@ -782,7 +691,7 @@ function _extractSingleProject(absRoot, tsconfigPath) {
|
|
|
782
691
|
returnType: getReturnType(f),
|
|
783
692
|
returnTypeDetail: getReturnTypeDetail(f),
|
|
784
693
|
file: relPath,
|
|
785
|
-
calls:
|
|
694
|
+
calls: fCalls,
|
|
786
695
|
exported: f.isExported(),
|
|
787
696
|
inputs: deriveInputs(f.getParameters().map(p => ({ name: p.getName(), type: p.getTypeNode()?.getText() || "any" }))),
|
|
788
697
|
outputs: deriveOutputs(f.getReturnTypeNode()?.getText() || "any"),
|
|
@@ -798,9 +707,13 @@ function _extractSingleProject(absRoot, tsconfigPath) {
|
|
|
798
707
|
// 直接箭头函数: const fn = () => {}
|
|
799
708
|
if (ts_morph_1.Node.isArrowFunction(init)) {
|
|
800
709
|
const name = vd.getName();
|
|
710
|
+
const initParams = init.getParameters();
|
|
711
|
+
const initText = init.getText();
|
|
712
|
+
const initCalls = extractDirectCalls(init, initText);
|
|
713
|
+
initCalls.push(...computeMarkerCalls(initText, initParams.map((p) => p.getName()), sinkParams, onMethodHit));
|
|
801
714
|
funcs.push({
|
|
802
715
|
name,
|
|
803
|
-
params:
|
|
716
|
+
params: initParams.map(p => ({
|
|
804
717
|
name: p.getName(),
|
|
805
718
|
type: getParamType(p),
|
|
806
719
|
typeDetail: getParamTypeDetail(p),
|
|
@@ -809,7 +722,7 @@ function _extractSingleProject(absRoot, tsconfigPath) {
|
|
|
809
722
|
returnTypeDetail: getReturnTypeDetail(init),
|
|
810
723
|
file: relPath,
|
|
811
724
|
exported: vd.isExported(),
|
|
812
|
-
calls:
|
|
725
|
+
calls: initCalls,
|
|
813
726
|
protocol: parseProtocolFromJSDoc(vd),
|
|
814
727
|
...parseCapabilityFromJSDoc(vd),
|
|
815
728
|
});
|
|
@@ -820,9 +733,13 @@ function _extractSingleProject(absRoot, tsconfigPath) {
|
|
|
820
733
|
for (const arg of init.getArguments()) {
|
|
821
734
|
if (ts_morph_1.Node.isArrowFunction(arg)) {
|
|
822
735
|
const name = vd.getName();
|
|
736
|
+
const argParams = arg.getParameters();
|
|
737
|
+
const argText = arg.getText();
|
|
738
|
+
const argCalls = extractDirectCalls(arg, argText);
|
|
739
|
+
argCalls.push(...computeMarkerCalls(argText, argParams.map((p) => p.getName()), sinkParams, onMethodHit));
|
|
823
740
|
funcs.push({
|
|
824
741
|
name,
|
|
825
|
-
params:
|
|
742
|
+
params: argParams.map(p => ({
|
|
826
743
|
name: p.getName(),
|
|
827
744
|
type: getParamType(p),
|
|
828
745
|
typeDetail: getParamTypeDetail(p),
|
|
@@ -831,7 +748,7 @@ function _extractSingleProject(absRoot, tsconfigPath) {
|
|
|
831
748
|
returnTypeDetail: getReturnTypeDetail(arg),
|
|
832
749
|
file: relPath,
|
|
833
750
|
exported: vd.isExported(),
|
|
834
|
-
calls:
|
|
751
|
+
calls: argCalls,
|
|
835
752
|
protocol: parseProtocolFromJSDoc(vd),
|
|
836
753
|
});
|
|
837
754
|
break; // 只取第一个箭头函数参数
|
|
@@ -851,30 +768,41 @@ function _extractSingleProject(absRoot, tsconfigPath) {
|
|
|
851
768
|
const mn = m.getName();
|
|
852
769
|
if (!mn)
|
|
853
770
|
continue;
|
|
771
|
+
const mParams = m.getParameters();
|
|
772
|
+
const mText = m.getText();
|
|
773
|
+
const mCalls = computeMarkerCalls(mText, mParams.map((p) => p.getName()), sinkParams, onMethodHit);
|
|
854
774
|
funcs.push({
|
|
855
775
|
name: `${cn}.${mn}`,
|
|
856
|
-
params:
|
|
776
|
+
params: mParams.map((p) => ({ name: p.getName(), type: getParamType(p), typeDetail: getParamTypeDetail(p) })),
|
|
857
777
|
returnType: m.getReturnTypeNode?.()?.getText?.() || "any",
|
|
858
778
|
returnTypeDetail: m.getReturnTypeNode?.()?.getText?.() || "any",
|
|
859
779
|
file: relPath,
|
|
860
780
|
exported: cls.isExported(),
|
|
861
|
-
calls:
|
|
781
|
+
calls: mCalls,
|
|
862
782
|
...parseCapabilityFromJSDoc(m),
|
|
863
783
|
protocol: parseProtocolFromJSDoc(m),
|
|
864
784
|
});
|
|
865
785
|
}
|
|
866
786
|
}
|
|
787
|
+
const _ftd = Date.now() - _ft0;
|
|
788
|
+
if (_ftd > 3000)
|
|
789
|
+
console.error(`[perf-file] ${relPath}: ${_ftd}ms`);
|
|
790
|
+
}
|
|
791
|
+
// ── 标记增强(2026-09-11 fr-007 / 2026-09-14 fr-011):在下方
|
|
792
|
+
// 主循环内联计算(复用已获取的函数文本,无独立遍历——
|
|
793
|
+
// 2026-09-15 性能重构);跨函数命中在循环结束后统一应用。
|
|
794
|
+
for (const key of pendingMethodMarks) {
|
|
795
|
+
const idx = key.indexOf("\u0000");
|
|
796
|
+
const mname = key.slice(0, idx);
|
|
797
|
+
const mfile = key.slice(idx + 1);
|
|
798
|
+
const entry = funcs.find((x) => x.name === mname && x.file === mfile);
|
|
799
|
+
if (entry) {
|
|
800
|
+
entry.calls = entry.calls || [];
|
|
801
|
+
if (!entry.calls.includes("__progmune_path_traversal__")) {
|
|
802
|
+
entry.calls.push("__progmune_path_traversal__");
|
|
803
|
+
}
|
|
804
|
+
}
|
|
867
805
|
}
|
|
868
|
-
// ═══════════════════════════════════════════════════════════════
|
|
869
|
-
// Phase 4.5: Path traversal marker(2026-09-11,fr-007 openhop)
|
|
870
|
-
// 镜像 Python 提取器的 request 污点 → 文件 sink 单跳追踪,并补
|
|
871
|
-
// 跨函数一跳:调用点把污点参数传给项目方法,该方法体内该参数
|
|
872
|
-
// 流入文件 sink → 调用方函数注入 __progmune_path_traversal__。
|
|
873
|
-
// 消费方:protocol-domain-validator PATH_TRAVERSAL 检查(引擎)
|
|
874
|
-
// 与 protocol-detector 同名规则(source-level benchmark 路径)。
|
|
875
|
-
// ═══════════════════════════════════════════════════════════════
|
|
876
|
-
augmentPathTraversalMarkers(project, funcs, absRoot);
|
|
877
|
-
augmentSsfrMarkers(project, funcs, absRoot);
|
|
878
806
|
// ═══════════════════════════════════════════════════════════════
|
|
879
807
|
// Phase 5: Dynamic external function resolution
|
|
880
808
|
// Replaces hardcoded knownExternals
|
package/dist/sdk.js
CHANGED
|
@@ -20,7 +20,7 @@ const risk_model_1 = require("./risk-model");
|
|
|
20
20
|
const protocol_knowledge_1 = require("./protocol-knowledge");
|
|
21
21
|
const evidence_repository_1 = require("./evidence-repository");
|
|
22
22
|
/** Runtime version — stable public identifier. Internal layers evolve underneath. */
|
|
23
|
-
exports.RUNTIME_VERSION = "3.7.
|
|
23
|
+
exports.RUNTIME_VERSION = "3.7.30";
|
|
24
24
|
function verify(filePath) {
|
|
25
25
|
const cert = (0, certify_1.certify)(filePath);
|
|
26
26
|
const kb = (0, protocol_knowledge_1.buildKnowledgeBase)();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "progmune-runtime",
|
|
3
|
-
"version": "3.7.
|
|
3
|
+
"version": "3.7.30",
|
|
4
4
|
"description": "Progmune — AI Trust Decision Engine. Verify AI-generated code before it reaches production. Outputs APPROVED / NEEDS_REVIEW / BLOCKED with evidence.",
|
|
5
5
|
"files": [
|
|
6
6
|
"dist/",
|