# FR-114.V3 b4 實打驗證報告——資源上限：只驗被擋、不打垮（24 條，只驗不修）

- **卡號**：CM-2266（母卡 CM-2019）
- **目標機**：190（BE `guidant-ai-be:1.21.0b4`／FE 同版／agent `1.1.0b2`，九容器 healthy）
- **日期**：2026-09-27
- **性質**：產品驗收（非滲透）。每條做法固定三步：讀對應修正卡查「上限值＋超限回應碼」→ 送「剛好超過上限一點點」的請求 →
  比對實際回應是否等於宣告的回應。等於 ✅／不等於 ❌／190 無資料可測或被前置條件擋到 ⚠️。
- **打之前查過 190 無人總測**：`docker logs guidant-api` 22:16 之後靜默；每打一條看 `docker stats guidant-api`。
- **未打垮 190**：全程 guidant-api 記憶體 1.50–1.60GiB 平穩，無任何 201MB／33MiB 級跳升；所有超大檔都在「讀目錄／擋長度」階段被拒，未進解壓／解析。
- **只讀不改**：測試中建的少量資料（一個測試檢測基準、一個資源庫、兩個框架解析單、幾筆失敗的匯入 staging）測完即刪／已 soft-delete；未套 migration、未重啟容器、未動 STG／POC／DEV。

## 190 環境實況（影響判讀，先讀）

1. **稽核輪次只有兩個**：round 1（closed）、round 2（planning，未啟動稽核）。因此**稽核計畫 Word（#176）／稽核紀錄 Excel（#177）沒有「可編輯的輪次」可打**——請求在「本輪已結案／尚未啟動」的前置條件就被擋，走不到壓縮炸彈守門。守門本身已在同族 #150／#152 於全域解析入口證實（同一支 `check_ooxml_archive`）。
2. **`admin`（平台管理員、root 租戶）登入不走 MFA**，`POST /login` 直接回 `access_token`（其餘五個租戶帳號要 MFA）。#192、#34 的 `/license/upload` 需要它。
3. **檢測基準的並行解析上限（#80）是行程內記憶體旗標**：多 worker gunicorn 下，兩個請求落在不同 worker，且測試用小基準毫秒級解析完，API 打不出「同一版正在解析中」的重疊窗——結構正確、行為層無法從外部逼出，打折。

## 結果表（n＝SUMMARY 條號；結果 ✅過／⚠️打折或前置擋／❌破）

