Skip to content

cluster: baseline 欄位驗證抽成受測 lib + 補 model 驗證 (#116, #115) - #126

Open
kiki830621 wants to merge 2 commits into
mainfrom
idd/cluster-baseline-field-validation
Open

cluster: baseline 欄位驗證抽成受測 lib + 補 model 驗證 (#116, #115)#126
kiki830621 wants to merge 2 commits into
mainfrom
idd/cluster-baseline-field-validation

Conversation

@kiki830621

Copy link
Copy Markdown
Member

Refs #116
Refs #115

Summary

Cluster PR — two issues on the same named resource (the gate's baseline field-validation channel), in the order the diagnoses established: extract-and-test first (#116), then add the missing validation (#115). Doing them apart would have written the module twice.

#116 — the gate parsed baseline.json in two independent inline heredocs: the worklist stage (corpus + language) and the model stage (entries[0].model, validated nowhere). Neither was testable without running the whole gate — while the compare stage has been a tested lib since #34. That asymmetry is precisely where #112's language whitelist and #115's model gap live: one heredoc refactor could silently drop a whitelist with no test going red.

scripts/lib/baseline-worklist.py now owns the validation, mirroring baseline-compare.py's shape. Interface --emit worklist|model, and both modes run the complete field pass — validating only the emitted field would recreate the very divergence that let model go unchecked.

#115model gains a whitelist in that same pass. It reaches a path sink (MODEL_DIRcd) and a CLI arg (--models), which is why #112 didn't remove the traversal capability — it just moved the payload one field over.

Verification

Independently re-verified by the orchestrator, not taken on the implementer's report:

Checklist


Generated by /idd-all cluster mode. Do NOT add a GitHub close trailer — IDD discipline requires manual /idd-close after merge.

…del (#116, #115)

The gate parsed baseline.json in TWO independent inline heredocs — the worklist
stage (corpus + language) and the model stage (entries[0].model, validated
nowhere). Neither was testable without running the whole gate, while the compare
stage has been a tested lib since #34. That asymmetry is exactly where #112's
language whitelist and #115's model gap live.

#116: scripts/lib/baseline-worklist.py now owns the validation, mirroring
baseline-compare.py (standalone, stdlib, exercised via Process from Swift).
Interface is --emit worklist|model, and BOTH modes run the complete field pass —
validating only the emitted field would recreate the two-heredoc divergence that
let model go unchecked. Validation completes before any emission, so a bad field
in a later entry leaves stdout empty rather than emitting earlier records.

#115: model gains a whitelist in that same pass. Segments are corpus-safe but
must start alphanumeric, at most one internal '/', and '..' is rejected outright
— so a value can only ever name something one level inside the cache root, never
escape it. The empirically reproduced payload
'large-v3-turbo/../../../../../../etc' now fails loud in both emit modes.

Byte-identical lock: the extracted worklist output hashes to the same
e3f18b26... as the pre-extraction heredoc (12 lines), pinned in the test.

Refs #116 Refs #115
…rminator (#126)

Five convergent verify findings, all in-scope:

- The / branch is removed. Three legs flagged it and the DA settled it by
  enumerating both sinks: --models takes family/size (whisper/...), a slash in
  MODEL_DIR yields the unusable .../openai_whisper-owner/repo, so ZERO slash-
  bearing values work at both. HF-style owner/repo lives in ModelGrid's hfRepo,
  not the baseline model field — the implementation brief asked for the wrong
  thing, and a spec derived from an error should be corrected, not implemented.
  The test that blessed openai/whisper-large-v3 documented behavior the gate
  cannot perform; it is now in the hostile list.

- The '..' substring guard is now locked by a test. Every previous hostile
  entry failed on some OTHER rule, so deleting that line left the suite green
  (Codex found this; mutation-confirmed here — deleting it now reddens 6).
  a..b and large-v3..turbo pass the charset+start rules and are caught only by
  the substring guard.

- '--' terminator at both call sites. Without it a baseline path beginning with
  '-' lands in argparse's option slot, and --help there exits 0 having
  validated nothing. A validator must have no rc=0 path that skips validation.

- Docstring no longer over-claims. 'can never escape it' became LEXICAL
  containment, explicitly assuming no hostile symlinks in the cache tree — cd
  resolves those (#129). 'one level INSIDE' was also imprecise.

- CHANGELOG entry added — #112, the analogous sibling, has one; this hole is
  the same one field over and reaches a cd target plus a CLI arg.

Refs #116 Refs #115
@kiki830621

Copy link
Copy Markdown
Member Author

Verify Report — PR #126 (#116, #115)

Engine

manual fan-out — 3 lens Agents(security / requirements / regression,model: opus)

  • sequenced Devil's Advocate(opus)+ Codex(gpt-5.6-sol xhigh)跨模型盲驗

誠實標記兩點

  1. 本輪用 3 個 lens 而非標準 4 個——logic lens 對「regex 驗證 + shell rewiring」這種
    改動與 security 的問題面高度重疊,故合併。這是刻意的比例調整,不是遺漏。
  2. Codex leg 本輪成功——前兩輪(regression-gate.sh:baseline language 欄位未驗證即入 worklist TSV(follow-up from #109 verify) #112measurement schema 增 run_kind / decode-determinism provenance 欄位(follow-up from #109 verify) #111)皆因 HTTP 429 缺席並記為 fail-closed
    INFO。它這次第一次出手就貢獻了兩個 Claude lens 全體漏掉的發現(見 Findings WhisperKitEngine rebuilds the pipeline per transcribe call — timed benchmark pass pays model load, X-REAL under-reports WhisperKit #7Lock the transcribeRaw → makeDecodeOptions wiring — factory-only regression test can't catch inline rebuild (from #6 verify) #9),
    回頭證實「缺席不當 PASS」的判斷是對的。

Aggregate

PASS — 0 blocking;5 個 in-scope fix 已於 round-1(be03a92)修畢;3 個 follow-up 已 file。

Round 1 findings → 修復 → 本 report 為修復後狀態。DA 明確表示無法推翻核心 PASS

要求覆蓋率

8 / 8 Strategy items FULLY addressed(#116 四項 + #115 四項)。

Findings(合併後,Fix 欄 = round-1 commit)

# Severity Finding Source Action
1 PASS model 白名單無 bypass:security 窮舉 ≤6 字元字母表 {a,0,./-_} 全空間、取被接受的 9,044 個推過真實 BSD sed → 0 逃逸 0 深度>2;並證明該 sed 長度保持且字母表收縮,結論可推廣 agents:security
2 PASS Unicode 陷阱正確避開:掃過 0→0x10FFFF 全空間,零個非 ASCII 匹配 [A-Za-z0-9]。若當初用 \d/\w 則會中招(\d 匹配 ٣\w 匹配 é)——code 兩者皆未使用 agents:security
3 PASS fullmatch 拒絕尾端換行(match(...+"$") 會接受)——docstring 的宣稱正確且 load-bearing agents:security+codex
4 PASS 測試非空洞:requirements 對實作做 mutation testing——刪 model 驗證 / 改 per-mode 驗證 / 改 emit-before-validate / 刪整個 script,四種變異全數變紅(M4 為 13/13 全紅) agents:requirements
5 PASS 抽取行為等價:OLD(HEAD~1)與 NEW gate 對 3 份敵意 baseline 並排執行,stdout / stderr / exit code 全部 diff-SAME;worklist 輸出 sha e3f18b26… 與測試 pin 值、與 #112 verify 當時獨立記錄的值三方一致 agents:regression+requirements
6 PASS set -euo pipefail 兩個呼叫點都確實 abort;regression 用 stub-lib harness 隔離出「worklist 過但 model 失敗」這種靠 baseline 內容無法觸發的組合,確認 MODEL=$(…) 中止、洩漏值never reaches stdout agents:regression+security+codex
7 MEDIUM ".." in model 那行不被任何測試鎖住——所有既有敵意 payload 都會因其他規則失敗,刪掉該行測試仍全綠(DA mutation 實證:HEAD 上刪除 → 13/13 綠) codex ✅ Fixed be03a92(加 a..b / large-v3..turbo;刪除該行現在紅 6 項)
8 MEDIUM / 分支應移除。DA 裁決性論據:枚舉兩個 sink 後發現零個/ 的值能同時通過——--models 收 family/size(whisper/…),而 /MODEL_DIR 產生不可用的 …/openai_whisper-owner/repo。測試 bless 的 openai/whisper-large-v3 兩個 sink 都拒絕owner/repo 住 ModelGrid 的 hfRepo,不是 baseline 的 model agents:security+regression+devils-advocate+codex(4 legs ✅ Fixed be03a92(移除分支 + 該值改列敵意清單)
9 LOW argparse 缺 -- terminator:BESTASR_BASELINE=--help(該檔存在時 [ -f ] 會過)使 validator exit 0 卻什麼都沒驗。validator 不該有 rc=0 卻跳過驗證的路徑 agents:security ✅ Fixed be03a92(兩個呼叫點加 --
10 LOW docstring 宣稱過絕對:「can never escape it」在 cache 內有惡意 symlink 時不成立(cd 會解析)。「one level INSIDE」在允許 owner/repo 時亦不精確 codex+agents:regression ✅ Fixed be03a92(改為 LEXICAL containment + 明示 symlink 假設,指向 #129
11 LOW CHANGELOG 未更新——#112(直接對應的 sibling)有條目,本 PR 的洞是同一個 class 換一欄且更大(觸及 cd target CLI arg) agents:regression ✅ Fixed be03a92
12 MEDIUM pin-reference-model.sh#115 的同款未修實例,且更嚴重——同欄位名、同 sed、同 cd+find+shasum sink、零驗證;DA 實證逃逸後將竊得的檔名與 sha256 寫回 git-tracked 的 baseline-meta.json。四個 leg 都從 baseline 往前追 model,沒人往回追 sink 問還有誰寫進去 devils-advocate Follow-up → #127(DA 要求 merge 前 file,已照辦)
13 MEDIUM gate 的 wiring 本身無測試:13 個測試全打 lib,把 gate 改回 inline heredoc 測試仍全綠。註解宣稱的 set -e abort 語意目前只有註解在背書;未來一個 local MODEL=$(…) 會靜默消掉它 agents:requirements(F1)+regression(M2)(2 legs 收斂) Follow-up → #128
14 LOW-MED 物理 containment 缺口:合法 model 值 + cache 內惡意 symlink 即可逃出 root(CACHE_ROOT/openai_whisper-large-v3-turbo -> /etc),詞法防護對此無效 codex Follow-up → #129(前提條件「先修過絕對註解」已於 be03a92 滿足)
15 INFO design D2「single fixed reference model」無任何強制:混用 model 的 baseline 以 rc=0 通過,然後用 entries[0] 的 model 測所有 corpus 卻對各自的 golden 比對——會往「通過」方向失敗的 silent mis-gate。本 PR 已建好 models 清單卻只用 [0],強制它現在是一行的事 devils-advocate Noted → 已記入 #117(含時效提醒:models 被當 dead code 刪掉前處理)

Scope Check

無超出 #116/#115 範圍的改動。Round-1 的 5 個修復全部針對本 PR 自身的立論
(「未受測的驗證就是 bug class」),無 scope creep。commit be03a92 觸及 3 檔 + CHANGELOG。

Process Gaps

無。Codex leg 本輪正常完成(前兩輪的 429 已解除)。

Follow-up Findings Filed

誠實的範圍聲明

本 PR 關閉的是 regression-gate.sh 讀取路徑上 model 欄位的詞法 traversal,並把該路徑的
驗證納入測試。不宣稱「baseline 欄位注入面已全數關閉」——metric#117)、
pin-reference-model.sh 的同款實例(#127)、物理 symlink containment(#129)皆為同一
residual class 的存活實例,已各自 file。

kiki830621 added a commit that referenced this pull request Aug 1, 2026
Verify found the PR asserting more than the code does — four times, in a change
whose whole subject is a log that must not mislead. No behaviour changes; the
14/14 byte-identical certification stands.

- safe()'s docstring said a later-added field is 'covered without the author
  having to remember'. It is not: the wrapping is explicit at 11 sites and a new
  f-string is unprotected until someone wraps it. The docstring now states that,
  plus the scope it actually has (C0/DEL/C1, so no ANSI sequence and no cursor
  return) and the scope it does NOT (U+2028, bidi overrides — no control byte
  needed; unreachable here only because corpus and language are whitelisted
  upstream).

- 'keeps the offending bytes diagnosable' overstated it: backslash is not
  escaped, so a real ESC renders identically to a benign literal '\x1b'. Now
  'stays visible'.

- The CHANGELOG's 'unlike golden/tolerance/error_rate it never passes through
  float()' implied float() was validating those. It is a conversion — it accepts
  NaN and Infinity, and the gate then reports exit 0 on a real regression. Filed
  as #134 rather than fixed here: the suggested three-line fix was tested and
  does not close the class (a JSON string "NaN" never reaches parse_constant).

- The headline claimed baseline values no longer reach the CI log unescaped,
  but regression-gate.sh still echoes model unescaped. Scoped to 'the compare
  stage'. Not fixed here because PR #126 deletes the exact heredoc involved and
  already rejects ESC/CR via MODEL_RE — a fix here would collide.

Also records the risk no leg had named: METRICS here, MetricKind in Swift and
metric_kind in the bench validator are three copies with no coupling test, and
because the check returns early, one legitimate future metric would suppress the
verdict for every corpus.

Refs #117
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant