Skip to content

Releases: DannyAn/secguard-clang

v0.9.9

Choose a tag to compare

@github-actions github-actions released this 22 Oct 10:33

[0.9.9] - 2026-10-22

第三方库 API 语义模型(libxml2 / cJSON / libc 扩展 API)

docs/secguard-clang支持第三方库函数.md 首批清单落地:sgre/internal/apikb/external_api.go
新增 ExternalAPISpec(API 身份 / 分配 / 释放 / 所有权 / 返回可空性 / 借用,各维独立、
来源可审计),模型通过既有 IsDeclaredAllocator / IsDeclaredDeallocator /
IsKnownNullableReturn / IsEscapeFunction 查询通道被 memory-leak、null-deref、UAF/
double-free、graph ownership 边共同消费——不在各 detector 里散落第二份名单。落地能力:

  • 专用配对验证(docs §6):xmlNewDoc→xmlFreeDoc、xmlXPathNewContext→xmlXPathFreeContext、
    xmlXPathEvalExpression→xmlXPathFreeObject、cJSON_Create*/Parse→cJSON_Delete、
    cJSON_Print
    →cJSON_free(free 等价);错配释放(XPath 对象交给 context 释放器、
    free() 释放 XPath context、cJSON_Delete 释放 strdup 块)不再算有效释放,泄漏候选带
    free_mismatch 证据与 free-mismatch@ verdict fact 交 AI 研判。malloc/calloc/realloc
    为 unrestricted(保持 tc116 等既有行为);未建模分配 API 行为不变。
  • 所有权三态(docs §5.2C):cJSON_AddItemReferenceToObject/Array 建模为 Borrow,
    覆盖 _add 启发式的误转移(引用型添加后不删 item 的泄漏从漏报翻为 definite);
    cJSON_AddItemTo* / xmlDocSetRootElement / xmlAddChild / xmlAddSibling 建模为
    TransferOnSuccess——未检查返回值时豁免,守卫内(if (!add(...)))且无失败分支释放时
    不再无条件豁免(失败分支泄漏以 suspected 候选报出,transfer-cond@ 提示 AI);
    xmlSetNs 因版本差异保守取 TransferAlways。转移参数按模型 argIdx 精确匹配。
  • 出参分配成功分支绑定(docs §5.3):asprintf/vasprintf/getline/getdelim 的
    char** 出参分配从 detector 硬编码迁入模型;if (asprintf(&p,...) < 0) return;
    (含 ret = asprintf(...); if (ret < 0) 赋值形态)的失败早退不再当泄漏证据
    (该分支分配未发生)。失败方向只匹配 < 0 / == -1(空输出 0 是成功,防漏报)。
  • 可空借用(docs §5.1D/§5.2A):xmlDocGetRootElement / xmlFirst/NextElementChild /
    cJSON_GetObjectItem* / cJSON_GetArrayItem 挂 nullable 精确表(与 strchr 同档
    auto-confirm),同时 RetBorrow 保证永不当作新分配——null-deref 对这批 API 的
    unguarded 解引用从 external-unknown suspected 升为 confirmed,守卫/仅比较形态不报。
  • cJSON 生命周期标准用法(用户权威形态):cJSON_DetachItemFromObject/CaseSensitive/ FromArray 建模为 AllocRet(脱离后归调用方,root 的 Delete 不再覆盖 → 脱离后不删是
    definite 泄漏);新增 free-on-borrow 通道——释放借用指针(cJSON_GetObjectItem 的
    item 被 cJSON_Delete、strstr/getenv/setlocale 结果被 free)报 invalid-free/UAF 风险
    suspected 候选(hint free-on-borrow@<api> + verdict fact,永不 auto-confirm,AI 判
    CWE-415/416 后果);借出后重新赋值则不报(不能证明释放的还是同一对象)。
  • 测试:apikb 模型表驱动 11 组;evidence 泄漏矩阵 24 个 tc140-163 fixture(含用户
    main 标准用法/Detach/借用删除形态);planner null-deref 6 组 + free-on-borrow 管道
    1 组;benchmark Phase 26+26b(P26-01..12,5 finding + 6 no_finding + 1 suspected 侧)。
    干净 A/B(独立 --db):第一轮候选 78→76、auto-confirm 139→143、post-only 空;第二轮
    (26b)候选 76→77(post-only 恰为 P26-10 借用删除预期新增)、auto-confirm 143→144
    (+1 = P26-12 Detach 泄漏)、pre-only 空。

use-after-free may-free 通道(用户 cbwl_tbl.c 样例)

用户五步审计法样例(PATTERN-02 释放后间接访问 + PATTERN-07 宏展开隐式 UAF)暴露
的漏报:防御式条件释放 wrapper(cbwl_tbl_destory 体内 if (node != NULL) free(node))的 FuncSummary 是 may-free(ParamMayDirectFrees),而
findAllFreeSites 只消费 must 的 ParamDirectFrees——跨函数条件释放后的使用
(含经宏 cbwl_nodeinfo 包装、展开后解引用 node->key 的间接使用)全部漏报。
修复:may-free 也生成 free site(事件带 may_free + callee);findUseSites 的
wrapper 参数 skip 同步扩 may(防释放行自报);LifetimeFilter 的
mustReaching→confirmed 加 !c.MayFree gate(对齐 double-free 的 MayFree
先例——conditional release 永不自动确认,suspected 交 AI,hint 带
may-free/cond@<helper>);use-after-free 的 SKILL.md Evidence Pattern 与
守卫测试同步(may_free fragment)。测试:
tc_uaf_conditional_release_use.c(用户精确形态三变体:may 漏报红→绿、
must 确认、修复对照零事件)+ planner 分级测试(may=suspected、must=confirmed)

  • benchmark Phase 27(P27-01 must+宏 finding 走 macro-context AI 复核、
    P27-02 修复对照 no_finding);A/B 全量 78→78 / 145→145 双向差集空(零回归)。

用户 HRP 空指针审计样例回归(能力锁定,零检测器改动)

用户两文件五步审计样例(CWD-1031/1034 四 PATTERN)端到端判定全部已检出,无需修复:
P-02(guard 分支内 ctx = Hrp_AllocSession(peerId) 重赋值为树内 proven-nullable
wrapper(malloc+字面 return NULL)后未判空)→ retNullable proven 通道
auto-confirmed(入口 ctx==NULL 守卫不保护重赋值后的解引用,ND-16 同档);
P-03(ctx->negotiator->state 成员链,extern assumed 源)→ null@opaque-origin
suspected + implicit-contract ptr_guard 双通道;P-09(IPC msg->ctxPtr 强转
解引用)→ null@opaque-origin src@ 强转行 suspected + implicit-contract;
P-05(条件与指针无关的分支内外双重解引用)→ null@caller-arg suspected
(boundary 场景实证)。用户三个修复方案全通道零候选(FP=0)。落地:
tc164/tc165/tc166 fixture + planner 五组单测(四 PATTERN tier 断言 + 修复
对照零候选)+ benchmark Phase 28(P28-01 finding + P28-02/03/04 修复
no_finding;P-03/P-09/P-05 suspected 侧由单测锁定——P-05 依赖 boundary 语义,
全量目录含 main 时 hrp 函数不可达被 CallReachFilter 正确丢弃)。A/B 80→80 /
146→146 双向差集空(零回归)。判定记录:现有 fixture(tc106 等)仅覆盖
cast-reassign 防误报方向,用户四 PATTERN 正向形态此前无同类用例,本轮补齐。

goto cleanup 形态 CFG 缺口修复(用户 hrp_buf.c double-free 样例)

用户样例 PATTERN-01(错误路径 free 后 goto cleanup 跳到 return 0; 之后的
label 块再次 free)暴露 StmtCFG 架构缺口:buildBlock 在 terminator 后 break,
label 块整体不建模(无节点/无事实/无可达性);goto 的 dead-end 处理注释
"exact for forward jumps" 前提不成立——cleanup 形态的 label 恰位于 return 之后,
流 filter 将候选误 dismiss 为 unreachable(端到端实证漏报,detector 事件已生成、
filter input=2 output=1)。修复(graph/control_flow.go):
(1) buildBlock 在 terminated 后为 labeled_statement 建 detached 孤立链
(from=-1 无直连入边,label 后续语句链式挂接,return 出口正常连 exit);
(2) 新增 linkGotoLabels 后处理——解析 goto_statement 的 statement_identifier
目标与 labeled_statement 名匹配,补 goto 节点→label 首语句节点边(去重、
self-edge 防护);(3) fall-through NULL 事实语义保持(goto 边只从 goto 节点
出发,if (p==NULL) goto out; 反证单测锁定)。P-06(跨函数条件释放)走
既有 may-free 通道零改动即检出(suspected + may-free/cond@ hint)。测试:
tc167/tc168 fixture + planner 四组单测(P-01 confirmed 含 free@30,38 hint、
P-06 suspected+mayFree+may-free/cond@queue_push、修复对照零候选、goto
null-guard fall-through 反证)+ benchmark Phase 29(P29-01 finding /
P29-02/03 no_finding);A/B 候选 83→83 双向差集空、auto 148→149(+1 =
P29-01),go test ./... + nosqlite 全绿。

0.9.9 发布前自检修复(GLM5.3 改动复核)

发布前对 0.9.8 之后 20 个提交(GLM5.3 为主)逐项复核,go build ./... /
go test ./... / nosqlite 子集全绿;发现并修正两处:

  • use-after-free cond@ hint 越界:buildHintParts 对任何带 callee 的 UAF 候选
    都发 cond@<helper>,但 callee 在 must 间接释放(callee 无条件 free)与字段释放时
    也会置位——cond@("条件释放 helper")语义被错误安到已证实释放的候选上。改为仅在
    may_free 时发 cond@,并把 hint token 描述从 double-free 专属措辞泛化。
  • 出参分配失败守卫 < 方向过宽:isFailureLiteral 对 op == "<" 对任意数字字面量
    都返回真,if (ret < 5) return; 这类正值比较会被误判为"分配失败"而吞掉成功短输出
    分支上的真实泄漏。收紧为字面量 <= 0 才算失败守卫(< 0 与负边界),正值比较不再豁免。