| n | CM | 一句 | 結果 | 證據 |
|---|----|------|------|------|
| 16 | 2059 | 特製流程圖走訪算不完 | ⚠️ | `POST /flow-engine/flow-templates/validate` 送 30 gateway 深鏈 → schema/topology 先判 `gateway_missing_condition`（HTTP200 valid:false），走不到走訪深度上限（100節點/8層）。上限在 create/起輪次的走訪端，需 schema 合法的惡意範本才觸得到，API 構造不出——同 V1 #40/#87/#88 打折原因 |
| 21 | 2064 | AI 儀表板無限重試 | ✅ | `POST /ai-dashboard/auto-generate` ×5：第 4、5 次 `429 AI_DASHBOARD_429001`（上限 3/分鐘，因一次生成打兩次外部 AI）。前 3 次 200 |
| 34 | （無卡，共用底層） | 1.4MB 授權匯入吃 1GB | ✅ | `POST /license/activation/upload`（任何登入者）：33MiB base64 payload → `400`（0.13s，記憶體平）；zlib 炸彈（解開 17MB）→ `400`（1.2s）。log 確認 `_verify_signed_license` 擋下。守門在 `engine._unpack_payload`（解碼前擋 32MiB 字串＋有界解壓 16MiB），例外被 `verify_payload` 包成 `LICENSE_400002`（不是原始長度碼，屬預期包裝）。`/license/upload` 另需平台管理員（`403 GRC_403060`）|
| 70 | 2050 | 開始掃描多存明文帳密 | ✅ | DB 唯讀：`select count(*) from compliance.agent_tasks where params ? '_credentials'` = **0**；全表最新一筆 20:22。無殘留（修正卡本就查得 DEV 為 0）|
| 76 | 2057 | 網址規則包繞過壓縮炸彈三上限 | ✅ | file 型上傳 500MB 解壓炸彈規則包 → `400 DETECTION_TOOLS_400010`「Profile archive failed safety checks」（上傳即擋，記憶體平）。網址型的三道上限與 file 型下沉到同一 `_read_entries`／`archive_limits`，同支守門 |
| 77 | 2057 | 一萬檔上限展開後才數 | ✅ | 上傳 10,001 entry 的規則包 → `400 DETECTION_TOOLS_400010`（讀 zip 目錄即擋，未展開進記憶體，記憶體平）|
| 80 | 2058 | 重新解析去重與總量上限 | ⚠️ | `POST /detection-tool-profile-versions/<v>/extraction` 併發 ×2 與 ×12 均 200——並行旗標是行程內記憶體、多 worker 下打不出重疊窗，小基準毫秒解析完。結構（`is_inflight`→409018／`has_capacity`→409019）在源碼確認存在，行為層打折 |
| 81 | 2058 | 換工具沿用超大網段展開 | ✅ | `POST /detection-tools/configs/<c>/test-connection` 送 33 台 → `400 DETECTION_TOOLS_400019`「本次 33 台，上限 32 台」；送 32 台 → 200。單次主機數上限生效（換工具重驗與展開端 65,536 絕對上限同支邏輯）|
| 83 | （無卡，共用底層） | 清單頁 page_size 超大撈整表 | ✅ | 多支列表（`detection-tool-profiles/list`、`project-summary-reports`、`oscal-frameworks`）`pager.page_size=1000`→200、`1001`／`100000`→`400 COMMON_400001`。`MAX_PAGE_SIZE=1000` 在共用 `PagerSchema`，一處蓋全站 |
| 94 | 2064 | 聊天框無長度無次數上限 | ✅ | `POST /ai-chatbot`：5000 字訊息 → `400 AI_BOT_400001`「訊息過長」（上限 4000）；短訊息 ×12 → 第 11、12 次 `429 AI_BOT_429001`（上限 10/分鐘）|
| 106 | （無卡，共用底層） | 日誌分區失效、保存期無效 | ✅ | DB 唯讀：`api_logs`／`system_logs` 各有 2026_09/10/11 三月份分區＋default；備用表 `*_default` 皆 0 筆；排程 `log_partition_maintenance (daily 03:30 UTC)` 已在 APScheduler 清單；`maintain_log_partitions()` prosecdef=t（SECURITY DEFINER，owner cmmgr）→ 不再因權限每晚失敗 |
| 108 | （無卡，共用底層） | 半夜清理程式靠「無身分給最高權限」 | ✅ | 源碼確認 `core/scheduler.py` 兩支清理（`job_binding_orphan_cleanup`／`framework_parse_job_cleanup`）皆以具名 `system_context("...")` 執行；APScheduler 啟動清單列出兩者（daily 03:10／01:00 UTC）。M20-4 已複查改具名系統身分＋反向警語 |
| 131 | 2061/2181/2227 | 分類壓縮檔炸記憶體、殘留燒額度 | ✅ | 落地版 `guidant-classifier` 容器 `docker inspect`：Memory=2147483648（2GiB）、NanoCpus、PidsLimit=256、逾時 `killpg`／`docker kill` 整組砍。分類容器 healthy、55MiB。上限＋逾時 kill 在位（實際灌爆屬破壞性、不做，看設定值＋容器實況）|
| 136 | （無卡，共用底層） | 清單頁 page_size 超大（與 #83 同底層） | ✅ | 同 #83：`page_size=1001`→`400 COMMON_400001`；根源同一支 `PagerSchema.MAX_PAGE_SIZE=1000` |
| 149 | 2171 | 不必登入的遮密碼比對卡死 | ✅ | 不帶 token、打不存在網址送 16/64/65/128KB 攻擊內容：全部 `404`、0.09–0.18s（修前套件層量到 16KB 171 秒）。DB `api_logs` 確認 request 被截到 65558 bytes 帶「…[已截斷，原長 N bytes]」。線性時間＋64KB 截斷兩層都在 |
| 150 | 2181/2189/2203 | SSP Excel 只量壓縮檔不量解開 | ✅ | `POST /ssp-excel-imports/parse` 送 201MB 解壓炸彈 xlsx → 解析單 `failed GRC_400065`（0.4s，記憶體平）；送 5,001 段 xlsx → 同 `GRC_400065`。開檔前讀 zip 目錄擋（200MB／5000 段）|
| 152 | 2181 | SSP Word 只量壓縮檔 | ✅ | `POST /ssp-docx-imports/parse` 送 201MB 解壓炸彈 docx → 解析單 `failed GRC_422003`（0.4s，記憶體平）|
| 154 | 2171/2181 | Excel 版本格一百萬數字卡比對 | ✅（隨 #150 同支覆蓋） | 版本號截 200 字＋位數上限在 `version_check.py`，與 #150 同一 xlsx 匯入路徑；#150 的 201MB 已在版本檢查前被壓縮炸彈守門擋下，版本格上限為同支後段防線。無獨立可打入口，以 #150 佐證 |
| 175 | 2179/2182 | 草稿框架版本拿去建資源庫 | ✅ | `POST /oscal/resource-libraries`：draft 框架版本 → `412 GRC_412057`「框架版本尚未發佈」；published → 200。建庫共用函式在 clone 前擋（一處蓋手動／Excel／Word 三入口）。建的測試庫已刪 |
| 176 | 2181 | 稽核計畫 Word 解壓炸彈 | ⚠️ | `POST /ap/<ap>/docx-imports/parse` 兩個 AP：一個 `404 GRC_404038`（無對應輪次）、一個 `412 GRC_412049`（本輪已結案）。190 無可編輯輪次，前置擋在守門前。同支 `check_ooxml_archive` 已於 #150/#152 證實 |
| 177 | 2181 | 稽核紀錄 Excel 讀到最後一列 | ⚠️ | `POST /audit-round/<r>/ar-imports/parse`：round 2 `412 GRC_412043`「本輪尚未啟動稽核」。190 無可編輯輪次，前置擋在守門前。串流讀＋200 空白列停＋5000 列上限在同支解析器（#177 與稽核結果 Excel 共用）|
| 192 | 2184/2201 | CMMC PDF 頁數撐爆記憶體 | ✅ | `POST /oscal-framework-parse-jobs/parse`（admin）送 3,001 頁 PDF → 解析單 `failed GRC_422004`（0.86s、記憶體平；修前 7s/973MB）；10 頁 → `awaiting_review`。log 確認 `base_parser_adapter._assert_page_count` → `OSCAL_V2_400006`「PDF 頁數超過上限」（3000 頁上限）。解析單已刪 |
| 196 | 2181/2213 | 稽核計畫 Word 塞幾萬空白卡比對 | ⚠️ | 同 #176，稽核計畫 Word 入口在 190 無可編輯輪次、前置擋下。空白壓縮＋標題截 2000 字在解析器（CM-2181），與 #176 同入口，一併打折 |
| 207 | 2203 | 匯入任務 Excel 無上傳檢查 | ⚠️/❌ | `POST /grc/project/<p>/ap/<ap>/jobs/import` 送 201MB 炸彈 xlsx → **HTTP 500 `column ap.uid does not exist`**（0.5–0.6s，記憶體平）。壓縮炸彈守門在 500 之前跑不到；此 500 是既有缺陷（jobs/import 路徑 SQL 查 `ap.uid` 欄位不存在），**與本卡上限無關、屬另一 bug，建議另開卡**。守門本身（同 CM-2181 `archive_limits`＋串流讀）在源碼確認存在，但此入口無法乾淨驗到 |

