progmune-runtime 3.7.46 → 3.7.47
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 +87 -0
- package/dist/extract-ir-taint-structural.test.js +217 -0
- package/dist/extract-ir.js +16 -1
- package/dist/sdk.js +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,92 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [3.7.47] — 2026-09-21
|
|
4
|
+
|
|
5
|
+
### 类方法的真实调用必须进 IR(状态机在 OO 代码上没有输入)
|
|
6
|
+
|
|
7
|
+
**一句话**:主 IR 循环的类方法分支**只调了 `computeMarkerCalls`(产 `__progmune_*` 语义
|
|
8
|
+
标记),从来没调过 `extractDirectCalls`(产真实调用名)**;而函数声明 / 箭头 / 包装箭头
|
|
9
|
+
三个分支都是两行齐全。后果是类方法的 `calls` 里永远只有标记、没有真实调用 ⇒ **协议状态
|
|
10
|
+
机在 OO 代码上拿不到输入**。
|
|
11
|
+
|
|
12
|
+
这不是新引入的回归。Claude 补充的历史事实:修 A1 时类方法提取循环里写死的就是
|
|
13
|
+
`calls: []`,`extractDirectCalls` 只对函数声明和箭头函数调用过,类方法从未接上。A1 只是
|
|
14
|
+
给方法条目挂标记时动过这个数组,基础的调用提取从来没补。**自该功能诞生之日起就空转**,
|
|
15
|
+
taintpath 十一族语料全是函数载体,从来没有东西逼它现形。
|
|
16
|
+
|
|
17
|
+
**顺带解释了三件悬案**:真实语料 `protocol=0`、引擎自扫「0 误报」、NestJS 语料过分干净
|
|
18
|
+
—— 裁判一直没拿到证据。
|
|
19
|
+
|
|
20
|
+
**修复**:类方法分支补上 `extractDirectCalls(m, mText)`(签名形参加 `MethodDeclaration`,
|
|
21
|
+
遍历逻辑无需改动),顺序与既有三个载体一致:先真实调用,再追加语义标记。
|
|
22
|
+
|
|
23
|
+
| 探针形态(18 条) | 修复前 | 修复后 |
|
|
24
|
+
|---|---|---|
|
|
25
|
+
| 类方法(同步 / async / this 成员 / 装饰器 / 构造函数注入) | 全空 | ✓ 全部提出 |
|
|
26
|
+
| static / private / 抽象类实现方法 / `this.a.b()` 链式 / 计算属性名 | 全空 | ✓ 全部提出 |
|
|
27
|
+
| 函数声明 / 箭头(基线对照) | ✓ | ✓ 不变 |
|
|
28
|
+
|
|
29
|
+
**三条不修**(探针确认后明确登记,避免下一轮重踩):
|
|
30
|
+
|
|
31
|
+
1. **getter / setter / 构造函数体 / 对象字面量方法 / 类表达式** —— 这五种不是「calls 空」,
|
|
32
|
+
而是**条目压根不在 IR 里**(载体未收集)。属另一类缺陷,风险量级不同(新增条目 = 新增
|
|
33
|
+
检测面),不能与本轮混在一起,否则反向验证无法归因。
|
|
34
|
+
2. **嵌套箭头回调里的调用** —— `extractDirectCalls` 遇嵌套函数即 `traversal.skip()`。实测
|
|
35
|
+
函数声明 / 箭头 / 类方法三者口径**完全一致**(都只提得到 `setTimeout`),是既有设计。
|
|
36
|
+
3. **计算属性名的名字解算** —— `C.[K]` 名字不可解是另一回事,不影响 calls。
|
|
37
|
+
|
|
38
|
+
### FP 观测池:把「裁判拿到证据」量化出来
|
|
39
|
+
|
|
40
|
+
修复后重扫(`fp-pool-scan.ts`,4 切片 / 197 文件):
|
|
41
|
+
|
|
42
|
+
| 读数 | 修复前 | 修复后 |
|
|
43
|
+
|---|---|---|
|
|
44
|
+
| 空 calls 函数 | 140 / 197(**71%**) | 23 / 197(**12%**) |
|
|
45
|
+
| 其中类方法空转 | 126 | 9 |
|
|
46
|
+
| NestJS 切片 | 59 / 61 空 | 3 / 61 空 |
|
|
47
|
+
| Express 切片 | 76 / 113 空 | 15 / 113 空 |
|
|
48
|
+
|
|
49
|
+
**代价也量出来了**:项目级 safeguard 19 → 21,但 **perFunction 层面新增 18 条** —— 类方法
|
|
50
|
+
第一次有了 calls,规则第一次看见这些序列。逐条判定:**FP 15 / 弱真 3 / 真漏洞 0**。四条
|
|
51
|
+
规则侧校准线索已登记在 `fp-pool.json`(迁移与种子脚本应豁免、TypeORM query builder 链被
|
|
52
|
+
Input Validation 误判、内部工具方法不应被要求 Authorization、`validate` 已存在却仍报缺校验
|
|
53
|
+
⇒ 是词表不认 class-validator)。
|
|
54
|
+
|
|
55
|
+
上一轮 7 条人工判定里那条「无法判定」的 `UserController.login`(Token Security)**本轮变
|
|
56
|
+
成有效票**:有了 calls 才看得出它是标准 `jwt.sign` 签发 ⇒ FP。
|
|
57
|
+
|
|
58
|
+
### 验收
|
|
59
|
+
|
|
60
|
+
| 门 | 结果 |
|
|
61
|
+
|---|---|
|
|
62
|
+
| taintpath 闸门 | **160/160** |
|
|
63
|
+
| fr-007 / fr-016 | pre 5→post 0 / pre 7→post 0 **均维持** |
|
|
64
|
+
| 定向用例 | **241 passed / 6 文件**(新增 E1 组 14 条:10 正 2 正对照 2 负对照) |
|
|
65
|
+
| TS 盲测 | 111 项目 / perFunction **3201 条**:**LOST 0 / ADDED 0**,汇总完全一致 |
|
|
66
|
+
| 反向验证 | 三刀,残留 0(见下) |
|
|
67
|
+
| tsc | 零错误 |
|
|
68
|
+
|
|
69
|
+
**反向验证三刀**(`/tmp/rev_e1.py(原名 rev_c4k.py)`):
|
|
70
|
+
|
|
71
|
+
| 刀 | 退回的旧行为 | 转红 |
|
|
72
|
+
|---|---|---|
|
|
73
|
+
| ① 类方法只留标记 | `const mCalls = computeMarkerCalls(...)` | **12 条**:正例 ①–⑩ + 负对照 ①② |
|
|
74
|
+
| ② 类方法不追加标记 | 摘掉 `mCalls.push(...computeMarkerCalls(...))` | 0 条(marker 由**调用方侧**补上) |
|
|
75
|
+
| ③ 同上(闸门视角) | 同 ② | 闸门仍 160/160 |
|
|
76
|
+
|
|
77
|
+
刀①归因**干净**:转红的 12 条全在类方法组,正对照 ⑪⑫(函数声明 / 箭头)仍绿。
|
|
78
|
+
刀②③顺带证明两件事:marker 链路有调用方侧兜底(不是单点);**taintpath 语料里根本没有
|
|
79
|
+
类方法 ⇒ 闸门对本轮是空过**,真正的验收在 fp-pool 与定向用例。
|
|
80
|
+
|
|
81
|
+
### 方法学
|
|
82
|
+
|
|
83
|
+
- **R22 第三次应验**:负对照·② 一开始写错期望(以为类方法体内有 sink 就会有 marker),
|
|
84
|
+
跑探针才确认 —— marker 只在**调用方把污点传进来**时才注入到被调用方。写 expect 之前
|
|
85
|
+
先把判据抄进探针注释,这条规矩已经是第三次救我。
|
|
86
|
+
- **R20 的适用边界**(Claude 指出):那三问(pre 有无校验 / 污点根是否在切片内 / fix 是否
|
|
87
|
+
新增校验)只适用于**修复回归语料**,**别误用到 FP 观测池**——池的标准是「真实 + HTTP
|
|
88
|
+
入口 + 文件操作」,不要求 CVE。两边管的方向不同(召回 vs 精度),标准本来就不同。
|
|
89
|
+
|
|
3
90
|
## [3.7.46] — 2026-09-21
|
|
4
91
|
|
|
5
92
|
### C4j:载体收口的最后一批 + 一处既有缺陷的回归门
|
|
@@ -1898,3 +1898,220 @@ const WRITE_LAYER = [
|
|
|
1898
1898
|
}, 150000);
|
|
1899
1899
|
}
|
|
1900
1900
|
});
|
|
1901
|
+
// ═══════════════════════════════════════════════════════════════
|
|
1902
|
+
// E1:类方法的**真实调用**必须进 IR(2026-09-21)
|
|
1903
|
+
//
|
|
1904
|
+
// 命名说明:C4k 已被占用(容器承载轴「属性写入 ⇒ 容器传播」,实测收益上限 0,
|
|
1905
|
+
// 判定不修,见设计稿 §十七)。本组是**提取器主干**,另开 E(Extractor)系列。
|
|
1906
|
+
//
|
|
1907
|
+
// 背景(Claude 补充的历史事实):主 IR 循环的类方法分支自该功能诞生之日起
|
|
1908
|
+
// 只调 computeMarkerCalls(产 __progmune_* 语义标记),从未调 extractDirectCalls
|
|
1909
|
+
// (产真实调用名);函数声明 / 箭头 / 包装箭头三个分支都是两行齐全。
|
|
1910
|
+
// 结果:类方法的 calls 里永远只有标记、没有真实调用 ⇒ **状态机在 OO 代码上
|
|
1911
|
+
// 拿不到输入**。FP 观测池实测:NestJS 切片 61 个函数里 59 个 calls 为空(97%),
|
|
1912
|
+
// 池合计 197 个里 140 个空。此前未暴露是因为 taintpath 十一族语料全是函数载体。
|
|
1913
|
+
//
|
|
1914
|
+
// 判据:类方法条目的 calls 必须同时含「真实调用名」与「语义标记」——
|
|
1915
|
+
// 本轮是**补**真实调用,不是拿它替换标记(负对照·②守这条)。
|
|
1916
|
+
// ═══════════════════════════════════════════════════════════════
|
|
1917
|
+
(0, vitest_1.describe)("E1:类方法的真实调用必须进 IR(此前只有标记,状态机拿不到输入)", () => {
|
|
1918
|
+
const cases = [
|
|
1919
|
+
// ── 正例:此前全空的五种已知形态 ──────────────────────────
|
|
1920
|
+
{
|
|
1921
|
+
name: "正例·① 类方法(同步)",
|
|
1922
|
+
fn: "C.m",
|
|
1923
|
+
files: {
|
|
1924
|
+
"a.ts": `
|
|
1925
|
+
function helper() { return 1; }
|
|
1926
|
+
export class C { m() { helper(); } }
|
|
1927
|
+
`,
|
|
1928
|
+
},
|
|
1929
|
+
want: ["helper"],
|
|
1930
|
+
why: "最基础的类方法;此前 calls 为空",
|
|
1931
|
+
},
|
|
1932
|
+
{
|
|
1933
|
+
name: "正例·② 类方法(async / await)",
|
|
1934
|
+
fn: "C.m",
|
|
1935
|
+
files: {
|
|
1936
|
+
"a.ts": `
|
|
1937
|
+
function helper() { return 1; }
|
|
1938
|
+
export class C { async m() { await helper(); } }
|
|
1939
|
+
`,
|
|
1940
|
+
},
|
|
1941
|
+
want: ["helper"],
|
|
1942
|
+
why: "async 方法体同样是 Block,此前一样空",
|
|
1943
|
+
},
|
|
1944
|
+
{
|
|
1945
|
+
name: "正例·③ 类方法经 this 成员调用",
|
|
1946
|
+
fn: "C.m",
|
|
1947
|
+
files: {
|
|
1948
|
+
"a.ts": `
|
|
1949
|
+
export class C { private repo: any; m() { this.repo.save(); } }
|
|
1950
|
+
`,
|
|
1951
|
+
},
|
|
1952
|
+
want: ["save"],
|
|
1953
|
+
why: "NestJS/TypeORM 的实际写法;属性访问取末端名",
|
|
1954
|
+
},
|
|
1955
|
+
{
|
|
1956
|
+
name: "正例·④ 类方法带装饰器",
|
|
1957
|
+
fn: "C.m",
|
|
1958
|
+
files: {
|
|
1959
|
+
"a.ts": `
|
|
1960
|
+
function helper() { return 1; }
|
|
1961
|
+
function Get(): any { return () => {}; }
|
|
1962
|
+
export class C { @Get() m() { helper(); } }
|
|
1963
|
+
`,
|
|
1964
|
+
},
|
|
1965
|
+
want: ["helper"],
|
|
1966
|
+
why: "装饰器不得影响方法体遍历",
|
|
1967
|
+
},
|
|
1968
|
+
{
|
|
1969
|
+
name: "正例·⑤ 构造函数注入(方法用注入进来的依赖)",
|
|
1970
|
+
fn: "C.m",
|
|
1971
|
+
files: {
|
|
1972
|
+
"a.ts": `
|
|
1973
|
+
export class C { constructor(private repo: any) {} m() { this.repo.save(); } }
|
|
1974
|
+
`,
|
|
1975
|
+
},
|
|
1976
|
+
want: ["save"],
|
|
1977
|
+
why: "DI 形态;探针里五种已知失效形态之一",
|
|
1978
|
+
},
|
|
1979
|
+
// ── 正例:顺手测到的、没人提过的形状 ──────────────────────
|
|
1980
|
+
{
|
|
1981
|
+
name: "正例·⑥ static 方法",
|
|
1982
|
+
fn: "C.m",
|
|
1983
|
+
files: {
|
|
1984
|
+
"a.ts": `
|
|
1985
|
+
function helper() { return 1; }
|
|
1986
|
+
export class C { static m() { helper(); } }
|
|
1987
|
+
`,
|
|
1988
|
+
},
|
|
1989
|
+
want: ["helper"],
|
|
1990
|
+
why: "static 也在 getMethods() 内,同属本轮",
|
|
1991
|
+
},
|
|
1992
|
+
{
|
|
1993
|
+
name: "正例·⑦ private 方法",
|
|
1994
|
+
fn: "C.m",
|
|
1995
|
+
files: {
|
|
1996
|
+
"a.ts": `
|
|
1997
|
+
function helper() { return 1; }
|
|
1998
|
+
export class C { private m() { helper(); } }
|
|
1999
|
+
`,
|
|
2000
|
+
},
|
|
2001
|
+
want: ["helper"],
|
|
2002
|
+
why: "可见性修饰符不得影响提取",
|
|
2003
|
+
},
|
|
2004
|
+
{
|
|
2005
|
+
name: "正例·⑧ 抽象类里的实现方法",
|
|
2006
|
+
fn: "C.m",
|
|
2007
|
+
files: {
|
|
2008
|
+
"a.ts": `
|
|
2009
|
+
function helper() { return 1; }
|
|
2010
|
+
export abstract class C { abstract a(): void; m() { helper(); } }
|
|
2011
|
+
`,
|
|
2012
|
+
},
|
|
2013
|
+
want: ["helper"],
|
|
2014
|
+
why: "abstract 无体,但有体的方法照旧要提",
|
|
2015
|
+
},
|
|
2016
|
+
{
|
|
2017
|
+
name: "正例·⑨ 链式调用 this.a.b()",
|
|
2018
|
+
fn: "C.m",
|
|
2019
|
+
files: {
|
|
2020
|
+
"a.ts": `
|
|
2021
|
+
export class C { a: any; m() { this.a.b(); } }
|
|
2022
|
+
`,
|
|
2023
|
+
},
|
|
2024
|
+
want: ["b"],
|
|
2025
|
+
why: "末端名即被调用的方法名",
|
|
2026
|
+
},
|
|
2027
|
+
{
|
|
2028
|
+
name: "正例·⑩ 计算属性名方法",
|
|
2029
|
+
fn: "C.[K]",
|
|
2030
|
+
files: {
|
|
2031
|
+
"a.ts": `
|
|
2032
|
+
function helper() { return 1; }
|
|
2033
|
+
const K = "m";
|
|
2034
|
+
export class C { [K]() { helper(); } }
|
|
2035
|
+
`,
|
|
2036
|
+
},
|
|
2037
|
+
want: ["helper"],
|
|
2038
|
+
why: "名字解不解得出是另一回事,calls 不该受名字影响",
|
|
2039
|
+
},
|
|
2040
|
+
// ── 正对照:既有载体不得被误伤 ────────────────────────────
|
|
2041
|
+
{
|
|
2042
|
+
name: "正对照·⑪ 函数声明(基线)",
|
|
2043
|
+
fn: "plain",
|
|
2044
|
+
files: {
|
|
2045
|
+
"a.ts": `
|
|
2046
|
+
function helper() { return 1; }
|
|
2047
|
+
export function plain() { helper(); }
|
|
2048
|
+
`,
|
|
2049
|
+
},
|
|
2050
|
+
want: ["helper"],
|
|
2051
|
+
why: "本轮只补类方法,函数声明分支不得回归",
|
|
2052
|
+
},
|
|
2053
|
+
{
|
|
2054
|
+
name: "正对照·⑫ 箭头函数(基线)",
|
|
2055
|
+
fn: "arrow",
|
|
2056
|
+
files: {
|
|
2057
|
+
"a.ts": `
|
|
2058
|
+
function helper() { return 1; }
|
|
2059
|
+
export const arrow = () => { helper(); };
|
|
2060
|
+
`,
|
|
2061
|
+
},
|
|
2062
|
+
want: ["helper"],
|
|
2063
|
+
why: "同上;箭头分支不得回归",
|
|
2064
|
+
},
|
|
2065
|
+
// ── 负对照:防修过头 ─────────────────────────────────────
|
|
2066
|
+
{
|
|
2067
|
+
name: "负对照·① 嵌套箭头回调里的调用仍不提取",
|
|
2068
|
+
fn: "C.m",
|
|
2069
|
+
files: {
|
|
2070
|
+
"a.ts": `
|
|
2071
|
+
function helper() { return 1; }
|
|
2072
|
+
export class C { m() { setTimeout(() => { helper(); }, 0); } }
|
|
2073
|
+
`,
|
|
2074
|
+
},
|
|
2075
|
+
want: ["setTimeout"],
|
|
2076
|
+
dontWant: ["helper"],
|
|
2077
|
+
why: "既有设计:extractDirectCalls 遇嵌套函数即 skip。已实测函数声明/箭头/类方法三者口径一致 ⇒ 本轮不动",
|
|
2078
|
+
},
|
|
2079
|
+
{
|
|
2080
|
+
name: "负对照·② 真实调用不得挤掉语义标记",
|
|
2081
|
+
fn: "Uploader.read",
|
|
2082
|
+
files: {
|
|
2083
|
+
"handler.ts": `
|
|
2084
|
+
import * as fs from "fs";
|
|
2085
|
+
export class Uploader {
|
|
2086
|
+
read(filePath: string) {
|
|
2087
|
+
return fs.readFileSync(filePath, "utf-8");
|
|
2088
|
+
}
|
|
2089
|
+
}
|
|
2090
|
+
export function dispatchTool(args: any) {
|
|
2091
|
+
const target = params.arguments.file_path;
|
|
2092
|
+
return new Uploader().read(target);
|
|
2093
|
+
}
|
|
2094
|
+
`,
|
|
2095
|
+
},
|
|
2096
|
+
want: ["readFileSync", PATH_MARK],
|
|
2097
|
+
why: "本轮是**补**真实调用,标记必须仍在——否则污点轴会整体倒退。" +
|
|
2098
|
+
"(写这条用例时先跑探针确认判据:marker 只在**调用方把污点传进来**时才注入到" +
|
|
2099
|
+
"被调用方,无调用方的类方法本就无 marker —— 探针 ①/④ 一致、②/③ 一致)",
|
|
2100
|
+
},
|
|
2101
|
+
];
|
|
2102
|
+
for (const c of cases) {
|
|
2103
|
+
(0, vitest_1.it)(`${c.name} —— ${c.why}`, () => {
|
|
2104
|
+
const dir = makeProject(c.files);
|
|
2105
|
+
try {
|
|
2106
|
+
const calls = marksFor(dir, c.fn);
|
|
2107
|
+
for (const w of c.want)
|
|
2108
|
+
(0, vitest_1.expect)(calls).toContain(w);
|
|
2109
|
+
for (const d of c.dontWant ?? [])
|
|
2110
|
+
(0, vitest_1.expect)(calls).not.toContain(d);
|
|
2111
|
+
}
|
|
2112
|
+
finally {
|
|
2113
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
2114
|
+
}
|
|
2115
|
+
}, 150000);
|
|
2116
|
+
}
|
|
2117
|
+
});
|
package/dist/extract-ir.js
CHANGED
|
@@ -338,6 +338,18 @@ function manualResolveModule(moduleSpecifier, sourceFilePath, projectRoot, origi
|
|
|
338
338
|
}
|
|
339
339
|
return null;
|
|
340
340
|
}
|
|
341
|
+
/**
|
|
342
|
+
* 提取函数体内**真实出现的调用名**(区别于 computeMarkerCalls 产出的语义标记)。
|
|
343
|
+
*
|
|
344
|
+
* 2026-09-21(类方法空转修复):形参类型补 MethodDeclaration。
|
|
345
|
+
* 此前本函数只对函数声明 / 箭头 / 包装箭头三个载体调用过;主 IR 循环里的
|
|
346
|
+
* **类方法分支从来没调过它**,只调了 computeMarkerCalls ⇒ 类方法的 calls 里
|
|
347
|
+
* 永远只有标记、没有真实调用 ⇒ 状态机在 OO 代码上拿不到输入(FP 观测池实测:
|
|
348
|
+
* NestJS 切片 61 个函数里 59 个 calls 为空)。该空转自类方法提取功能诞生即存在,
|
|
349
|
+
* 因 taintpath 十一族语料全是函数载体而从未现形。
|
|
350
|
+
* MethodDeclaration 与 FunctionDeclaration 一样具备 getBody() / getText(),
|
|
351
|
+
* 遍历逻辑无需改动。
|
|
352
|
+
*/
|
|
341
353
|
function extractDirectCalls(func, preText) {
|
|
342
354
|
const body = func.getBody();
|
|
343
355
|
if (!body)
|
|
@@ -2861,7 +2873,10 @@ function _extractSingleProject(absRoot, tsconfigPath) {
|
|
|
2861
2873
|
continue;
|
|
2862
2874
|
const mParams = m.getParameters();
|
|
2863
2875
|
const mText = m.getText();
|
|
2864
|
-
|
|
2876
|
+
// 2026-09-21:补上真实调用提取(此前只有 computeMarkerCalls,见 extractDirectCalls 注释)。
|
|
2877
|
+
// 顺序与函数声明 / 箭头两个分支保持一致:先真实调用,再追加语义标记。
|
|
2878
|
+
const mCalls = extractDirectCalls(m, mText);
|
|
2879
|
+
mCalls.push(...computeMarkerCalls(mText, mParams.map((p) => p.getName()), sinkParams, onMethodHit, guardFns, directGuardFns, shapers));
|
|
2865
2880
|
funcs.push({
|
|
2866
2881
|
name: `${cn}.${mn}`,
|
|
2867
2882
|
params: mParams.map((p) => ({ name: p.getName(), type: getParamType(p), typeDetail: getParamTypeDetail(p) })),
|
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.47";
|
|
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.47",
|
|
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/",
|