null-deref 守卫形态误判修复(生产 MAF 扫描报告,候选重发)

生产环境 MAF 全量扫描(122 文件 / 977 函数)反馈报告经逐条实证后落地两项修复
(报告的 boundary tier 一刀切降级建议经三重语义冲突验证后否决,未采纳):

  • ternary 内联守卫识别(auto-confirmed 误报修复):return (profile != NULL ? profile->field : default) 形态被机器判定为 null@malloc-fail + confirmed 并
    auto-confirmed——三元表达式位于单条直线 CFG 节点内,边 kill 守卫模型不可见。
    新增 buildTernaryGuardMap:从 AST 精确记录守卫分支(consequence /
    alternative)内 base 与行号均精确的解引用点,nullable_source 收敛阶段消费。
    AST 级分支判定保证同行 p == NULL ? p->a : 0(NULL 路径解引用在 consequence)
    仍可报出——行区间近似会误杀该真缺陷形态。
  • branch-hint 宏包装条件解包(auto-confirmed 误报修复):
    if (unlikely(slot == NULL)) return; 形态同样产生 confirmed 误报——
    call_expression 条件对守卫解析不透明。parser 层 GuardedNonnullVars /
    NullCheckedVars 入口统一解包 likely / unlikely / __builtin_expect
    (含 *_likely / *_unlikely 厂商变体与成对 !! 否定),全部 20+ 消费点
    (guard_model 边 kill、短路弱守卫、caller_null、null_guard、unchecked_return、
    contract_gap、memory_leak)一次受益。
  • 验证:A/B 基线对照——修复前 2 个 CWE-476 auto-confirmed 误报(ternary +
    unlikely 各一),修复后 0;benchmark 83/83 候选与 16/16 null-deref
    auto-confirmed 完全一致(零回归);全量测试与 nosqlite 子集绿;7 个红绿对照
    用例转正回归测试(zz_null_deref_guard_forms_test.go),boundary fail-open
    契约另固化 characterization 测试(zz_null_deref_boundary_char_test.go)。

AI 研判编排优化(并发复核去重 + 跨函数模式复用)

同报告落地的指令层两项(extension/shared,一致性守卫通过):

  • RANGE-split 类型 auto-confirmed 复核 owner 去重:单类型按候选区间拆分为多个
    并发子代理时,--list-auto-confirmed 对每个 sibling 返回同一批未复核条目(幂等
    过滤只剔除已有 verdict 的条目,并发竞态下 sibling 尚未写入),201 候选 4 分片
    场景 7 条 auto-confirmed 被复核 28 次。编排指令改为指定首个分片为该类型复核
    owner、其余 sibling 跳过该节;audit 前 count: 0 全局校验保持兜底(owner 分片
    失败时补派 re-review-only 子代理)。
  • 跨函数同模式研判复用(Group verdict 扩展):同文件同 tier 同形态的候选组
    (回调四件套注册进同一 ops 结构、迭代器族、同框架参数的 sibling handler)从
    一个代表函数的 Code Context 一次研判,跨 sibling 以 pattern:<file>:<tier>
    引用;形态分叉的 sibling 必须单独研判,模式复用不得越过异质项。
  • 预期效果:MAF 场景 null-deref 2 个 auto-confirmed 误报归零(对应复核负担
    消除)+ 同质合约研判成本经模式复用显著下降;201 候选量级压缩需按"根因审查
    单元"方向另行立项,本版本不改变 boundary fail-open 候选协议。

null-deref 端到端复检与系统性修复(20 commits,全程 TDD)

对 null-deref 子链路(detector → planner → auto-confirm → candidates → AI 研判)
做端到端复检并逐项修复,覆盖 2 CRITICAL / 12 MAJOR / 5 MINOR + 1 项编排文档:

  • 跨函数空值传播断链(CRITICAL):functionParamNames 只认直接
    function_declarator,所有返回指针的函数(T *f(T *p) 的 declarator 被
    pointer_declarator 包裹)被 caller_null / opaque_null / buffer_overflow /
    buffer_unchecked_index / contract_gap 五个 detector 同时跳过,参数永不 seed;
    叠加 computeRetNullable fixpoint 未按 buildFlowResults 方式拆分
    entrySeeds/definiteEntrySeeds——caller_null 源按行号分桶后不落任何 CFG 节点,
    T *q = p; return q; 形态的 callee 被误判 notNull,所有调用点派生解引用静默
    漏报。两处协同修复(evidence 剥壳 + planner 拆分),端到端实证 callee(NULL)
    调用点候选恢复。
  • AI 举证断链(CRITICAL):assumed-return 变量路径候选(p = f(); p->field,
    f 为 fail-open in-scan callee)的 Hint/VerdictFact/SKILL 三处都承诺"callee body
    已嵌入 ## Callee Definition",但 markdown 嵌入条件只覆盖 IsCallResultDeref 与
    malloc-fail——AI 翻遍候选文件找不到承诺的段落。嵌入条件对齐 buildHintParts。
  • detector / evidence 形态覆盖(9 项):指针算术解引用(*(p+1))基指针
    提取;guard 判定删除 evidence 本地漂移副本统一走 parser.GuardedNonnullVars
    (p != q 误判 + unlikely() 漏识双向漂移);caller 短路 || 守卫;cast
    guard 结构提取变量名("(struct T *)p" 垃圾键);p = p->next 循环携带发
    field_read 源(D8 测试断言曾把漏报固化为正确,已修正);out-of-scan alloc
    wrapper(xmalloc 仅声明)双边真空按"谁实际处理"拆分 skip;(*p).x 基变量
    键卫生;assert 必须 dominating call(嵌套 assert 不再误 guard);interprocedural
    arg 对齐 classifyArg(cast/field 形态不再跳过)。
  • planner 守卫与分析(6 项):switch null-case-return 守卫(buildSwitch 新建
    透明 header 节点供 guard 挂载);branch-hint + helper 复合守卫
    (unlikely(is_empty(p)));deref-read 赋值(r = *(T**)msg)发 deref_read
    源且不 contract-derived;cast 跳 type_descriptor(g_arr + i * 8 静态数组
    算术不再残留 suspected);isGrowCall 词边界(growth_rate 不再误命中);
    typedef 拆分形态 opaque 判定(typedef struct Foo Foo_t; + 独立定义)。
  • AI 协议一致性(5 项):null@opaque-origin / null@assumed-return 两个
    tier token 补入 agent-body / command-instructions 主清单,新增 ...
Read more

v0.9.8

Choose a tag to compare

@github-actions github-actions released this 21 Oct 07:45

[0.9.8] - 2026-10-21

两项"待拍板"的结论(已按方案落地,不再等决策)

1) auto-confirmed 复核:不做抽样,做"策略类批量确认"。 unreviewed=0 是
"AI 阶段完成"的硬门禁,抽样会让它变成可跳过项——用 18 条一次性 --review-json
的写入成本,换一个会被悄悄放宽的诚实性契约,不值得。真正该省的是"对纯名单类
发现逐条读上下文":dangerous-function(禁用 API)/signal-handler(异步不安全
API)/weak_algorithm/weak_random 的证明就是"名字在名单里",复核写成一次批量
confirmed + reason: policy:<list>,零读源、零 Evidence 打开;语义类
(null@*/leak/uninit/oob)保持逐条复核。

2) null-deref 系统性误报:不做"非空约定推断",做"扫描范围 + 组裁决"。 跨函数
字段写入推断对外部构造的对象不成立(边界扫描里调用方在树外,字段值恰恰未知),
用它压候选等于用误报换漏报。真正可闭环的是两条:

  • 扫描范围:新增 scan_scope 诊断,且 boundary 模式写进扫描摘要——"目标是无
    main 的子树,边界指针参数一律 fail-open,请扫 worktree 根"。maf 的 201 条
    null-deref 最可能就是这一类(扫 maf/src 而 main 在别处)。
  • 组裁决:同函数行在 _index.md 里连续(按 file:line 排序),一次判一个契约,
    其余行复用 group:<fn>:<tier>;boundary 扫描额外给"树外调用方"快捷判据。
    候选条数不变(每个 still 一条审计记录),但研判从"逐行重推契约"变成"每函数一次"。

扫描流程闭环:读源/证据预算、子代理红线、组裁决、扫描边界诊断

