p/security-audit 預演 triage 紀錄(Checkmarx 送測前置)日期:2026-07-25 案號:CM-903 目的:為 Checkmarx SAST 送測預演,先用 semgrep 的 audit 級 ruleset 自掃,把會被 SAST 報出來的項目先定性、先備答辯材料,避免送測當天才逐條解釋。
原始 JSON 依
docs/security-reports/README.md規則存本機當日資料夾(不入版控):docs/security-reports/2026-07-25/semgrep-security-audit-preview-{backend-baseline,backend-final,frontend}.json本文件是入版控的結論(README 規則 3:「數字與判定要能離開本機存活」)。
| 項目 | 值 |
|---|---|
| 工具 | semgrep 1.171.0(Community / OSS engine) |
| ruleset | p/security-audit + p/python |
| 指令 | semgrep --config p/security-audit --config p/python --metrics=off --json -o <out> --timeout 120 . |
| 範圍 | repo 根目錄(--scan was limited to files tracked by git) |
| BE 掃描規模 | 1144 條規則 × 5341 個 target |
| FE 掃描規模 | 40 條規則 × 1401 個 target |
註:本次刻意用 repo 根目錄全掃(非 scripts/sonar_scan.sh 的白名單制),因為送測時 Checkmarx 也是吃整包原始碼。
| Repo | 修前 | 修後 |
|---|---|---|
| BE | 3 條(ERROR 2 + WARNING 1) | 0 條 |
| FE | 0 條 | 0 條(未動任何 FE 檔) |
BE 三條的處置:真修 1 條、標記誤報 2 條(皆附中文說明與本文件連結)。
python.lang.security.use-defused-xml — ERROR → 誤報,標記app/flow_engine/util/bpmn_generator.py:3(import xml.etree.ElementTree as ET)grep 全檔確認零個 ET.parse / ET.fromstring / ET.XMLParser 呼叫 → 原生 ET 未用於任何解析路徑safe_parse,即 defusedxml.ElementTree.parseElement / SubElement / tostring / register_namespace)——產生 XML 不讀外部實體,無 XXE 面# nosemgrep: python.lang.security.use-defused-xml.use-defused-xml。既有 defusedxml 硬化未動。dangerous-subprocess-use-tainted-env-args — ERROR → 低風險真實項,已硬化infra/evidence_classification/classifier_container_runner.py(subprocess.run(cmd, ...))cmd 是 list 形式、無 shell=True → 沒有 shell 命令注入面(規則訊息建議的 shlex.quote() 在此不適用)api/evidence_classification/routes/evidence_classification_route.py:62 evidence_folder_id_override = body.get("evidence_folder_id") → 一路傳到 service:198-199 → runner.run(evidence_folder_id=...) → 進 docker argv。 framework_id 同樣來自 HTTP body(route:52)- 開頭,docker CLI 會當成 flag 解析(例 --privileged、-v /:/host), 即「docker flag 注入」而非命令注入。今天未爆,但面是真的model 已有 ALLOWED_MODELS 白名單(service:181),tenant_id / min_confidence / workers / limit 是數值型經 str() 包過 → 無此面_SAFE_ARG_RE = ^[A-Za-z0-9_][A-Za-z0-9_.-]*$,套用於 evidence_folder_id / framework_id / model(model 為 defense-in-depth 再驗一次)。 不合法即 raise 既有的 ContainerRunError(domain 層例外,未自創新型別)。 刻意「寧可嚴不可鬆」:不允許開頭為 - 或 .。cmmc-l1 / iso27001 / 各 model id)全通過;21 個攻擊/異常值全被拒,含 --privileged、-v、--rm、 -e ANTHROPIC_API_KEY=leak、--mount=type=bind,src=/,dst=/host、abc;rm -rf /、 a`id`、a$(id)、../../etc/passwd、空字串、含引號/換行者,以及非字串型別。subprocess 就報,看不到上游驗證。故在 subprocess.run 的 cmd 引數行加 nosemgrep 並附說明。 ⚠️ 踩到既有已知坑(CM-889 handoff §2-3 記載):多行呼叫的 finding 錨在引數行 (cmd, capture_output=...)而非 subprocess.run( 起始行,標錯行不生效—— 第一次標在起始行時複掃仍報,移到引數行才消。template-unescaped-with-safe — WARNING → 誤報級,不改防護邏輯api/project_summary_report/templates/project_summary_report_template.html:44 ({{ report.summary|safe }})project_summary_report_route.py:160-167 在 render_template 之前對 report.summary 跑 bleach.clean(tags=白名單, attributes=..., protocols=..., strip=True)。 白名單(L33-41)只含排版標籤(p/br/b/strong/i/em/u/s/ul/ol/li/h1-h6/a/img/span/div/ table 家族/blockquote/pre/code),無 script、無 on* 事件屬性。 另有 CM-892 的 img src 限縮(只收 data:image/*,擋 WeasyPrint 伺服器端外抓 SSRF)grep 確認該 template 全 repo 唯一 consumer 是 route:168 → weasyprint.HTML(string=html).write_pdf() → send_file 下載 PDF。 沒有任何路徑把它 render 給瀏覽器,無 script 執行環境{# nosemgrep: ... #} + 中文說明。 實測此形式有效(複掃後該條消失)——case 原本預期「HTML 檔標記可能無效」, 實際上 semgrep 是把它當 Jinja/Python template 解析,{# #} 註解生效。FE 掃描結果是 0 findings,但不等於「FE 通過所有檢查」,有兩點必須揭露:
p/security-audit + p/python 對 Vue/JS/TS 專案能匹配的規則本就少。 要有意義地掃 FE,需另外指定 JS/TS 導向的 ruleset(如 p/javascript、 p/typescript、p/xss)——本次預演未做,屬已知缺口。errors 陣列,level=warn,type=PartialParsing):
figma/src/app/components/auditor/EvidenceAnalytics.tsx:238figma/src/app/pages/ProjectCreate.tsx:401 / :428 (皆為 & 中文 / & Confirm 的語法解析失敗) 這兩個檔在 figma/ 目錄下(設計稿轉出的參考碼,非上線程式),但 parse 失敗代表那些行沒被任何規則檢查過,「0 條」在這兩檔上不成立。case 頁原文寫「171 條規則執行錯誤(3 個規則需 Pro engine——弱加密偵測/CORS 設定——在 77 檔上未執行)」。
本 case 動工時無法重現此數據:
/private/tmp/semgrep-preview-2607/be-security-audit.json) 本身即 errors: 0、skipped_rules: 0errors: 0、skipped_rules: 0推測原因:該數字可能來自另一次不同參數的掃描(例如 --config auto,會拉更多需 Pro engine 的規則),或是 FE/BE 混記。本文件記錄我實際量到的值; 若「171 條」有其他來源,請補充來源指令後併入。
Pro engine 限制本身仍然成立且需揭露:本次全程使用 Community/OSS engine, semgrep 的跨檔案資料流分析(interfile taint)與部分規則需 Pro 授權。 Checkmarx 具備跨檔案分析能力,可能報出本次掃不到的跨檔 taint 路徑。
| Checkmarx 可能報的 | 我方立場 |
|---|---|
XXE(xml.etree import) |
解析端全走 defusedxml(3 處),原生 ET 只用於產生 XML。見 §3① |
命令注入(subprocess.run) |
list 形式無 shell=True,無 shell 注入面;docker flag 注入面已加字元集白名單擋掉(拒 - 開頭)。見 §3② |
XSS(|safe) |
render 前 bleach 白名單消毒(無 script / 無 on* 事件),且輸出只進 WeasyPrint 轉 PDF、不進瀏覽器。見 §3③ |
| 硬編碼 IP | 內網 dev IP / 虛構 E2E 測試資料,已於 SonarQube 標 accept(CM-900) |
figma/ 兩檔的 parse error 若要收乾淨,需修那兩行語法或把 figma/ 加入 .semgrepignore(該目錄非上線碼,後者較合理)evidence_folder_id 之外新增任何進 docker argv 的字串參數, 必須一併過 _validate_cmd_arg(規則寫在 runner 檔內註解)project_summary_report_template.html 未來被 render 給瀏覽器(不只轉 PDF), §3③ 的「無 script 執行環境」前提即失效,|safe 需重新評估