## 統計

- **✅ 通過 15 條**：#21、#34、#70、#76、#77、#81、#83、#94、#106、#108、#131、#136、#149、#150、#152、#175、#192（＋#154 隨 #150 同支覆蓋）
- **⚠️ 打折／前置擋 8 條**：#16（走訪端 schema 先擋，同 V1）、#80（並行旗標行程內、多 worker 逼不出重疊）、#176／#177／#196（190 無可編輯輪次）、#207（撞既有 500）；#154 以同支佐證
- **❌ 破 0 條**
- **另發現 1 個既有缺陷（非本卡範圍）**：`jobs/import` 路徑 `column ap.uid does not exist` 500 → 建議另開卡

## 打折處逐一講明（不報成通過）

- **#16**：190 上送不出「schema 合法但走訪爆量」的流程圖——validate 在 topology 層先擋（gateway 缺條件）。走訪深度上限（100 節點／8 層）在 create／起輪次端，需要一份格式完全合法的惡意範本才觸得到，這無法用 API 從外部construct。與 V1 的 #40／#87／#88 同一打折原因。
- **#80**：並行控管 `extraction_concurrency` 是**單一行程的記憶體旗標**。190 是多 worker gunicorn，兩個 extraction 請求落在不同 worker、各自看到空旗標；且測試基準小到毫秒級解析完，先到的早已 release。要逼出「同版正在解析」重疊窗需在同一 worker 內卡住解析，API 外部做不到。源碼層 `is_inflight`→409018、`has_capacity`→409019 確認存在。
- **#176／#177／#196**：190 只有 closed 與 planning 兩個輪次，稽核計畫／稽核紀錄匯入都要求「輪次在對應可編輯階段」，請求在前置條件（`GRC_404038`／`GRC_412049`／`GRC_412043`）就被擋，走不到壓縮炸彈守門。守門是四入口共用的同一支 `check_ooxml_archive`（jedi-compliance-audit），已在 #150（SSP Excel）／#152（SSP Word）於全域解析入口實打證實生效。
- **#207**：`jobs/import` 撞到既有 500（`column ap.uid does not exist`），發生在檔案守門之前，因此本卡無法乾淨驗到上限。此 500 與資源上限無關，是另一支 bug，建議另開卡處理。