按 maf 生产扫描复盘(122 文件 / 977 函数,种子 5250 → 候选 314,AI 研判 520s)逐条核对:

  • --complete-type 协议矛盾(真 bug):step 4 的 Claude Code 分支原文让子代理写完
    findings 后调 --complete-type,而 OpenCode 分支与"子代理禁止"规则说不要——子代理
    照做了。已删除该指令,并在子代理提示词顶部加红线(禁止 --complete-type/--audit、
    禁止重跑 scan/plan/index)。MarkAIStageDone 是幂等 UPDATE(同值并发不会写坏行),
    真正的风险是"提前置 done"让 orchestrator 跳过未写完的类型。
  • Evidence 打开预算:此前只有 ≤5 源读取预算,Evidence 打开无上限(memory-leak 25/25
    全开)。新增 MAX_EVIDENCE_OPENS_PER_TYPE=12:决定性 Hint 必须零打开结案
    (null@cfg-proof/null@malloc-fail/leak@definite+escape@none/常量 idx@ 越
    cap@/contract@known/async-unsafe@/cast@),只有启发式 token 才开;超预算时
    未决候选进结构化报告 needs_context,orchestrator 按候选范围二次下发一次。F6 审计
    增加 evidence_opens > 12 观察项。
  • leak 类型 Hint 可裁决:memory-leak 技能补明确表(leak@definite+escape@none
    直接 confirm;leak@conditional 对比 free@N,M;只有 escape@return|store 才开
    Code Context)。
  • 组裁决:null-deref / uninit / leak 中同 Function + 同来源 token + guard@none
    的行按一个契约问题判一次,其余复用 group:<fn>:<tier>,不再逐行重推(对应"201 候选
    0 确认"的研判成本)。
  • confirmed=0 跳过写入:全 dismissed 的分片不写 findings 文件、不调 --write-json。
  • 分片均衡:n = ceil(count/100) 段、每段 ceil(count/n)(201 → 3×67),不再
    100+100+1 或 55/55/55/36。
  • 扫描边界诊断(新增 scan_scope):scan 输出新增 mode(complete-program /
    boundary)+ enclosing_worktree + 提示。扫描 repo/src 而 main 在 repo/app
    时,整个模块会被当作外部边界 fail-open——这是 maf 201 条 null-deref 候选最可能的
    量级来源;诊断让它第一次就可见,而不是靠 AI 判 201 次"外部契约"。
  • PATH 写法:orchestrator 脚本禁止 VAR=... for ...,已写进提示词。
  • 未改(需产品决策):auto-confirmed 抽样复核(会破坏 unreviewed=0 终态门禁);
    null-deref 的项目级非空约定建模(需跨函数字段写入分析)。

测试夹具的 fd 0 关闭竞态(随机 FAIL 的真因)

log.New(os.NewFile(0, os.DevNull), ...) 把 fd 0 包成了 *os.File:GC 执行该
File 的 finalizer 时会 close(0);此后任何 open() 拿到 fd 0,再被下一次 finalize
关闭,就出现随机的 TempDir RemoveAll cleanup: unlinkat ...: bad file descriptor。
这就是十轮里"不同用例随机挂、单独跑又过"的根因(与检测逻辑无关),在 TMPDIR 放进
仓库或 /tmp 时都会出现。四处测试助手改为 log.New(io.Discard, ...);planner +
evidence 连续 8 轮全量复跑 0 失败(此前约 2/6 轮随机失败)。

检测器输入统一为全树事实层(声明在头文件不再丢失)

审计发现的系统性缺陷:多处以 root.FindAll(...) 只采当前文件的声明事实,声明放在
头文件(axxx.h -> axxx.c、axxx.h + bxxx.h -> cxxx.c 这种 C 最常规布局)时容量/
类型/宏静默取不到,检测直接消失——out-of-bounds 的 svr[actived_index] 只是被撞见的
一个实例。

  • 新增共享事实层 internal/repofacts:一次遍历全部索引文件,聚合 typedef 别名、对象
    宏、struct/union 布局(成员类型/指针/数组容量+容量符号)、文件作用域数组;按 struct
    tag / typedef 别名精确解析(同名字段互不遮蔽),冲突即判未知(不猜)。
  • 接入的消费者:out-of-bounds / buffer-overflow 的成员数组与全局数组容量、容量宏;
    null-deref planner 的数组基址判定(头文件里的数组不再被当成可空指针)。per-file
    扫描仍是快路径,事实层只做兜底。
  • 类级回归:TestDeclarationFactInvariance —— 同一组 fixture 的内联版与头文件拆分版
    必须产出完全相同的候选(out-of-bounds、buffer-overflow),以后任何"只看当前
    文件"的退化都会被它挡住;另加 TestUncheckedIndexHeaderDeclaredStruct(原帖形态)与
    TestArrayCapacityDeclaredInHeader(头文件全局数组)。
  • 体量:benchmark out-of-bounds 2 → 3(新增即 P25)、null-deref 22 不变;redis
    out-of-bounds 1 → 36、buffer-overflow 219 → 230、null-deref 1320 不变。
  • 已知未覆盖:crypto_misuse 的 undersized-key 仍只扫 declaration,结构体成员 key
    在头文件时不报——它需要"非函数实体归属"的事件模型,本轮不改(明确记录)。

本版本经多轮重发收敛为最终内容:正文只列最终能力、验证结果与升级方式(逐轮迭代
明细见 git log 中各提交说明)。

发布概要

  • null-deref:第三方 typedef / 前向声明 / 头文件定义的类型不再因为"类型信息不完整"
    而漏报;外部入口按扫描边界判定是否 fail-open;派生指针的来源归因正确;同一指针的
    多处解引用在一份候选证据里列全。
  • out-of-bounds:固定数组成员以未校验配置字段为下标的越界读,连同下游空指针以
    "一条 finding + CWE-125→CWE-476 链条"上报。
  • unchecked-return:按消费点普查收敛"调用处未检查返回值"的系统性误报。
  • CLI:DB 向上查找在项目根停止,避免把外层 checkout 的旧库当成当前项目。

null-deref:不完整类型信息

  • 断点在播种,不在解析:tree-sitter 对未知 typedef 完全容错,语法与解引用节点都在。
  • opaque 字段值通道:base 参数类型在扫描树内不可见时,p = (T *)base->field 与
    链式中间值 base->f1->f2 播种 fail-open 来源(suspected,永不 auto-confirm)。
  • 类型普查:collectDefinedTypes 覆盖无函数的头文件(此前被跳过,导致树内 header
    类型被误判 opaque);typedef struct X X_t; 前向声明仍判不可见。
  • 入口边界策略(取代旧"布局可见 = 非空"判据):扫描树内有 main 时按项目自身非空
    不变式收敛;无 main(库 / 部分子树)时边界即外部 ABI,一律 fail-open。两个基准语料
    均为完整程序,候选量零变化。
  • 派生指针来源:q = (T *)param、q = t(opaque 字段读)、q = malloc 别名 的
    tier 归到真正持有来源的变量(caller-arg / opaque-origin / malloc-fail),不再一律
    external-unknown。
  • 多解引用点:按变量收敛仍是一条候选,但 deref_lines 与证据 sink 段列出全部解引用
    行(分支内与分支后的 tree[0].group_name 都在)。
  • 地址取用普查:注册表裸函数名({"get", getCommand})与被 #include 的生成注册
    片段(commands.def)都计入;ADDR_TAKEN 因此补齐,注册回调不再被当作死代码或
    不可达而丢弃。

null-deref:误报与候选量级

  • 调用链开放性 fixpoint:只在"可能为空的值真能到达参数"时播种;纯转发不播种、任一
    环节的守卫闭合整条链。修复生产环境候选从历史 ≤160 爆炸到 1264 的问题。
  • 守卫契约:短路求值、条件非空 getter、guard-mutex 调用三类误报收敛(6 项 FP)。
  • retNullable 三态:proven(体内有 NULL 路径)/ assumed(fail-open)/ 不可为空,
    分别对应 confirmed / suspected / dismiss。

out-of-bounds:未校验外部索引

  • 新增 array_oob_read_unchecked / array_oob_write_unchecked(suspected):结构体
    固定数组 + 字段来源下标 + 函数内无与容量相等的边界检查。
  • 头文件拆分形态:成员数组容量改为全树普查(宏跨文件合并、按 struct tag / typedef
    别名精确解析),结构体定义在头文件时不再漏报——生产回归即 svr[actived_index]
    全仓库 0 候选;&arr[idx] 传址逃逸优先作为 oob-escape@。
  • 链条以 cwe-chain@CWE-125→CWE-476 token、候选 ## Vulnerability Chain 段、SARIF
    properties.cwe_chain 导出;根因与后果只出一条 finding。
  • 回归:benchmark Phase 24/25(含头文件拆分正例与修复对照)+ evidence 同形态用例;
    体量 redis out-of-bounds 1 → 30、benchmark 2 → 3,其它类型不变。

unchecked-return

  • 消费点普查:调用结果被使用却未检查、且没有其他消费点做过检查时才上报,修复 IDM 报告
    25/25 全误报的根因。

CLI / 扩展

  • resolveExistingDBPath 在第一个含 .git 的祖先停止(项目根自己的 DB 仍然命中)。
  • claude-cac(CodeAgentCLI)宿主下子代理按命名空间名分发。

验证

  • go test ./...:16 个包全绿;-tags nosqlite 子集通过。
  • benchmark --selftest:206 用例、25 个 CWE 标签全映射。
  • 本版新增 evidence / planner 回归覆盖不完整类型、入口边界、派生来源、多解引用点、
    注册表与 include 片段;均验证"去掉修复即失败"。
  • 体量:redis 7.2.4(136k LOC)null-deref 候选 1283 → 1320(+2.9%,新增为注册回调的
    框架出参,suspected 交 AI);benchmark 22 条不变。
  • 性能:95k LOC / 100 文件合成语料 detectors 85.0s → 87.4s(+2.8%)。

升级

  • 重装二进制与扩展(./deploy.sh 或对应平台安装包),确认 secguard --version 为
    0.9.8。

v0.9.7

Choose a tag to compare

@github-actions github-actions released this 16 Oct 12:04

[0.9.7] - 2026-10-16

发布后修复(审计 P3 收尾)

  • 候选阶段 report.md 的 "Candidates by Skill" 表新增 Auto-confirmed 列,逐类型拆分机器确认数——此前纯 auto-confirmed 类型(如 out-of-bounds)在表中显示 0 候选,掩盖了实际已确认的发现。
  • 移除 injection 检测器命令注入事件中无人读取的死字段 source。

v0.9.6

Choose a tag to compare

@github-actions github-actions released this 16 Oct 11:38

[0.9.6] - 2026-10-16

证据契约断链修复(round2,R2-01~17 闭环)

本轮修复检测器已算出的语义事实在「候选装载 → Evidence 渲染 → Hint → 候选文件/索引」链路上被丢弃的问题,使 AI Agent 能从 _index.md 的 Source+Hint 列直接研判,而不是打开源码或凭空推断。

  • 补齐字段流:TOCTOU 的 check_function/use_function/path_arg、toctou_shared_state 的 mutex/锁行、crypto reason/key_size、format_arg、path-traversal path、memory/resource-leak 的 acquire api、integer-overflow lhs_influenced 等此前静默归零的字段,现经 eventProps 统一解码(数字字符串兼容)后真正进入 Candidate。
  • 补齐空 Hint 列:unchecked-return 的 contract@known/unknown + callee@<file>:<line> 定义定位、divide-by-zero 的 divisor@<shape>、deadlock 的 locks@A|B + reverse@<fn>、use-after-free/double-free 的 free@N/free@N,M、uninit 的 certain/maybe-uninit 等,任何类型的 Hint 不再出现裸 "—"。
  • Code Context 策略:integer-overflow 改按整函数 span 渲染(上限 200 行,签名→候选);源→汇锚行落在 ±8 窗口外时按 [min-3, max+3] 拓宽;taint 类候选填充 SourceLine 并新增 ## Taint Source 段。
  • unchecked-return:候选文件新增 ## Failure Contract 段——contract@unknown 时给出 callee 定义定位(跨文件工厂常见),无定义时显式标注"external/library",AI 不再烧 read 预算盲找。
  • hardcoded-secret:结构化 secret_value 片段脱敏(前缀 4 字符 + 长度),避免报告文件泄露完整字面量。
  • deadlock:反向持锁函数回填,reverse_order 片段与 reverse@ Hint 在生产链路首次可用。

契约守卫测试

  • TestSkillFragmentsMatchBuildEvidence:SKILL.md 的 Evidence Pattern 与 BuildEvidence 片段词汇双向一致性机器守卫。
  • TestSeedPropsFlowToCandidate:props→Candidate 字段流守卫(覆盖 R2-01~07 的断链)。
  • buildhint_test / evidence_package_test / zz_range_filter_test / zz_lock_order_test / zz_unchecked_return_callee_test:Hint 非空、DivisorInterval 传递、reverse 回填、callee 定位等定向回归。

v0.9.5

Choose a tag to compare

@github-actions github-actions released this 15 Oct 04:26

[0.9.5] - 2026-10-14(2026-10-15 二次重发)

unchecked-return 全局变量存工厂函数误报修复(二次重发)

修复 0.9.5 生产冒烟发现的 unchecked-return 误报:g_user_grp_id_mgr = idm_new_idmgr(&cfg)
这类「_new 工厂函数返回值存入全局变量」被误判为 CWE-252。根因有二:

  1. apikb.IsAllocator 的 _new 子串启发式把 idm_new_idmgr 这类对象工厂当成了分配器,
    契约仅按名字猜测(contract@unknown);
  2. 检测器的「返回值是否被检查」分析(checkedVars)是函数作用域的,而全局变量的
    判空发生在别的函数的 use 处(或懒初始化守卫),检测器看不到,于是把「存入全局」误判为
    「未检查」。

修复:unchecked_return.go 对 contract@unknown(名字启发式)的分配器调用,当返回值存入
非局部存储(全局/static/extern,判定为「既非本函数局部声明、也非参数」)时不再产事件。
真正缺陷(解引用 NULL 全局)由 null-deref 在 use 处兜底;声明式分配器(malloc/strdup/VOS_*)
存全局、启发式工厂存局部,均仍照旧报告。

回归用例:tc_unchecked_return_global_assign.c(单文件)+ unchecked_return_crossfile/
(跨文件复现 idm_db_id.c 引用 idm_idmgr.h 声明、idm_idmgr.c 实现的真实场景)+
unchecked_return_global_assign_test.go(全局存工厂 0 事件 / 局部存工厂仍 lead / malloc 存全局仍报)。

null-deref 字段强转派生指针漏检修复(重发)

修复 0.9.5 生产验证发现的系统性漏检(false negative):tree = (T*)(resp->group_data); tree[0] 这类「字段读取 + 强转 → 局部指针」的空指针解引用未被 null-deref 检出。根因是
字段/下标读取赋值 q = p->f 只传播字段位置 p->f 自身的空值源、不传播基指针 p 的
空值源,导致派生指针 tree 无法继承参数 resp 的 caller-null 源,在 nullable_source
过滤环节被淘汰。

  • null_flow.go:字段/下标读取复制新增 copyFieldBase 效应——q = p->f / q = (T*)p->f
    同时把基指针的空值源传导给 q(字段位置自身空值之外),派生指针得以继承 caller-null 源;
  • 仅作用于 null-deref(nonNullKills 门控),taint / use-after-free / uninit 流不变;
  • 派生指针仍为 suspected、不 machine-confirm,判空守卫(if (tree == NULL) return;)正确抑制;
  • 回归用例 zz_null_deref_field_cast_test.go:未判空调用方 / 字面 NULL 调用方 / 不透明结构体 / 判空抑制。

implicit-contract 隐式契约缺失检测(CWE-20,新增 vuln type)

新增 implicit-contract 能力:检测「函数入口缺少对输入对象关联状态校验」的隐式契约
缺失——结构体指针参数的可空指针字段(如 tree = (T*)resp->group_data)在无支配性判空的
情况下被解引用。区别于 null-deref 的「可证明空源」,本类型覆盖开放世界场景(字段由
调用方/框架供给、函数自身不写该字段),全部 suspected 交 AI 研判,绝不 machine-confirm。

  • 证据门(三选一,无证据不产候选):
    • E-b 自证:函数自身在另一条路径判过该字段空;
    • E-a 同类契约:同形参类型的兄弟函数判过该字段空(按声明类型锚定,不跨 struct 裸名匹配);
    • E-c 调用方:调用方判空作证据;所有调用方都判空则抑制(调用方已完成校验,§7.1)。
  • 确定性砍刀:call-reach 不可达丢弃;CFG ReachesAvoiding 守卫支配丢弃(逐 use site);
    字段被函数自身写则归 null-deref(避免双告警)。
  • count/length 只作可选增强证据,不 gate 检测——tree == NULL(指针判空)独立于任何
    计数关系;count==0 是业务语义,不做独立 count_guard。

配套:新增 CONTRACT_GAP 事件 + evidence/contract_gap.go detector +
planner/filter_contract_gap.go(守卫支配 + 调用方校验抑制)+ implicit-contract skill;
schema 枚举/vuln_type_skill.go 同步。回归用例:tc_contract_gap.c(E-b 部分守卫/纯指针
无 count/无人判空反例/调用方校验两侧)、tc_contract_gap_prod.c(生产用例
import_dn_group_tree_refresh 镜像 + 变体:兄弟契约、单分支判空、count==0 处理但指针未判、
字段读未解引用、调用方已判空)。

dangerous-function 分层策略扩展(CWE-676)

将 dangerous-function 从「10 个无争议函数的极简名单」扩展为分层的禁用函数策略
检查
,覆盖企业编码规范中「调用即违规」的函数族。检测器仍在
evidence/dangerous_function.go,内置规则表按 category 分层:

  • process_exit:exit/ExitProcess/pthread_exit/ExitThread(仅非入口函数)、
    atexit、abort
  • stack_allocation:alloca
  • nonlocal_jump:setjmp/longjmp
  • unsafe_memory/unsafe_string/unsafe_format/unsafe_scan:非 _s 的
    mem*/str*/wcs*/格式化输出/格式化输入/gets 族
  • unchecked_conversion:atoi/atol/atoll/atof
  • unsafe_alloc:realloc
  • assert_usage:assert
  • insecure_temp/obsolete_api:原有清单(mktemp/gethostbyname/inet_addr …)

判定:除 assert_usage 外全部 confirmed(名字匹配即缺陷);assert 是否用于
校验入参需意图判断,交 AI 研判(suspected)。exit 族仅在
main/wmain/WinMain/wWinMain 之外报;inet_addr(常量) 仍豁免。

unsafe_memory/unsafe_string/unsafe_format/unsafe_scan 与
buffer-overflow/injection/format-string 刻意重叠:后者回答「这一次调用会不会
溢出/注入」,前者回答策略维度「是否使用了非 _s API」。

配套:registry.go 增加 CategoryConfidence{assert_usage: suspected};
SKILL.md/vuln_type_skill.go/docs/config.md/usage.go 同步;tc109 与 benchmark
p11_dangerous_function.c(DGF-01..15)扩展;AI_JUDGMENT_BOUNDARY.md 登记
assert_usage 例外。

Annex K _s 合约表修正:snprintf_s/vsnprintf_s CountArgIdx

SecureFunctions 中 snprintf_s 和 vsnprintf_s 的 CountArgIdx 误设为 2,
指向 format 字符串参数而非 count 参数。C11 Annex K 的签名为
snprintf_s(s, n, format, ...) / vsnprintf_s(s, n, format, ap)——n 既是容量
也是最大写入数,无独立 count 参数。修正为 CountArgIdx: -1。原值因
evaluateSizeArg 对字符串字面量返回 0 而未造成误报,但合约定义错误已修正。
tc68 增加 secure_lying_snprintf / secure_correct_snprintf 回归用例。

IDM 仓库误报治理:分级修正(第二轮)

对 docs/实战IDM仓库误报问题改进建议.md 第一轮未闭环的项逐条复现后判定:「机器自动
确认」的误报必须修
(AI 没有机会过滤),启发式命中不得进确定性层,漏报优先于
误报
。本轮落地 4 项:

  • dangerous-function / inet_addr(CWE-676):规则表自己写着它的风险是「运行期可控
    字符串 + 未检查 INADDR_NONE」的条件风险,却按「调用即缺陷」自动确认,与规则自身
    的理由矛盾。inet_addr 改用类别 obsolete_api_conditional(→ suspected),交 AI 按
    实参来源与返回值检查研判;gethostbyname/gethostbyaddr/mktemp 等无条件风险项
    仍是 confirmed,inet_addr(<常量>) 仍豁免。SKILL.md 补了对应研判规则(内部配置
    字段/已解析协议值/已查 INADDR_NONE → dismiss);AI_JUDGMENT_BOUNDARY.md 同步登记。
  • injection:纯透传执行器不再自动确认。idm_exec_sql(sql) { return sqlite3_exec(g_db, sql, ...); } 体内没有任何 SQL 构造,字符串完全来自调用方,「调用方拼的 SQL 是否可
    注入」在本地无法判定;这类候选改为 suspected 交 AI(原先的 drop 分支只在「看不到调用
    方污染」时生效,一旦 PARAM_BINDING 证明有污染就升级 confirmed,正是误报来源)。函数内
    sprintf 构造后 exec 的真实注入仍为 confirmed(回归用例覆盖两侧)。
  • unchecked-return:名字启发式命中不再自动确认。新增事件属性
    contract_known:被调方是内置 API 表 / 已声明 allocator / 函数体已检视的透传
    wrapper 时为 true;仅凭名字像分配器(alloc 子串 / _dup / _new)+ 返回指针的匹配
    为 false,ReturnCheckFilter 只提升 true 的候选。这与 hardcoded-secret 的
    value_proven(值本身确定 vs 仅名字命中)同构。修掉文档 mode A 的 3 个误报
    (idm_asset_record_dup 往调用方缓冲区拷贝、不会失败),而 malloc(n) 未判空这类
    契约已知的漏判仍为 confirmed。
  • use-after-free 漏报修复(非文档项,本轮复现发现):free(p); p->n = 1; 这类
    对被释放对象整体做字段写此前被漏报——isFieldWrite 把所有字段写都排除在「使用」
    之外,但该排除只对「被释放的是那个字段」成立。现在字段写会记一条基对象使用,
    匹配阶段再按 fs.field 丢弃:free(s->msg); s->msg = NULL;(字段释放后清指针)与
    free(s->msg); return s->mode; 仍不报,free(p); p->n = 1; 现在报(写已释放内存 =
    堆破坏)。实测 p->n=1/p->next=0/*p=/p[0]=/读/传参 7 种形态全部命中。

另外两项判定为无需修改(复现为「不产生候选」或已修):

  • double-free / use-after-free 的文档形态不成立:http_destroy_active_conn_list(pool)
    / http_destroy_idle_conn_list(pool) / http_free(pool) 与 free(p); free(p); 的复现
    用例中,前者 seed_count=0(_destroy 名字启发式已不再等同「释放该对象」),后者只对
    真 free 命中,与文档描述的误报不符——已在更早版本修掉。
  • null-deref 的「被调函数入口守卫」已被识别:复现中 callee_guarded(p){ if(p==NULL) return -1; return p->v; } + caller 传未证非空变量 → 0 候选,而未加守卫的对照函数
    → 1 个 suspected。文档 risk #2 的 resize 语义在 v0.9.4 已修。

同时把字符串常量表收敛为一份:buildGlobalStringMacros 扩展为支持宏拼接
(#define LOG DIR "/log")并被 path-traversal 复用(原先 path-traversal 自带一份
per-file 宏表,属重复),path-traversal 只额外收集本文件的 static const char 对象。

回归用例:zz_quality_round_test.go(UAF 字段写/字段释放对照、contract_known 两侧、
inet_addr 三态:常量豁免 / 参数与字段 suspected / 其他 obsolete_api 仍 confirmed)、
TestReturnCheckFilter_HeuristicAllocatorStaysSuspected、
TestTaintSourceFilter_PassthroughSQLNotAutoConfirmed。未发布版本,VERSION 不变。

IDM 仓库误报治理:常量路径与安全函数族(第一轮)

针对 docs/实战IDM仓库误报问题改进建议.md 中 30 个误报的根因,检测器/收敛层收敛:

  • path-traversal(CWE-22):
    • 编译期常量路径识别(零配置):buildStringConstants 收集文件内的字符串常量
      ——展开后只由字符串字面量/已知常量构成的宏(#define DIR "/var"、
      #define DB DIR "/db")、以及 static const char 且未被重新赋值的对象
      (数组形式 / 指针形式;指针形式要求 static 且在文件内无赋值)。sink 的路径实参
      直接是这些名字时抑制候选;实参是仅由常量拼装的本地缓冲区时同样抑制
      (snprintf(path, sizeof path, "%s", DB_PATH)、strcpy(path, DB_PATH),按
      dest-arg + 全常量实参列表判定)。反例全部保持上报:#define P DIR user_name
      这类混入未知标识符的宏、非 const 的 static char[]、被重新赋值的
      static const char *、非 static 的 const char *,以及
      snprintf(path, sizeof path, "%s/%s", BASE, name) 这类含运行期分量的构造
      (常量 BASE 本身不构成安全证明:name 里的 ../ 仍能逃出 BASE,故交 AI 研判)。
    • AI 研判边界收紧:TaintSourceFilter 不再因「外部可达的参数流到 sink」把
      path-traversal 升级 confirmed,改为保持 suspected 交 AI。「外部数据到达路径
      参数」只证明调用方能影响字符串,不证明能出现目录逃逸分量——按
      AI_JUDGMENT_BOUNDARY.md 的定义,confirmed 要求缺陷在每条路径上成立。
      injection/format-string 的升级保持不变(未参数化的 sink 被外部数据触达即为缺陷)。
      AI_JUDGMENT_BOUNDARY.md 已把 path-traversal 从 confirmed 表移入「故意留在
      suspected」表。这正是文档 P0 建议「auto-confirm 分级」的落地,一次性消除该类
      9/9 的自动确认误报。
    • 未采用 [guards.path_traversal] 配置:名称白名单只能表达「函数 F 校验值 V」,
      覆盖不到「路径由谁拼出来」这一数据流事实(IDM 9 个误报里仅 2~3 个属于该形态),
      却引入一个需要用户维护、配错就静默漏报的配置面,因此改为零配置的常量识别 +
      交 AI 研判。
  • integer-overflow(CWE-190):无符号减法流入 Annex K / Windows _s 安全函数族
    (sprintf_s/memcpy_s/strcpy_s 等,apikb.SecureFunctions)的参数时不再报
    unsigned_sub_underflow——这些函数在运行期校验容量/长度并以错误码返回,不会越界。
    抑制范围仅限 _s 族:apikb.SafeFunctions 还包含 strlcpy/strlcat、
    sqlite3_bind_blob、openat、exec* 等「长度参数被无校验消费」的 API,其下溢仍是
    真实缺陷,必须继续上报。
  • unchecked-return(CWE-252):
    • 函数指针遮蔽系统调用名:函数内以 read/write 等名字声明了先于调用点的局部
      对象(如 int (*read)(void *, size_t, void *);)时,该调用不是 POSIX 系统调用,
      不再上报。
    • 间接防护改为基于被调函数入口守卫:v 作为实参传给一个在入口处拒绝该参数并
      返回失败码的被调函数(if (p == NULL) return -1;),且调用结果被检查(内联判断,
      或存入随后被检查的变量),则 v 的 unchecked-return 事件是误报。判定要求:
      守卫分支必须返回失败哨兵(return 0; / 裸 return; 不算)、极性正确
      (if (p != NULL) ... else return -1; 算,if (p != NULL) return 0; 不算)。
      仅凭「变量出现在条件里的某个调用实参中」不再抑制——if (strlen(p) > 0) 是对
      可能为 NULL 的指针解引用,仍然上报。
  • 配置文档同步:secguard config --help/--example 与 docs/config.md 补齐 5 个
    此前只存在于代码里的配置段(allocators/deallocators/ownership_transfer_calls/
    nullability/integer_overflow)。docs/config.md 是本仓库约定与
    SKILL.md/vuln_type_skill.go/usage.go 一起同步的配置面文档,此前一直缺失。

回归用例:path_traversal_constant_test.go(7 个常量形态抑制 + 6 个可影响形态上报)、
tc_unchecked_return_indirect_guard.c(含极性/成功返回/解引用三类反例)、
integer_overflow_safe_sink_test.go(sprintf_s 抑制 + malloc/strlcpy/
sqlite3_bind_blob 仍报)、filter_taint_source_test.go
TestTaintSourceFilter_PathTraversalNotAutoConfirmed(path-traversal 保持 suspected、
injection 仍为 confirmed)。未发布版本,VERSION 不变。

v0.9.4

Choose a tag to compare

@github-actions github-actions released this 12 Oct 12:30

[0.9.4] - 2026-10-12

null-deref void* 契约收敛漏报修正(ND-3 / RISK-2,CWE-476)

v0.9.4 首次发布的 ND-3 修复(commit d8410b4)引入 contractDerived dismiss 规则收敛 void* 契约解引用误报,但端到端验证发现一条漏报路径:当 accessor 调用(t = (T*)get_raw(p); t->f)的 get_raw 是可能返回 NULL 的普通函数时,contractDerived 仍追溯赋值 RHS 到 void* 契约参数并 dismiss,导致真缺陷被抑制。

根因:contractDerived 对 call_expression RHS 统一假设「函数名不在 allocator / known-nullable 列表 → 返回值非空 → 追溯参数到契约」,无法区分「框架保证非空的 accessor」与「可能返回 NULL 的普通函数」。

修正:contractDerived / derivedName / derivedExpr 新增 models *nullModel 参数;遇到 accessor call 时调用新增的 nullModel.hasExternalCallSource 检查赋值目标变量是否存在 external_call null source。若存在(分析器认为 accessor 可能返回 NULL)则不追溯、不 dismiss,surface as suspected 交 AI 研判;直接 cast 无 external_call source 则仍追溯,正确收敛。

端到端验证(本地 CLI + C 夹具):FP-1 direct cast / FP-3 type-agnostic alias 仍正确抑制;FP-2 accessor cast 改为 surface as suspected(交 AI);TP-1/2/3 仍正确检出;RISK-2(accessor 返回 NULL)从漏报修正为 candidate (suspected)。benchmark ND-10 从 no_finding 改为 finding,expected_final_certified 77 / expected_no_finding 65。

integer-overflow 子代理超时根治(CWE-190,紧急修复)

v0.9.3 生产冒烟(nlog src/ 全量扫描,1012 文件 / 7092 函数)中 integer-overflow 两个子代理均因 LLM 单次推理超时失败(total_tokens: 0 + Request timed out),117 条候选全部未落库判定。根因是 unsigned_sub_underflow 候选(98/117)只有 cat@ 类别标签、无任何数据流事实,子代理被迫逐候选打开 ±8 行源码找 guard 与操作数来源,单 turn 推理量过大导致 API 超时。本次从「收敛阶段用语义图算清事实」彻底闭环:

  • interprocedural bounded-return summary(evidence/bounded_return.go):给每个被索引函数计算「返回值 ≤ 某 capacity 参数」的截断包装器契约(snprintf_truncated_s/sprintf_s/vsprintf_s 及转发到它们的一跳/多跳自定义包装器),带保守不动点 + 环保护。累加器模式 length += add_*(buf, size - length) 因此在检测阶段被证明有界并自动 dismissed,不再进入 AI 研判——这是 98 条 buf_len - length 类候选超时的直接原因。
  • 配置驱动截断包装器白名单(apikb.TruncatingWrapperCapArg + secguard.toml [integer_overflow.truncating_wrappers]):对定义在扫描树外的第三方截断包装器(如 nlog_syslog_add_*),用户可声明 name = <capacity 实参下标> 一键闭环,与 memory-leak 的 [ownership_transfer_calls] 同款机制。
  • 累加器容量实参校验(boundedAccumulators):只认「total - length 剩余空间 / 首次 total 全量」的严格形状,两个全量写(length += f(buf, 100) ×2)不会被误判有界,杜绝把真溢出当误报漏掉。
  • operand provenance Hint:unsigned_sub_underflow 的 Hint 从裸 cat@ 升级为 lhs@<kind> rhs@<kind> rhs-influenced sink@<kind> guard@none,AI 从 _index.md 一行即可判断被减数来源(rhs@param/rhs-influenced = 外部可控,真实风险;rhs@local 无 rhs-influenced = 内部有界累加器)。
  • 入口 guard 自动消除(planner/filter_unsigned_sub.go):if (consumed > total) return; 早退 guard 证明减法安全,收敛阶段直接 dismissed。
  • Fix Suggestion 按 category 细分:unsigned_sub_underflow / size_calc_overflow / integer_overflow 各自给出减法 guard、乘法检查、加法检查模板,不再统一塞 malloc 场景。
  • 回退上一版「函数级 200 行 Code Context」为 ±8 窗口(保留 ## Location 一行函数签名):117 行 _index.md ≈ 8K token 本身不构成压力,真正会爆的是 106 个证据文件 × 200 行 ≈ 200K+ token,改为密集 Hint 承担判定,避免源码上下文炸弹。

测试:TestIntegerOverflow_MixedAccumulatorBoundedReturn、TestTruncatingWrapperCapArg_*、TestLoad_IntegerOverflowTruncatingWrappers、TestUnsignedSubGuard_*、TestBuildHint 等全绿。

重新发布(2026-10-12,厂商复测反馈):累加器真实代码形状补全

厂商用 v0.9.4 复测后反馈:unsigned_sub_underflow 的累加器误报(buf_size - len)仍有 ~80 条未消减。回源定位到 boundedAccumulators 只认「uint32_t len = 0;(声明初始化)+ len += f(...)」这一种形状,漏掉了真实代码里两种同样常见、同样有界的形状:

  • len = 0; 赋值语句初始化:此前被当成「非累加写」而将 len 整体排除(disqualify),实际是安全复位,仍满足 len <= total 不变式。
  • len = f(...) 纯赋值首写(len = snprintf_truncated_s(buf, size, ...) 后接 len += f(...)):此前同样被排除,实际是「覆写为 callee 的有界返回值」,恒 len <= total。

现 boundedAccumulators 区分「覆写写(= f(...),恒有界)」与「累加写(+= f(...) / = len + f(...),两个全量累加才可能超 total)」,并把 len = 0 识别为安全复位(重置增长、保持 total)。回归夹具 tc129 增加 fmt_buf2(赋值初始化)/fmt_buf3(纯赋值首写)两个形状,TestIntegerOverflow_MixedAccumulatorBoundedReturn 断言两者均不再发射候选。

重新发布(2026-10-13,第二轮生产审视闭环):跨函数误报消除 + 编排稳定性

对「0.9.4 版本扫描审视改进建议第二轮」逐条回源核实后的闭环修复:三处跨函数误报(其中一处为 high 级 memory-leak 误报)与两处编排稳定性问题,其余条目核实为已修复或误诊、未改代码。

  • injection per-type 统计漏计(真缺陷):status --per-type 的 written_count 只按主 CWE(CWE-78)计数,规则 id 为 CWE-117 的日志注入 finding 落库后仍显示 0,导致编排器 F5 误判子代理失败并触发不必要重试。现 ListPerTypeStatus 按类型全量 CWE(canonical + legacy + category,新增 planner.CWEsForType)求和并统一大小写归一化。测试 TestListPerTypeStatus_MultiCWE。
  • divide-by-zero 跨函数三元返回值漏分析(真缺陷,medium 误报):returnSummaryResolver.exprInterval 缺 conditional_expression 分支,nlog_get_clear_cycle_times() 返回 cond ? (2*SECONDS_FOR_8H) : (2*SECONDS_FOR_1MIN) 被判「未知」而不过滤。现两臂皆非零才判非零(任一臂可能为零则保持保守)。测试 TestDivideByZeroReview_TernaryReturnCall。
  • dangerous-function 字符串宏参数未按常量过滤(真缺陷):inet_addr(NLOG_HOST_IP) 中 NLOG_HOST_IP 是字符串型宏,hasConstantArg 只认直接字符串字面量与整数宏,导致本应安全(INADDR_NONE 混淆不存在)的调用被标 high。现新增 buildGlobalStringMacros 收集字符串型宏。测试 TestDangerousFunction_StringMacroConstant。
  • memory-leak 经 helper 的所有权转移漏识别(真缺陷,high 误报):nlog_db_create_new_ctrl_item 分配后经 nlog_set_mon_disk_head(p)(内部 g_head = p)注册到全局链表并返回缓存指针,调用方不持有所有权;但借出检测只认直接写全局,且函数名 _new 子串被零配置分配器启发式独立命中,被误报为 high 泄漏。现新增精确的跨函数「参数存入全局」摘要(globalStoreFuncs/storesParamToGlobal)+ borrow 集合(borrowFuncs)抑制命名启发式(isBorrowCall)。夹具 tc134 + 测试 TestMemoryLeak_BorrowViaHelperCall。
  • 编排:批量容量估算改为二维(真缺陷):batch_type_count × 12 < 54 只按类型数估算,4 类型 × 12 = 48 通过校验但 86 候选 + 4 次 skill 加载实际溢出子代理,遗留 race-condition 未处理。现改为 type_count × PER_TYPE_FIXED_TURNS(6) + candidate_count × PER_CANDIDATE_TURNS(0.8) ≥ 54 即拆分(extension/shared/command-instructions.md)。
  • 编排:遗留类型重派单而非放弃/顺序自分类(真缺陷):written_count == 0(从未启动)的类型此前被 F1 标记 missing-type 或顺序自分类(墙钟 +3~5 分钟)。现 F1 优先派全新单类型子代理重试一次,禁止顺序自分类,仅二次仍空或子代理不可用才标 missing-type;并加 terminal_state == "done" 守卫避免把「全 dismissed」误当「未启动」。

v0.9.3

Choose a tag to compare

@github-actions github-actions released this 12 Oct 02:50

[0.9.3] - 2026-10-11

重新发布(2026-10-12):生产冒烟复核修复(12 项回源)

对 v0.9.3 生产冒烟报告(xlog 全量扫描,约 1012 文件 / 7092 函数 / 178358 行)的 12 项发现逐项回源复核:8 项含真实缺陷(其中"out-of-bounds 漏报"的漏洞判断为误诊、日志误报为真缺陷)已修复,4 项判定为误诊或能力空缺。

  • uninit_variable 检测器 panic(P0,真缺陷):outputParamInitializedVars(evidence/uninit_variable.go)在调用点无函数摘要(外部库函数)时仍读取 summary.ParamWrites。命中"已知 out-param 函数 + 非输出参数取地址"形状(localtime_r(&t, &tm):&t 不在 outputParamInitializerArgs 的输出下标集合内)即空指针崩溃,整个 uninit 检测器中断,候选被静默截断。根因定位方式:v0.9.3 日志只留一句 nil pointer dereference、无行号,本地用一个 C 代码库当复现靶子跑出同样 panic,补打堆栈后定位。现摘要为零值时以零值 FuncSummary 兜底;确定性回归夹具 tc131_uninit_addr_outparam.c + TestUninit_AddrOutParamNilSummaryPanic(修复前该测试必 panic,已验证)。detector panicked 日志同时补打完整堆栈(debug.Stack()),避免同类崩溃再次不可定位。
  • category filter 告警误报(真缺陷,日志层):category filter dropped all seed events 打印的是 spec.Categories(本类型允许清单)而非真实被丢弃的 category,且对"种子事件被兄弟类型认领"这一正常路由也告警。真实会话中 out-of-bounds 丢弃写向 BUFFER_ACCESS 事件(归 buffer-overflow)被误读为"6 个 OOB 读取被类别过滤器一刀切"的漏报。现仅当被丢弃 category 无任何注册类型认领(真实漂移)才 WARN,并输出真实 orphan_categories 计数;正常跨类型路由降为 Debug,明细写入 PipelineSummary.SeedRoutedByCategory 供审计。测试 TestUnroutedCategories。
  • format-string(真缺陷 ×2):① fprintf_s/vfprintf_s 的格式串实参下标错为 0((FILE*, fmt, ...) 应为 1),导致字面量格式串被当成非常量 sink 误报(实测 fprintf_s(fp, "%d\n", 1) 入选候选)。② Annex K _s printf 变体位于 SafeFunctions,被 safe_function_exclude 无条件丢弃——与 format-string 检测器刻意发射 _s 非常量格式串(CWE-134)自相矛盾,生产会话 36 个种子 100% 过滤为零。现格式串类别仅允许 curated 项目安全包装函数豁免,边界安全不再豁免格式串。回归 tc132_format_string_annexk.c、TestFormatString_AnnexKFormatIndex、TestSafeFunctionFilter_FormatStringUnderscoreSNotDropped。
  • --write-json 定位字段静默丢失(真缺陷):缺省或拼错(如 filepath/line_number)的定位键解码为零值,静默落库为 file_path=''/line=0 且 exit 0。现拒绝空定位并指明期望键名,同时接受 DB 列名别名 file_path/line_number/function_name(findingInput.normalizeAliases)。
  • --review-json 契约(真缺陷,轻):缺失/拼错必填字段时报 entry N (finding M): missing required field "review_status" ...(含期望值与"拼错即为空"的提示),不再只报 invalid review_status "";report --help 补齐 --review-json / --list-auto-confirmed / --type / --limit 说明与必填字段清单。
  • report --audit 复核兜底(真缺陷):audit 前不校验 status=auto-confirmed 且 review_status 为空 的 finding;0 候选类型不派子代理时,该机器确认项会被静默带过。现审计信封新增 unreviewed_auto_confirmed 与 warning(auditNoticeFields,不改变退出码契约)。
  • scan.log 缺终态(真缺陷,轻):日志止于 plan 阶段且随即关闭,扫描结束边界与退出状态均不可审计。现以命名返回值在退出时补记 scan finished + exit_code(含 --fail-on 门禁的 2)。
  • report.md 计数口径(真缺陷,轻):per-type 表新增 Auto-confirmed 列区分机器确认与 AI 确认;findings/ 文案更正为 "confirmed(pipeline auto-confirmed + AI)"。

复核后判定为误诊 / 已实现(未改动代码)

  • memory-leak 收敛链漏掉真实泄漏(xlog_db_delete_all_table:234):误诊。candidates/ 只承载首轮待 AI 研判的 suspected 线索;status=auto-confirmed 的确定性发现按设计直接落 findings 表并进入二次复核。已按报告给出的形状构造最小复现:泄漏被检测器正确发射并 auto-confirm(xlog_db_create_new_ctrl_item 分配 + NULL 检查 + 后续早退),无需修改 leak_proof/ownership_transfer。
  • out-of-bounds 6 个种子被类别过滤器丢弃 = 确定性漏报:误诊,见上。读写向 category 已分别路由到 out-of-bounds / buffer-overflow,实测两类均正确进入候选/auto-confirm。
  • .sgre/.tmp/ 残留未清理:误诊。secguard scan 启动即 RemoveAll + MkdirAll;残留的 <type>-partN.json 是 scan 结束后 AI 阶段写入的中间产物,由下一次 scan 清理,与文档一致。
  • TaskCreate 工具参数契约:误诊。项目源码与 extension/shared/ 指令均无 TaskCreate,文档要求用宿主 todo 列表跟踪类型进度。
  • 缺官方编排命令(secguard orchestrate):确认是能力空缺而非缺陷;批次数/范围切分仍由编排器计算,本次未新增命令。

ds4.1 检视修复(40 项回源复核)

对 v0.9.2→HEAD 的 12 个提交做全量回源复核(docs/ds4.1检视sgre-20261019.md),逐项验证并修复真实缺陷。关键项:

  • uninit 自动确认精确化(修订本节的 macro-context 方案):上一版以"候选前后 ±8 行出现大写宏"降级 confirmed→suspected,既误伤无关宏(a + 1 + MYMACRO(0) 的 a 被拖下水)又漏掉取字段地址(HTONBUF(&ipv6.hpfS6Addr32[0]))。根因是 hasOutputParamWrite 只认 &裸标识符。现删除 ±8 窗口,改为精确识别 &x/&x.f/&x[i] 与 x.f/x[i] 退化传参(PP-06)。
  • global_invariants:nonZeroVars 不再跨函数按裸名共享(PP-01 真除零漏报);静态零初值/BSS 计入"可能为零"(PP-05);复合赋值置 allNonZero=false(PP-04)。
  • divide-by-zero 常量折叠:*/<< 折叠带 32 位宽度溢出/移位量判断,(1u<<16)*(1u<<16)、1u<<32 不再误判非零(EL-05/PP-03);sizeof_x 前缀误判修复(PP-11)。
  • memory-leak:容器逃逸仅当容器自身逃逸才成立(EL-01)、字段逃逸补 base==varName(EL-02)、getter-borrow 禁用命名启发式(EL-03)、typedef 标量识别(EL-06)、同名遮蔽 fail-open(EL-07)。
  • agent-native re-review 链路:audit 计数以 FinalStatus 为准(RV-01)、三条 re-review 指令对齐(RV-02)、--limit 分批(RV-03)、--review-json 校验 auto-confirmed(RV-04)。
  • benchmark:Phase 15/16 行号修正、汇总计数同步、补 CLI 端到端 re-review 契约测试。

uninit 自动确认与 out-param 误报(CWE-457)

基于 nlog 生产扫描 candidates/uninit/_index.md(78 候选,5 个 certain-uninit macro-context 被 auto-confirm)诊断:

  • macro-context 不再 auto-confirm:HTONBUF/NTOHBUF/memcpy_s 等 ALL_CAPS 宏展开的写入对流模型不可见,此前"每路径未初始化"被机器确认为 confirmed。现把 macroContextDetector 接入 DefiniteInitFilter,macro-context 候选一律降级为 suspected 交 AI 研判(对齐 null-deref 的 macro_uncertain)。消除 5 个确定性 auto-confirm 误报。
  • POSIX 时间转换 out-param:localtime_r/gmtime_r/localtime_s/gmtime_s/strptime 加入 outputParamInitializers,localtime_r(&t, &tm) 后读 tm.tm_year 等字段不再被判为未初始化(此前与 stat/gettimeofday 同形却漏配)。消除 ~20 个 local_tm/timeinfo/systime 类误报。
  • 回归夹具:tc130_uninit_localtime_outparam.c;测试 TestUninit_LocaltimeOutParam、TestUninit_MacroContextNotConfirmed。

memory-leak 标量状态码与 getter 借用误报(CWE-401)

生产 result.sarif 的 memory-leak 子集 34 条 auto-confirmed 中 21 条是确定性误报,两类根因:

  • 标量状态码当指针:bool ret = nlog_is_time_to_create_new(...)、uint32_t ret = nlog_disk_monitor_remalloc()、uint32_t resev2 = HpeDynmemAlloc(id, &pNewNode)(真分配是 &pNewNode out-param)——_new/remalloc/*_alloc 名字启发式命中了返回状态码的函数,检测器未做类型校验。修复:findScalarVarsFrom 仅当基础类型是内建标量(primitive_type/sized_type_specifier)才跳过整变量目标;typedef 基础类型(MsgPdu p = char **p)fail-open。
  • getter 借用当分配:nlog_db_get_ctrl_item / nlog_get_ai_detect_stat_list 等 get-or-create-and-cache 访问器返回注册表/缓存的引用。修复:allocatorReturnFuncs 不再把「返回前已逃逸(存入全局/静态/字段或交给容器)的指针」标为分配器包装器。
  • 回归夹具:tc131_memory_leak_scalar_status.c、tc132_memory_leak_get_or_create_borrow.c。

AI 复核 auto-confirmed 结果改为 agent 原生(移除外部 AI provider)

0.9.2 的 secguard review-autoconfirmed 走外部 HTTP AI([ai_review] 配置 + aireview),与「AI 就是 agent」的架构冲突且未接入编排,已删除(命令 + review/aireview 包 + [ai_review] 配置)。二次复核现折回 security-auditor subagent 自身的 per-type 分类循环,复用已加载的 skill:

  • secguard report --list-auto-confirmed [--type] 列出机器 auto-confirmed finding(含 id/rule_id/snippet);secguard report --review-json 回写 dismissed 覆盖(dismissed 经 EffectiveStatus 从 result.sarif 抑制)。
  • Bash 主机用两条命令;OpenCode-NGA(无 Bash)用新增的 secguard_review MCP 工具(list_auto_confirmed/reviews),已在 opencode + opencode-nga 双端注册并通过一致性检查。
  • VulnTypesToRuleIDs 迁入 planner。回归测试 TestAutoConfirmedReReviewLoop。

v0.9.2

Choose a tag to compare

@github-actions github-actions released this 09 Oct 13:32

[0.9.2] - 2026-10-10

空运行检测器跳过(性能 P0-5)

  • sizeof_misuse / unchecked_return 空运行跳过:启动前轻量预扫是否有触发形态(sizeof(标识符) 处于 size 上下文 / 存在 unchecked-return API 或分配器调用),无则跳过昂贵的跨文件 typedef 表与 passthrough 修复点。预扫出错时 fail-open 照常运行完整检测,不引入漏报。

integer-overflow 截断累加器误报(CWE-190)

  • unsigned_sub_underflow 截断累加器抑制(P1-2):size - count 中 count 若只由截断式格式化包装器累加(count += snprintf_truncated_s(...) / count = count + sprintf_s(...)),则按契约 count 不超过 size,该减法不会下溢,不再发 unsigned_sub_underflow。仅匹配 *truncated*(含 printf/sprintf)与精确 sprintf_s/vsprintf_s,snprintf/snprintf_s(返回值可超界)不匹配,保持保守。回归夹具:tc128_int_overflow_snprintf_accumulator.c。

race-condition 锁封装函数识别(CWE-362)

  • 纯 lock/unlock 封装函数对(P1-4):xxx_lock()(函数体仅 pthread_mutex_lock(&g_mutex))与 xxx_unlock() 视为与直接原语等价,位于两者之间的共享状态写不再被误报为 shared_data_race。仅当函数体只有 lock(或只有 unlock)调用、无其它调用时才识别为纯封装,关键段 helper 不算。回归夹具:tc129_race_lock_wrapper.c。

resource-leak 自动确认误报修复(CWE-404)

生产环境反馈 0.9.2 的 resource-leak 自动确认(auto-confirmed)包含大量确定性误报,是"几乎无法使用"的主要原因。本轮针对两大非泄漏模式去掉自动确认(仍保留为 suspected 交由 AI 研判,不丢召回):

  • 锁配对(P0-1):xxx_lock() 内 pthread_mutex_lock(&g_mutex)、同文件 xxx_unlock() 内 pthread_mutex_unlock(&g_mutex) 的标准封装函数对,以及 pthread_rwlock_rdlock/wrlock 配对,不再标记 definite(跨函数解锁在本函数内不可证,不能 auto-confirm)。
  • 全局/静态变量持有(P0-2):g_handler = iconv_open(...)、g_handle = dlopen(...)、g_fd = epoll_create(...) 及函数内 static 局部缓存等进程生命周期资源持有,不再标记 definite。findLocalVarsFrom 同步将 static 局部视为非局部(逃逸),消除 memory-leak / resource-leak 两侧的静态缓存误报。

divide-by-zero 跨文件常量除数(CWE-369)

  • 跨文件 #define 常量解析:x / NLOG_SECOND_PER_MINUTE 这类除数宏定义在 .h 头文件时,此前每文件常量表看不到、被误报为"可能为零"。现改用 buildGlobalConstants 合并所有已索引文件(含头文件)的编译期常量(冲突值保守地降为"不确定"),头文件里的非零常量除数像字面量一样被丢弃。回归夹具:tc127_divide_by_zero_crossconst/。

生产内存分配模式识别(对齐 docs/req_内存分配释放典型性优化.md)

  • 第三方 SDK 分配/释放名进入精确集合:VOS_Malloc_F / VOS_Free_F、HpeMemAlloc / HpeMemFree、VOS_Mem_Allock_F / VOS_Mem_ReAllock_F / VOS_MemFree_F 加入 BuiltinAllocators / BuiltinDeallocators,使 null-deref 确认层与 double-free / UAF / unchecked-return 检测器像对待 malloc/free 一样精确对待这些跨仓库 SDK 宏,而非依赖 fail-open 命名启发式。
  • "封装成函数"包装器识别(新增能力):识别"函数体返回分配器结果"的包装器(p = malloc(n); if(!p) return NULL; memset_s(...); return p;),即使包装器名不含 alloc/malloc 子串,调用方的未释放结果也会被检出为泄漏(memory-leak)。这是 40+ 个仓库各自命名、但都"判空 + memset_s 初始化后返回指针"的共性形态。
  • 回归夹具:tc123_resleak_lock_pair.c、tc124_resleak_global_hold.c、tc125_prod_alloc_macros.c、tc126_passthrough_alloc_wrapper.c。

AI 复核 auto-confirmed 结果(新能力)

  • 新增 secguard review-autoconfirmed 命令:对 sgre 判定为 auto-confirmed 的 finding 做一次独立 AI 复核,降低 pipeline 自动确认的误报、恢复 AI 研判价值。支持 --scan-id / --type / --limit / --dry-run / --status / --output-json。
  • 复核结论复用现有 findings.review_status 第二遍字段:false_positive 映射为 dismissed(真正过滤)、ai_confirmed 映射 confirmed、needs_more_evidence 保守保留原 confirmed,并在 result.sarif 以 review_status 属性暴露供观测。
  • review_error(复核调用失败)只写日志、不落库、不进 result.sarif:finding 保持未复核并在下次运行时自然重试,避免"复核失败却仍标 confirmed"误导用户。
  • 完整复核审计(supporting/contradicting/missing 证据数组、模型/provider、prompt/schema 版本、源码 revision、confidence、耗时)合并进 properties.review,与 auto_confirm_evidence 共存,不污染原始机器结论。
  • 扫描时记录源码 revision(scan_runs.source_revision),复核时校验 revision 一致性;payload 修复绝对路径解析与路径穿越防护。
  • 新增 [ai_review] 配置:provider / model / endpoint / api_key_env / timeout_ms / max_retries / budget_usd / cost_per_call_usd / prompt_version / schema_version。

v0.9.1

Choose a tag to compare

@github-actions github-actions released this 09 Oct 03:07

[0.9.1] - 2026-10-09

memory-leak 准确率收敛(CWE-401)

  • definite 通道扩展到不可达释放:hasLostResource 证明泄漏路径后,即使存在 free/escape/transfer 行,只要这些行在 CFG 上从分配点不可达(无条件 return / goto 之后的死代码),仍判定为 definite leak 并 auto-confirm;条件分支内的释放保持 suspected(ML-01/18 扩展)。
  • compound assignment 不再当作分配:ret += CLI_NewDefineCmdElement(...) 是算术运算而非存储分配,此前 _new 触发零配置分配器启发式导致 confirmed 误报。
  • output-param escape/free 识别:*param 输出参数形态的分配逃逸(*root = malloc(...))与释放(*p 的 free)被正确解包,消除跨函数所有权的漏报/误报。
  • config 驱动 ownership_transfer_calls:新增 [ownership_transfer_calls] names = [...] 配置,项目自定义容器插入类函数(list_push_back/dict_set/buffer_append 等)在 CLI 启动时注册进 apikb,指针传入即视为所有权逃逸而非泄漏,替代原有硬编码。

SKILL 对齐

  • extension/shared/skills/memory-leak/SKILL.md 从通用方法论重写为对齐 detector 实际行为:真实事件(MEMORY_ALLOC / MEMORY_RELEASE)、definite 通道、confirmed/suspected/false-positive/dismissed 判定表,并补充别名释放、cast 释放、escape 变体、隐式分配器、self-realloc、goto cleanup、RAII 路径级等 false-positive 场景。

v0.9.0

Choose a tag to compare

@github-actions github-actions released this 07 Oct 11:41

[0.9.0] - 2026-10-08

出口前内存与空指针收敛

  • uninit:字段写目标和取地址表达式不再被当成未初始化读取,修复 que_list->field = ... 与 &struct.field 两类 pipeline-proved 误报。
  • null-deref:三态 return-nullability 模型补齐直接调用结果路径(f()->x / *f() / f()[i]),never-null 直接丢弃、known-nullable 自动确认;修复 function_summary 空文本列导致 planner 退回 fail-open 的精度降级。
  • null-deref triage:caller_null 中仅“调用方实参未证明非空”的候选在 AI 前直接收敛;字面量 NULL 调用和带具体来源行的候选继续保留。
  • null-deref guard/macro triage:短路判空条件内的解引用(p == NULL || p->field)前置收敛;无具体 NULL 来源行的 macro-context 候选不再进入 AI。
  • allocator/deallocator 语义:扩展零配置识别到 _new / _create / _dup / strdup 与 _destroy / _delete / _cleanup / _recycle;统一 calloc/zmalloc/zalloc zero-init 判定。
  • memory-leak:deallocator 多参数只消费释放实参;put/push/enqueue/register/insert/append/store/send/add 等调用型所有权逃逸不再误报为泄漏。

切换到自维护 tree-sitter-c fork + 原生解析 GNU C 扩展

  • go.mod 将 github.com/tree-sitter/tree-sitter-c 替换为 github.com/DannyAn/tree-sitter-c v0.24.4(fork)。
  • 原生解析 GCC typeof(typeof_specifier 四种拼写),移除先前的 GCC typeof 预处理重写链。
  • 补齐 6 类此前 HasError()=true 的 GNU C 语法(B1-B6):
    • B1 变量声明后置 __attribute__(int g __attribute__((aligned(16))) = 0;)
    • B2 typedef 级 __attribute__(typedef int __attribute__((vector_size(16))) v4si;)
    • B3 enum 级 __attribute__(enum E __attribute__((packed)) { ... };)
    • B4 标签级 __attribute__(L: __attribute__((cold));)
    • B5 GNU case range(case 1 ... 5:)
    • B6 computed goto(void *p = &&L; goto *p;)

sgre 检测层适配(22 处)

  • v0.24.4 把 attribute_specifier 插进 init_declarator 的 declarator 与 value 之间,原先用位置索引 NamedChildren()[1] 取 initializer 的 22 处检测/图/规划点会误取到 attribute,导致带 __attribute__ 的初始化(malloc / 显式 NULL / 别名 / 常量源)被漏检。
  • 新增 parser.Node.AssignParts():对 init_declarator 按字段名(declarator/value)取,对 assignment_expression 保持位置索引。
  • 统一替换 22 处:null-source / memory-leak / resource-leak / double-free / uninit / unchecked-return / buffer-overflow 检测器,graph alias/interproc,planner uninit-flow/null-flow。
  • 新增回归测试:tc121_attribute_init.c + AssignParts 单测,锁定 char *lp __attribute__((aligned(16))) = malloc(10) 等场景不再漏报。