盤點日期 2026-09-24|盤點卡 CM-2123(母卡 CM-2122)|這份文件只盤點、不掃描——切好棒等首腦開卡派工。 行數全部是
wc -l逐檔實算。基準:BEfeature/reviewcommita7f403856;jedi monorepofeature/reviewcommiteafc7ae5(jedi-oscal-v2/工作區乾淨)。每棒段落附兩側各自的驗證指令。
名詞先講清楚(讀者是 PM 與新手工程師):
jedi-oscal-v2,放在另一個 repo(~/Projects/Jedicogy/module/jedi-python-package/jedi-oscal-v2/)的一包程式,負責把合規文件存進資料庫、再從資料庫組回來。它沒有網址入口,所有功能都要靠主專案或 jedi-compliance-audit 套件呼叫。get_by_id/get_by_uid/update/delete_by_id/delete_by_uid)。套件的服務層有 33 支方法直接拿呼叫者給的編號去讀、改或刪,沒有任何範圍檢查(見第 4 節)。主專案目前的呼叫端都先在正確的父層底下找到那筆、才把編號交進去(形狀對),所以這不是現成的洞,而是「下一個寫呼叫端的人漏一步,就是第 118/119 項」的地雷區。掃描要做的是逐支確認呼叫端都有先找父層。02-schema.sql 與 DEV 實查一致)。第 1 點講的「靠呼叫端先找父層」,底下完全沒有第二道防線。這是總表第 128 項的全貌,本棒補齊清單(第 5 節)。oscal_io_service.py 只做 JSON 匯出、以及「把主專案已經組好的 Python 字典寫進資料庫」的匯入。真正會碰到 XML 的是主專案那側讀 Word(python-docx,底層已關掉外部實體解析)與讀 Excel(openpyxl,主專案已裝 defusedxml,會自動套用防護)。掃描重點從「XML 攻擊」換成「超大/超深的字典」與「Word/PDF 壓縮炸彈」。app/oscal/service/ssp_docx_import_app_service.py:220、:226 都是 str(e))。這跟總表第 132 項(PDF 解析失敗吐伺服器路徑)同一個形狀,但這支檔在 FR-113 O4 範圍內,本批不重掃;列給 W1 研究員當「解析器丟出來的例外會長什麼樣」的背景。⚠️ 以上是盤點時開檔看到的「疑點」與「形狀」,不是掃描結論。
cd ~/Projects/Jedicogy/module/jedi-python-package/jedi-oscal-v2
git ls-files -- 'jedi_oscal_v2/*.py' | xargs wc -l | tail -1 # 363 支、15,852 行正式程式 363 支/15,852 行,與卡片數字一致(tests 不掃)。另有兩個非 .py 目錄:assets/(兩份 CMMC 官方 PDF+一份對照檔,當範例與種子資料用)、migrations/001-cmmc-2-catalog-seed.sql(CMMC 目錄的種子資料)——都不是程式邏輯,不掃。
363 支的去向:
| 分類 | 檔數 | 行數 | 為什麼 |
|---|---|---|---|
| 排進掃描棒 | 69 | 7,117 | 見第 2 節(去重後;含 2 支空的 adapter __init__.py) |
| domain/entity 純資料結構 | 99 | 2,195 | 全部是 @dataclass 欄位宣告,0 個方法、0 個 if(見附錄 A) |
| infra/model 資料表宣告 | 54 | 2,921 | 全部是 SQLAlchemy 欄位宣告,0 個方法、0 個驗證器/事件(附錄 A) |
| infra/mapper 欄位搬家 | 54 | 2,671 | 每支只有 if model is None: return None 一個判斷,其餘一欄一欄搬;沒有任何一支做 json.loads 或遞迴組裝(JSON 欄位由資料庫驅動直接給 Python 物件)(附錄 A) |
| domain/repository 介面 | 54 | 802 | 抽象方法宣告,沒有實作(附錄 A) |
| 錯誤碼與列舉常數 | 2 | 103 | common/code/oscal_v2_error_code.py(41)、common/enum/oscal_enums.py(62) |
jedi_oscal_v2/__init__.py |
1 | 30 | 只有 docstring |
其餘 __init__.py(0~1 行) |
30 | 13 | |
| 合計 | 363 | 15,852 | ※ 不掃的共 294 支/8,735 行,明細見附錄 A |
與卡片說法的差異:卡片寫「infra/repository 54」是指 54 張表對應 54 支 repo,但實際 *_repo_impl.py 是 45 支,另外 9 支是 __init__.py。要列查詢條件的就是這 45 支(全部排進 V1/V2/V4a)。
cd ~/Projects/Billows/Audit-Manager/compliance-manager-be
git ls-files -- domain/oscal/parser domain/oscal/adapter | xargs wc -l | tail -1 # 11 支(含 2 支空檔)、2,986 行⚠️ 比卡片多 2 支:卡片列 7 支/2,946 行(取自 FR-113 盤點第 196~210 行),但目錄實際還有:
| 檔 | 行數 | 為什麼補進來 |
|---|---|---|
domain/oscal/parser/framework_patterns.py |
19 | 四組「控制項編號長什麼樣」的正規表示式(regex)。Word 解析器每一段文字都會拿它去比對;regex 寫法不好會被特製文字卡死 CPU(ReDoS),要一起看。併入 W1 |
domain/oscal/adapter/adapter_registry.py |
21 | 「框架代號 → 用哪支轉接器」的對照表,DI 在用。併入 W2 |
兩支 __init__.py 是空檔。所以主專案側實際掃 9 支/2,986 行。
套件本體一支都沒被掃過,零重疊。以下是「呼叫套件的主專案檔」,全部在 FR-113 範圍內:
| 主專案檔 | 呼叫套件的什麼 | 哪一棒掃過 |
|---|---|---|
app/oscal/service/ssp_docx_import_app_service.py |
import_ssp/clear_ssp_body/build_ssp_snapshot |
FR-113 O4 |
app/oscal/service/ssp_excel_import_app_service.py |
同上 | FR-113 O3a |
app/oscal/service/oscal_export_app_service.py |
export_oscal |
FR-113 O2 |
app/oscal/service/framework_app_service.py |
import_catalog_from_pdf/excel |
FR-113 O8b |
app/oscal/service/framework_parse_job_service.py |
PDF 解析工作 | FR-113 O8a |
app/oscal/service/ssp_components/inventory_items/leveraged/party_app_service.py |
ssp_service 的改/刪 |
FR-113 O5 |
app/oscal/service/ssp_control_implementation_service.py |
update_implemented_requirement/update_statement |
FR-113 O1 |
app/module_frame/service/module_frame_*_service.py(party/components/inventory/leveraged/control_default/control_objective_default) |
同上 | FR-113 B 系列 |
app/project/service/project_start_app_service.py |
clone_resource_library |
FR-095 H1 |
jedi-compliance-audit 套件是第二個大呼叫端(assessment_result_app_service/poam_app_service/audit_round_app_service 直接用本套件的 repo 與 service),在 FR-116 C2a/C3 範圍內。
⚠️ 卡片提到「O1/O6 報告有引用 ssp_reference_document_repo_impl.py」——查證屬實,那支檔在 jedi-compliance-audit、不是本套件,本套件沒有程序書相關的表或程式。第 118/119/129 項的根因不在本套件。
⚠️ 一支例外沒被任何人掃過:app/flow_control/service/assessment_plan_app_service.py 呼叫本套件 generate_draft 與 clone_metadata(:313、:354),它不在 FR-113 任何一棒、也不在 FR-116 盤點的主專案清單。本批不收(它是主專案業務層、不是 Word 解析器),列在第 7 節請首腦決定歸屬。
| 檔數 | 行數 | |
|---|---|---|
| 套件側 | 69 | 7,117 |
| 主專案側 | 9 | 2,986 |
| 合計(去重) | 78 | 10,103 |
切法原則:按文件類型縱切——同一種文件的「服務 → repo 實作」放同一棒,因為這支套件要找的是「子物件有沒有被綁在正確的父文件底下」,拆開就看不出來。oscal_io_service.py 1,660 行一支獨立成棒。主專案 Word 解析器拆兩棒,跟套件 CMMC PDF 解析器(V4b)配對:兩邊都是「吃外部檔案、解析成內部格式」,風險形狀一樣(大小上限、壓縮炸彈、錯誤訊息外洩、regex 卡死)。
接縫檔(刻意在兩棒重複列):catalog_service.py(V4a 管資料、V4b 管解析入口)。
| 棒 | 做什麼 | 檔數 | 行數 | 側 |
|---|---|---|---|---|
| V1 | 🔴 SSP 本體:服務、複製與凍結、SSP 子表與 metadata 的 repo | 21 | 1,503 | 套件 |
| V2 | 🔴 稽核計畫、稽核結果、改善計畫:服務與 repo | 26 | 1,880 | 套件 |
| V3 | 🔴 OSCAL 匯入匯出整份文件(oscal_io_service.py) |
1 | 1,660 | 套件 |
| V4a | 控制項目錄、基準線、框架:服務、目錄複製、repo | 14 | 1,202 | 套件 |
| V4b | CMMC PDF/Excel 解析器 | 8 | 1,110 | 套件 |
| W1 | 主專案 Word 內容抽取器 | 5 | 1,658 | 主專案 |
| W2 | 主專案 CMMC 轉接器與中介格式 | 4 | 1,328 | 主專案 |
| 逐棒加總(含接縫重複) | 79 | 10,341 |
逐棒加總扣掉接縫 catalog_service.py(238 行,重複 1 次)= 78 檔、10,103 行,與 1.4 一致。
這一棒要回答:ssp_service.py 那一堆「只收編號就改/刪」的方法,每一個呼叫端是不是都先在正確的 SSP 底下找到那筆;SSP 複製/凍結時,子物件的歸屬欄位有沒有全部換成新 SSP、有沒有漏帶或帶到別份 SSP 的東西。
套件側(cd ~/Projects/Jedicogy/module/jedi-python-package/jedi-oscal-v2)
350 jedi_oscal_v2/app/service/ssp/ssp_service.py ← 🔴 21 支只收編號就讀/改/刪的方法
245 jedi_oscal_v2/app/service/ssp/ssp_clone_service.py ← 深複製 12 張子表
192 jedi_oscal_v2/app/service/snapshot/oscal_snapshot_service.py ← 凍結、資源庫三件組複製
129 jedi_oscal_v2/app/service/snapshot/metadata_clone_service.py ← metadata(角色、人員、資源)複製
48 jedi_oscal_v2/infra/repository/ssp/ssp_by_component_repo_impl.py
36 jedi_oscal_v2/infra/repository/ssp/ssp_component_repo_impl.py
47 jedi_oscal_v2/infra/repository/ssp/ssp_control_implementation_repo_impl.py
31 jedi_oscal_v2/infra/repository/ssp/ssp_diagram_repo_impl.py
50 jedi_oscal_v2/infra/repository/ssp/ssp_implemented_requirement_repo_impl.py
40 jedi_oscal_v2/infra/repository/ssp/ssp_information_type_repo_impl.py
38 jedi_oscal_v2/infra/repository/ssp/ssp_inventory_item_repo_impl.py
46 jedi_oscal_v2/infra/repository/ssp/ssp_leveraged_authorization_repo_impl.py
17 jedi_oscal_v2/infra/repository/ssp/ssp_repo_impl.py
38 jedi_oscal_v2/infra/repository/ssp/ssp_statement_repo_impl.py
47 jedi_oscal_v2/infra/repository/ssp/ssp_system_characteristics_repo_impl.py
43 jedi_oscal_v2/infra/repository/ssp/ssp_system_implementation_repo_impl.py
38 jedi_oscal_v2/infra/repository/ssp/ssp_system_user_repo_impl.py
17 jedi_oscal_v2/infra/repository/base/metadata_repo_impl.py
17 jedi_oscal_v2/infra/repository/base/party_repo_impl.py
17 jedi_oscal_v2/infra/repository/base/resource_repo_impl.py
17 jedi_oscal_v2/infra/repository/base/role_repo_impl.py
共 21 檔/1,503 行。
驗證:
cd ~/Projects/Jedicogy/module/jedi-python-package/jedi-oscal-v2 && P=jedi_oscal_v2 && git ls-files -- $P/app/service/ssp/ssp_service.py $P/app/service/ssp/ssp_clone_service.py $P/app/service/snapshot/oscal_snapshot_service.py $P/app/service/snapshot/metadata_clone_service.py "$P/infra/repository/ssp/*_repo_impl.py" "$P/infra/repository/base/*_repo_impl.py" | xargs wc -l | tail -1 # 21 檔、1503這一棒要回答:稽核結果與改善計畫的服務收到「風險編號+一串判定編號」「矯正措施編號」這類輸入時,有沒有驗證它們屬於同一份稽核結果;update_poam_item、link_findings 這種只收編號的方法被誰呼叫。
套件側(cd ~/Projects/Jedicogy/module/jedi-python-package/jedi-oscal-v2)
215 jedi_oscal_v2/app/service/ap/assessment_plan_service.py ← 用 ap_uid 找 AP 再整批換任務/受評對象
156 jedi_oscal_v2/domain/service/ap/ap_draft_service.py ← 從 SSP 產生 AP 草稿
184 jedi_oscal_v2/app/service/ar/assessment_result_service.py
143 jedi_oscal_v2/app/service/ar/assessment_risk_service.py ← 🔴 link_findings 不驗判定屬於誰
186 jedi_oscal_v2/domain/service/ar/ar_finding_matrix_service.py
144 jedi_oscal_v2/app/service/poam/poam_service.py ← 🔴 update_poam_item 只收編號
146 jedi_oscal_v2/app/service/poam/remediation_service.py ← upsert 用 risk_id+uuid 定位(有範圍)
38 jedi_oscal_v2/infra/repository/ap/ap_assessment_activities_repo_impl.py
51 jedi_oscal_v2/infra/repository/ap/ap_assessment_subjects_repo_impl.py
36 jedi_oscal_v2/infra/repository/ap/ap_local_objectives_repo_impl.py
17 jedi_oscal_v2/infra/repository/ap/ap_repo_impl.py
35 jedi_oscal_v2/infra/repository/ap/ap_reviewed_controls_repo_impl.py
47 jedi_oscal_v2/infra/repository/ap/ap_task_participants_repo_impl.py
45 jedi_oscal_v2/infra/repository/ap/ap_task_subjects_repo_impl.py
52 jedi_oscal_v2/infra/repository/ap/ap_tasks_repo_impl.py
38 jedi_oscal_v2/infra/repository/ar/ar_assessment_subjects_repo_impl.py
45 jedi_oscal_v2/infra/repository/ar/ar_finding_repo_impl.py
68 jedi_oscal_v2/infra/repository/ar/ar_finding_risk_repo_impl.py ← link/unlink 只用 (finding_id, risk_id)
34 jedi_oscal_v2/infra/repository/ar/ar_observation_repo_impl.py
29 jedi_oscal_v2/infra/repository/ar/ar_remediation_repo_impl.py
17 jedi_oscal_v2/infra/repository/ar/ar_repo_impl.py
29 jedi_oscal_v2/infra/repository/ar/ar_results_repo_impl.py
29 jedi_oscal_v2/infra/repository/ar/ar_risk_repo_impl.py
29 jedi_oscal_v2/infra/repository/poam/poam_item_repo_impl.py
39 jedi_oscal_v2/infra/repository/poam/poam_milestone_repo_impl.py ← get_by_assignee 零呼叫者
28 jedi_oscal_v2/infra/repository/poam/poam_repo_impl.py
共 26 檔/1,880 行。
驗證:
cd ~/Projects/Jedicogy/module/jedi-python-package/jedi-oscal-v2 && P=jedi_oscal_v2 && git ls-files -- "$P/app/service/ap/*_service.py" "$P/app/service/ar/*_service.py" "$P/app/service/poam/*_service.py" "$P/domain/service/ap/*_service.py" "$P/domain/service/ar/*_service.py" "$P/infra/repository/ap/*_repo_impl.py" "$P/infra/repository/ar/*_repo_impl.py" "$P/infra/repository/poam/*_repo_impl.py" | xargs wc -l | tail -1 # 26 檔、1880這一棒要回答:整份文件進來時,內容裡的 uuid、名稱、陣列長度、巢狀深度有沒有上限;「更新模式」用什麼鍵認定「是同一筆」,能不能被拿去覆蓋別份 SSP 的資料;匯出時會不會把不屬於這份文件的東西組進去;整份匯入失敗時會不會寫一半。
套件側(cd ~/Projects/Jedicogy/module/jedi-python-package/jedi-oscal-v2)
1660 jedi_oscal_v2/app/service/io/oscal_io_service.py
共 1 檔/1,660 行。
驗證:
cd ~/Projects/Jedicogy/module/jedi-python-package/jedi-oscal-v2 && git ls-files -- jedi_oscal_v2/app/service/io/oscal_io_service.py | xargs wc -l # 1 檔、1660這一棒要回答:目錄複製(資源庫套用框架時用)有沒有把子物件的父層編號全部換掉;基準線解析時「從哪份目錄挑控制項」這個來源是不是伺服器端決定;框架刪除、版本發佈這幾支「只收 uid」的方法被誰呼叫、有沒有平台管理員檢查。
套件側(cd ~/Projects/Jedicogy/module/jedi-python-package/jedi-oscal-v2)
238 jedi_oscal_v2/app/service/catalog/catalog_service.py ← 接縫(V4b 也帶)
333 jedi_oscal_v2/app/service/snapshot/oscal_clone_service.py ← 目錄樹、基準線複製
72 jedi_oscal_v2/app/service/profile/profile_service.py
163 jedi_oscal_v2/domain/service/profile/profile_resolution_service.py
120 jedi_oscal_v2/app/service/framework/framework_service.py ← delete/publish 只收 uid
28 jedi_oscal_v2/infra/repository/catalog/catalog_control_param_repo_impl.py
57 jedi_oscal_v2/infra/repository/catalog/catalog_control_part_repo_impl.py
41 jedi_oscal_v2/infra/repository/catalog/catalog_control_repo_impl.py
41 jedi_oscal_v2/infra/repository/catalog/catalog_group_repo_impl.py
17 jedi_oscal_v2/infra/repository/catalog/catalog_repo_impl.py
17 jedi_oscal_v2/infra/repository/framework/framework_repo_impl.py
26 jedi_oscal_v2/infra/repository/framework/framework_version_repo_impl.py
32 jedi_oscal_v2/infra/repository/profile/profile_import_repo_impl.py
17 jedi_oscal_v2/infra/repository/profile/profile_repo_impl.py
共 14 檔/1,202 行。
驗證:
cd ~/Projects/Jedicogy/module/jedi-python-package/jedi-oscal-v2 && P=jedi_oscal_v2 && git ls-files -- $P/app/service/catalog/catalog_service.py $P/app/service/snapshot/oscal_clone_service.py "$P/app/service/profile/*_service.py" "$P/domain/service/profile/*_service.py" "$P/app/service/framework/*_service.py" "$P/infra/repository/catalog/*_repo_impl.py" "$P/infra/repository/profile/*_repo_impl.py" "$P/infra/repository/framework/*_repo_impl.py" | xargs wc -l | tail -1 # 14 檔、1202這一棒要回答:上傳的 PDF 有沒有頁數與大小上限;解析出錯時錯誤訊息往上丟什麼(總表第 132 項同形);Excel 版本走 pandas+openpyxl,有沒有公式或壓縮炸彈的防護。
套件側(cd ~/Projects/Jedicogy/module/jedi-python-package/jedi-oscal-v2)
238 jedi_oscal_v2/app/service/catalog/catalog_service.py ← 接縫:import_catalog_from_pdf/excel 入口
117 jedi_oscal_v2/infra/adapter/base_parser_adapter.py ← 頁碼範圍計算
34 jedi_oscal_v2/infra/adapter/oscal_parser_factory.py
639 jedi_oscal_v2/infra/adapter/cmmc/cmmc2_lv2_parser_adapter.py ← pdfplumber+pandas+openpyxl(中文 docstring 最多的檔)
55 jedi_oscal_v2/domain/ports.py ← 解析器介面
27 jedi_oscal_v2/common/code/parser_adapter_type.py ← 宣告了 YAML/JSON 匯入類型、但沒有實作(見第 6 節)
0 jedi_oscal_v2/infra/adapter/__init__.py
0 jedi_oscal_v2/infra/adapter/cmmc/__init__.py
共 8 檔/1,110 行(含 2 支空檔)。
驗證:
cd ~/Projects/Jedicogy/module/jedi-python-package/jedi-oscal-v2 && P=jedi_oscal_v2 && git ls-files -- $P/app/service/catalog/catalog_service.py "$P/infra/adapter/*.py" "$P/infra/adapter/cmmc/*.py" $P/domain/ports.py $P/common/code/parser_adapter_type.py | xargs wc -l | tail -1 # 8 檔、1110這一棒要回答:Word 檔打開前有沒有看大小、解壓後的大小與段落數有沒有上限;逐段比對控制項編號的 regex 會不會被特製文字卡死;模糊比對(rapidfuzz)對超長段落的耗時。
主專案側(cd ~/Projects/Billows/Audit-Manager/compliance-manager-be)
892 domain/oscal/parser/docx_section_extractors.py
555 domain/oscal/parser/docx_parser_core.py ← python-docx Document() 開檔點(:170、:192)
120 domain/oscal/parser/control_id_matcher.py ← rapidfuzz 模糊比對
72 domain/oscal/parser/docx_intermediate.py
19 domain/oscal/parser/framework_patterns.py ← 補入:四組控制項編號 regex
共 5 檔/1,658 行。
驗證:
cd ~/Projects/Billows/Audit-Manager/compliance-manager-be && git ls-files -- domain/oscal/parser/docx_section_extractors.py domain/oscal/parser/docx_parser_core.py domain/oscal/parser/control_id_matcher.py domain/oscal/parser/docx_intermediate.py domain/oscal/parser/framework_patterns.py | xargs wc -l | tail -1 # 5 檔、1658這一棒要回答:Word 解析出來的中介資料轉成 OSCAL 字典時,uuid 與名稱是不是由伺服器產生(還是直接沿用檔案裡寫的);這份字典之後會餵給 V3 的 import_ssp——檔案裡的 uuid 能不能對上別份 SSP 已存在的資料。
主專案側(cd ~/Projects/Billows/Audit-Manager/compliance-manager-be)
860 domain/oscal/adapter/cmmc_ssp_adapter.py
369 domain/oscal/parser/ssp_intermediate.py
78 domain/oscal/adapter/i_ssp_docx_adapter.py
21 domain/oscal/adapter/adapter_registry.py ← 補入:框架代號 → 轉接器對照
共 4 檔/1,328 行。
驗證:
cd ~/Projects/Billows/Audit-Manager/compliance-manager-be && git ls-files -- domain/oscal/adapter/cmmc_ssp_adapter.py domain/oscal/parser/ssp_intermediate.py domain/oscal/adapter/i_ssp_docx_adapter.py domain/oscal/adapter/adapter_registry.py | xargs wc -l | tail -1 # 4 檔、1328① 找「只收編號就改/刪」的方法,逐一追呼叫端:
# 套件服務層:哪些方法把呼叫者給的編號直接交給 repo 改/刪
grep -nE "\.(get_by_id|get_by_uid|update|delete_by_id|delete_by_uid)\(" <檔>
# 主專案與 compliance-audit:誰呼叫它
grep -rn "\.<方法名>(" --include='*.py' ~/Projects/Billows/Audit-Manager/compliance-manager-be/{api,app} ~/Projects/Jedicogy/module/jedi-python-package/jedi-compliance-audit/jedi_compliance_audit套件本身沒有守門是預期的(它沒有網址入口、不知道使用者是誰)。要答的是「呼叫端在把編號交進來之前,有沒有先在正確的父文件底下找到它」。主專案目前已知的正確寫法:ssp_components_app_service.py:138-142 的 _resolve(ssp_id, item_uid)——先列出這份 SSP 的所有元件、比對 uid、找不到就 404,再把找到的 id 交給套件。
② 找「呼叫端傳什麼、套件就寫什麼」:套件多支方法收 <strong>fields,然後逐欄 setattr 寫進物件(例如 remediation_service.py:77-78、poam_service.py:141-142)。如果呼叫端把使用者的整包輸入直接傳進去,使用者就能改到 risk_id、ar_result_id 這種歸屬欄位**,把資料搬到別份文件底下。要確認每個呼叫端傳進 **fields 的鍵是白名單。
V1(SSP 本體)🔴
ssp_service.py:125-344:21 支「收編號就讀/改/刪」的方法(update_ssp,以及元件、資產清單、沿用授權、控制項實作、實作敘述、by-component、人員各一組 get/update/delete)。盤點時追到主專案 25 處呼叫(第 4.2 節),分屬 10 支方法;update_ssp、delete_implemented_requirement、delete_statement、update_by_component、delete_by_component、六支 get_* 在兩個呼叫端 repo 零呼叫者。掃描要確認:①25 處都真的先在同一份 SSP 底下找到那筆(盤點只抽了 ssp_components_app_service.py:138-142,形狀對,其餘待掃);②update_* 傳進來的物件是從資料庫讀出來再改欄位,不是用使用者輸入重新組一個(重新組的話 ssp_id/system_implementation_id 就能被改掉)。ssp_service.py:121-123 list_ssps(query):查詢條件全部選填(query or SspQueryEntity()),不帶條件就回全部客戶的全部 SSP(表沒有隔離)。盤點時找到 6 處呼叫,全部都帶 id=,預期不是洞,確認即可。ssp_clone_service.py:103-245 deep_clone_ssp:複製 12 張子表,每張都用 replace(src, id=None, uid=None, ssp_id=new_ssp_id, ...)。要確認:①所有「指向別張表」的欄位(例如 by-component 的 component_id)都有換成新複製出來的那筆,沒有留著指向舊 SSP 的元件;②來源 SSP 編號(src_ssp_id)是誰給的——主專案呼叫端 project_start_app_service.py(FR-095 H1 已掃)與 jedi-compliance-audit 的開輪流程。oscal_snapshot_service.py:52-96 snapshot_ssp:凍結後把 status 改成 frozen。套件內沒有任何地方檢查「凍結的 SSP 不能改」——ssp_service.update_* 都不看 status。確認主專案有沒有擋(第 7 節)。metadata_clone_service.py:58-129:複製角色/人員/資源時,人員的 email_addresses、telephone_numbers 等個資會一起複製到新的 SSP。確認這是預期行為。V2(稽核計畫、結果、改善計畫)🔴
assessment_risk_service.py:109-125 link_findings(risk_id, finding_ids):逐一把判定編號接到風險上,完全不驗判定屬於哪份稽核結果。底下 ar_finding_risk_repo_impl.py:42-55 link() 也只查 (finding_id, risk_id)。唯一呼叫端是 jedi-compliance-audit 的 assessment_result_app_service.py:792,它傳入的 desired 是從 uid 轉出來的——轉換時有沒有限定在本輪的稽核結果底下,是 FR-116 C3 的範圍;本棒只要確認套件這支「完全信任呼叫端」的形狀,並在報告寫明「兩側要一起看」。assessment_risk_service.py:132-143 get_risk_findings:用 get_by_id(link.finding_id) 撈判定,不驗判定跟風險在同一份結果。如果 link_findings 被塞進別份結果的判定,這支就會把它讀出來。poam_service.py:126-144 update_poam_item(item_id, <strong>fields):只收編號、</strong>fields 全部照寫。主專案與 compliance-audit 零呼叫者,目前打不到;確認後列「零呼叫者但對外公開」。remediation_service.py:49-94 upsert_remediation:用 risk_id 限定範圍再比 uuid,形狀對。但 **fields 逐欄照寫——確認呼叫端(compliance-audit poam_app_service.py:317、assessment_result_app_service.py:661)傳的是白名單。assessment_plan_service.py:113-215 三支 set_*:用 ap_uid 找計畫、再整批刪掉舊的子物件重建。刪除時用 delete_by_id(existing.id),existing 來自 get_by_ap(ap.id),有範圍。確認 ap_uid 由呼叫端驗過歸屬。poam_milestone_repo_impl.py:31-39 get_by_assignee(user_id):只用使用者編號、沒有任何文件範圍,會回「這個人在所有客戶、所有改善計畫裡被指派的里程碑」。盤點時找不到任何呼叫者,列「零呼叫者」。V3(匯入匯出)🔴
import_ssp(:321-430)收的是已經是 Python 字典的 oscal_ssp,本身沒有 json.loads。字典的來源只有兩條:主專案 Word 匯入(ssp_docx_import_app_service.py,上傳限 20MB,:50)與 Excel 匯入(ssp_excel_import_app_service.py,限 10MB,:47),全站請求上限 50MB(config/config.py:105)。所以「從外面丟一份超深的 OSCAL JSON 進來」這條路目前不存在;要看的是:Word/Excel 解析出來的字典裡,陣列長度(例如 5 萬個元件)有沒有上限、import_ssp 逐筆 add() 會不會把資料庫卡住。:356-365):人員用 (metadata_id, type, name)、元件用 (system_implementation_id, title, type)。因為範圍都帶了 metadata_id/system_implementation_id,跨 SSP 覆蓋應該不可能;但人員的 uuid 是照檔案裡寫的直接存(:984 uuid=p.get("uuid") or self._gen_uuid()),oscal 區的 parties.uuid 只有索引、沒有唯一限制。掃描要確認:同一個 uuid 出現在兩份 SSP 時,主專案有沒有任何地方「用 party uuid 找人」而會找到別份 SSP 的那一筆(主專案 ResponsiblePartyEntity 就是用 party_uuid 串起來的,見 CLAUDE.md「OSCAL Parties」段)。clear_ssp_body(:281-319):刪掉整份 SSP 的內容。參數是 SSP 數字編號,由呼叫端決定。確認兩個呼叫端(ssp_excel_import_app_service.py:478、ssp_docx_import_app_service.py:384)的編號都來自已驗權的範本。export_oscal(:229-251)與四個 _export_*:用 uid 撈根文件,然後所有子物件都用根文件的數字編號往下撈,範圍是對的。入口 oscal_export_app_service.py 只給了目錄匯出(api/oscal/routes/framework/oscal_framework_version_route.py,短效憑證綁版本 uid,FR-113 O8b 已看過);SSP/稽核結果/改善計畫的 JSON 匯出在主專案目前沒有網址入口,列「有能力、沒入口」。@transaction 包住,所以整份匯入是一個交易,中途出錯會整份撤回。確認兩個呼叫端都沒有在 import_ssp 中間自己 commit。raise ValueError(f"... id={...!r}"),訊息帶內部數字編號。確認主專案有沒有把 str(e) 回給前端(ssp_excel_import_app_service.py:621 msg = str(e) 要看去向)。V4a(目錄、基準線、框架)
oscal_clone_service.py:90-315 目錄樹與基準線複製:群組、控制項、部分、參數四層,每層複製後用 get_by_id(new_id) 讀回、改 parent_id 再 update。確認「舊編號 → 新編號」的對照表有涵蓋所有層,沒有子物件的 parent_id 還指向來源目錄的群組。framework_service.py:61-120:delete_framework(uid)、delete_version(uid)、publish_version(uid) 只收 uid。框架是全平台共用資源(主專案 framework_app_service.py:202 有 require_platform_admin()),確認所有呼叫端都有這道檢查(FR-113 O8b 掃過呼叫端,本棒確認套件側沒有第二個入口)。profile_resolution_service.py:基準線「從哪份目錄挑控制項」的來源欄位 source_catalog_id。確認這個欄位只由伺服器端的複製流程寫入,沒有從使用者輸入寫入的路徑。catalog_service.py:161-163 list_catalogs()、framework_service.py:50-54 list_frameworks():不帶條件=回全部。目錄與框架本來就是全平台共用,預期不是洞。V4b(CMMC PDF/Excel 解析器)
cmmc2_lv2_parser_adapter.py:232-268 pdfplumber.open(pdf_stream) 後逐頁讀文字與字型資訊。頁碼範圍由 base_parser_adapter.py:37-58 _resolve_pages 算,有夾在總頁數之內,但沒有「總頁數上限」。確認主專案 framework_parse_job_route.py:81-83 只量了檔案大小、沒有擋大小(只記錄),唯一的上限是全站 50MB。一份 50MB、上萬頁的 PDF 會解析多久要實測或估算。try/except,例外直接往上丟;主專案 framework_parse_job_service.py:226-228 寫成 f"{error_message}: {e}"——這就是總表第 132 項,已知、不重報,本棒只確認套件丟出來的例外內容(pdfplumber/pandas 的例外會不會帶暫存檔路徑)。excel_parser(:450-472)走 pandas.read_excel:openpyxl 在主專案環境會自動用 defusedxml 防 XML 炸彈(第 6 節);但套件自己的 pyproject.toml 沒有宣告 defusedxml,換個主機環境防護就會消失。parser_adapter_type.py:23-27 宣告了 YAML、JSON 兩種匯入類型,全套件沒有任何一處實作;pyproject.toml 也宣告了 pyyaml 依賴卻零 import。確認不是「預留入口、某天被接上 yaml.load」的地雷(第 7 節)。W1(Word 內容抽取器)
docx_parser_core.py:170、:192 Document(file_path):python-docx 打開 Word 檔(Word 檔本質是 zip 壓縮檔裝一堆 XML)。python-docx 的 XML 解析器已設 resolve_entities=False(不解析外部實體),XML 外部實體攻擊不成立;但解壓縮沒有大小上限——一個 20MB 的 Word 檔解壓後可以是好幾 GB(壓縮炸彈)。確認主專案有沒有在打開前看 zip 內各檔的解壓大小。framework_patterns.py:四組 regex 都是固定長度的數字與字母組合,盤點時目測沒有「巢狀重複」這種會卡死的寫法(例如 (a+)+),但 iso27001 那組 A\.\d+(\.\d+)* 有一層重複,請研究員用超長 A.1.1.1.1… 字串實測。control_id_matcher.py:50-120 fuzzy_match_control:每一段文字都拿去跟所有候選控制項做模糊比對,耗時是「段落數 × 控制項數 × 文字長度」。CMMC L2 有 110 項,一份幾千段的 Word 可能要跑很久。確認有沒有段落長度或段落數上限。docx_section_extractors.py 892 行:逐表格、逐儲存格讀文字。確認合併儲存格、巢狀表格這種特殊結構不會讓迴圈爆掉。W2(CMMC 轉接器與中介格式)
cmmc_ssp_adapter.py 把中介格式轉成 import_ssp 吃的 OSCAL 字典。🔴 要確認字典裡的 uuid(人員、元件、實作項目)是轉接器自己產生,還是沿用 Word 內容——如果 Word 裡能寫 uuid 而轉接器照抄,就接上 V3 講的「同一個 party uuid 出現在兩份 SSP」。component-uuid 決定 by-component 掛在哪個元件底下(V3 :1342-1397 用 comp_map 查,查不到就跳過,範圍對)。確認轉接器不會輸出「別份 SSP 的元件 uuid」。adapter_registry.py:框架代號 → 轉接器。代號來自使用者選的框架,確認查不到時是回錯誤而不是落到某個預設轉接器。「範圍條件」=除了編號之外,查詢有沒有再加上「而且要屬於某份文件」。
盤點時逐支讀過 45 支 *_repo_impl.py。自己寫的方法共 41 個,每一個的查詢條件都有上一層的編號:
| 文件類型 | 方法(查詢條件) |
|---|---|
| SSP(13 支 repo) | get_by_ssp(ssp_id) ×3、get_by_system_implementation(system_implementation_id) ×4、get_by_system_characteristics(system_characteristics_id) ×2、get_by_control_implementation(control_implementation_id)、get_by_implemented_requirement(implemented_requirement_id) ×2、get_by_statement(statement_id) |
| AP(8 支) | get_by_ap(assessment_plan_id) ×5、get_included(assessment_plan_id)、get_roots(assessment_plan_id)、get_children(parent_id)、get_by_task(task_id) ×2、delete_by_task(task_id) ×2 |
| AR(8 支) | get_by_ar_result(ar_result_id) ×4、get_by_target(ar_result_id, …)、get_by_assessment_result(assessment_result_id)、get_by_risk(risk_id) ×2、get_by_finding(finding_id)、link/unlink(finding_id, risk_id) |
| POA&M(3 支) | get_by_poam(poam_id)、get_by_remediation(remediation_id)、🟡 get_by_assignee(user_id)、get_by_uid(uid) |
| 目錄、基準線(5 支) | get_by_catalog(catalog_id)、get_roots(catalog_id)、get_children(parent_id)、get_enhancements(parent_id)、get_by_control(catalog_control_id)、list_aos(catalog_control_id)、get_by_profile(profile_id) |
| 框架、metadata(6 支) | 無自訂方法 |
例外兩支:
PoamMilestoneRepoImpl.get_by_assignee(user_id)(poam_milestone_repo_impl.py:31-39):只用使用者編號、跨所有文件。零呼叫者。PoamRepoImpl.get_by_uid(uid)(poam_repo_impl.py:21-28):只用 uid,是根文件的查法,唯一呼叫者是 V3 的 _export_poam,而那條匯出目前沒有網址入口。每一支 repo 都繼承 jedi_common 的 BaseRepositoryImpl,自動多出這些方法(jedi-common/jedi_common/session/database/repository/base_repository_impl.py):
| 方法 | 位置 | 查詢條件 |
|---|---|---|
get_by_id/get_by_uid |
:356-383 |
只有編號 |
update(entity) |
:406-427 |
只有 entity.uid 或 entity.id |
delete_by_id/delete_by_uid |
:429-443 |
只有編號 |
delete_by_ids/delete_by_uids |
:445-453 |
只有編號清單 |
get_all_by_fields(query) |
:286 |
查詢條件的非空欄位;全空=整張表 |
這是全專案共用的基底、不是本套件的錯,本身不是發現。重點是本套件的服務層把這些方法原封不動開放出去:
| 套件服務方法 | 做什麼 | 主專案/compliance-audit 呼叫端 | 呼叫端有沒有先找父層 |
|---|---|---|---|
SspService.update_component/delete_component |
改/刪元件 | ssp_components_app_service.py:123,135、ssp_resources_context_service.py:106,111、module_frame_components_service.py:115,125 |
✅ 抽查第一支有 _resolve(ssp_id, uid);其餘 V1 確認 |
update_inventory_item/delete_inventory_item |
改/刪資產清單 | ssp_inventory_items_app_service.py:121,130、module_frame_inventory_service.py:116,125 |
V1 確認 |
update_leveraged_authorization/delete_… |
改/刪沿用授權 | ssp_leveraged_app_service.py:125,134、module_frame_leveraged_service.py:127,134 |
V1 確認 |
update_party/delete_party |
改/刪人員 | ssp_party_app_service.py:108,118、module_frame_party_service.py:132,141 |
V1 確認 |
update_implemented_requirement |
改控制項實作 | ssp_control_implementation_service.py:90、module_frame_control_default_service.py:209,223 |
V1 確認 |
update_statement |
改實作敘述 | ssp_control_implementation_service.py:860,878、module_frame_control_objective_default_service.py:223,240 |
V1 確認 |
update_ssp、delete_implemented_requirement、delete_statement、update_by_component、delete_by_component、get_component/get_inventory_item/get_leveraged_authorization/get_implemented_requirement/get_statement/get_party |
零呼叫者 | — | |
AssessmentRiskService.update_risk/delete_risk |
改/刪風險 | compliance-audit assessment_result_app_service.py:747,765 |
FR-116 C3 範圍 |
🔴 AssessmentRiskService.link_findings |
判定接到風險 | compliance-audit assessment_result_app_service.py:792 |
FR-116 C3 範圍;套件側完全不驗 |
AssessmentRiskService.get_risk_findings |
讀風險的判定 | 零呼叫者 | 跟著 link_findings |
PoamService.get_poam(poam_id) |
讀改善計畫 | compliance-audit(FR-116 C3) | |
🔴 PoamService.update_poam_item(item_id, <strong>fields) |
改改善計畫條目 | 零呼叫者** | — |
FrameworkService.delete_framework/delete_version/publish_version/update_* |
框架增刪改 | framework_app_service.py 等(FR-113 O8b 已掃) |
平台管理員檢查 |
OscalIoService.clear_ssp_body(ssp_id) |
清空整份 SSP 內容 | ssp_excel_import_app_service.py:478、ssp_docx_import_app_service.py:384 |
V3 確認 |
各服務 get_ssp/get_ap/get_ar/get_catalog/get_profile/get_framework(by uid) |
讀根文件 | 多處 | 根文件本來就只能用 uid 找,由呼叫端決定能不能看 |
標紅統計:
ssp_service 21、assessment_risk_service 4、poam_service 2、framework_service 5、oscal_io_service.clear_ssp_body 1)。其中有活呼叫端的 20 支要逐一確認呼叫端先找父層;零呼叫者 13 支列第 7 節清理建議。另外 remediation_service.upsert_remediation 有範圍,但 **fields 全開(第 3 節②)。link_findings(套件完全不驗、要跟 FR-116 C3 兩側對看)、🔴 update_poam_item(零呼叫者但 **fields 全開)。對照來源:①出貨版 scripts/init/02-schema.sql(靜態快照);②DEV 實查(唯讀 SELECT,2026-09-24 12:49,cm_app 帳號)。
| 群組 | 表 | 客戶欄位 | 出貨 02-schema | DEV |
|---|---|---|---|---|
| 共用 | metadata、parties、roles、resources |
無 | 關 | 關 |
| 框架 | frameworks、framework_versions |
無 | 關 | 關 |
| 目錄 | catalogs、catalog_groups、catalog_controls、catalog_control_parts、catalog_control_params |
無 | 關 | 關 |
| 基準線 | profiles、profile_imports |
無 | 關 | 關 |
| 🔴 SSP | system_security_plans、ssp_system_characteristics、ssp_information_types、ssp_diagrams、ssp_system_implementations、ssp_system_users、ssp_components、ssp_inventory_items、ssp_leveraged_authorizations、ssp_control_implementations、ssp_implemented_requirements、ssp_statements、ssp_by_components |
無 | 關 | 關 |
| 🔴 稽核計畫 | assessment_plans、ap_reviewed_controls、ap_assessment_subjects、ap_assessment_activities、ap_local_objectives、ap_tasks、ap_task_subjects、ap_task_participants |
無 | 關 | 關 |
| 🔴 稽核結果 | assessment_results、ar_results、ar_assessment_subjects、assessment_observations、assessment_findings、assessment_risks、assessment_finding_risks、assessment_remediations |
無 | 關 | 關 |
| 🔴 改善計畫 | poams、poam_items、poam_milestones |
無 | 關 | 關 |
45 張:客戶欄位 0 張、隔離開啟 0 張、規則 0 條。
DEV 的 oscal 區共 52 張表,開了隔離的只有 5 張,全部是主專案的解析工作紀錄表,而且只有這 5 張有客戶欄位:
| 表 | 客戶欄位 | 隔離 |
|---|---|---|
framework_parse_jobs、ap_docx_parse_jobs、ar_xlsx_parse_jobs、ssp_docx_parse_jobs、ssp_excel_parse_jobs |
有 | 開 |
ssp_reference_documents、ssp_reference_document_mappings(compliance-audit 的程序書,FR-116 已列) |
無 | 關 |
clone_catalog_tree),複製出來的那份跟公版在同一張表、同樣沒有客戶欄位——無法從資料庫區分哪份是公版、哪份是某客戶的副本。結論:OSCAL XML 格式未支援,套件層 XML 攻擊面不存在。交接文件寫的「XML 解析攻擊」在本套件不成立。
查證(2026-09-24,jedi monorepo eafc7ae5):
cd ~/Projects/Jedicogy/module/jedi-python-package/jedi-oscal-v2
grep -rnE "import (xml|lxml|defusedxml|yaml)|from (xml|lxml|defusedxml|yaml)|etree|ElementTree|minidom|sax" jedi_oscal_v2 --include='*.py'
# → 0 筆
grep -rniE "xml|yaml" jedi_oscal_v2 | grep -v Binary
# → 只有 common/code/parser_adapter_type.py:26 YAML = "YAML"(一個沒有實作的常數)| 項目 | 結果 |
|---|---|
| 套件正式碼 import XML 解析器 | 0 支(xml、lxml、defusedxml、ElementTree、minidom、sax 全無) |
| 套件正式碼 import YAML | 0 支。但 pyproject.toml 宣告了 pyyaml 依賴(沒用到的依賴) |
oscal_io_service.py 支援的格式 |
匯出只有 JSON(:239-240,其他格式直接 raise ValueError);匯入收的是 Python 字典,不解析任何文字格式 |
parser_adapter_type.py 的 ImportType |
宣告了 PDF/EXCEL/YAML/JSON,只有 PDF、EXCEL 有實作(catalog_service.py:118-155) |
真正會碰到 XML 的地方(都不在套件、而且都已有防護):
| 在哪 | 用什麼讀 | XML 防護 | 還剩什麼風險 |
|---|---|---|---|
主專案 Word 匯入(W1 docx_parser_core.py:170,192) |
python-docx → lxml | python-docx 建解析器時設 resolve_entities=False(.venv/.../docx/oxml/parser.py:19),外部實體不會被解析 |
壓縮炸彈(Word 是 zip,解壓沒上限)、段落數與模糊比對耗時 |
套件 CMMC Excel 匯入(V4b excel_parser) |
pandas → openpyxl | openpyxl 偵測到 defusedxml 就自動使用(openpyxl/xml/__init__.py:29-42);主專案 pyproject.toml:93 有宣告 defusedxml>=0.7.1 |
套件自己沒宣告 defusedxml,防護依賴主機環境;壓縮炸彈 |
| 套件 CMMC PDF 匯入(V4b) | pdfplumber(pdfminer) | 不是 XML | 頁數沒上限、惡意 PDF 物件結構 |
掃描重點因此換成:①V3——Word/Excel 解析出的字典,陣列長度、字串長度有沒有上限;②W1/V4b——Word/Excel 的 zip 壓縮炸彈、PDF 頁數;③沒有任何地方把 JSON 當輸入的「深度炸彈」,因為目前沒有 OSCAL JSON 匯入入口(主專案只有三處 json.loads,都是解析表單裡的小欄位:ssp_excel_import_route.py:48、ssp_scoped_excel_import_route.py:55、framework_parse_job_route.py:74,受全站 50MB 上限約束)。
app/flow_control/service/assessment_plan_app_service.py 沒被任何一棒掃過:它呼叫本套件的 generate_draft(:313)與 clone_metadata(:354,覆核子輪複製稽核計畫用)。FR-113 十棒、FR-116 盤點的主專案清單都沒有它。它不是 Word 解析器,本批不收。要歸 FR-116(稽核計畫相關)還是另開,由首腦決定。update_poam_item(**fields 全開)、get_risk_findings、ssp_service 的 update_ssp/delete_implemented_requirement/delete_statement/update_by_component/delete_by_component 與六支 get_*;另加 repo 層的 get_by_assignee(跨全部客戶)。現在打不到,哪天有人接上網址就是現成的「只認編號」。建議併入 FR-092(死碼清理)後續,或在套件裡改成要求父層編號的版本——這是清理建議,不是資安發現。pyyaml 依賴與 ImportType.YAML/JSON 常數,但零實作。如果未來要做 YAML 匯入,必須用 yaml.safe_load(yaml.load 可以執行任意程式)。建議拿掉 pyyaml 依賴,或在常數旁寫明這個陷阱。defusedxml:Excel 解析的 XML 防護目前靠主專案環境剛好有裝。要不要在套件 pyproject.toml 補宣告,由首腦決定(改套件要走發版流程)。snapshot_ssp 把 status 設成 frozen,但 ssp_service.update_* 都不看 status。主專案有沒有擋要等 V1 確認;如果沒有,這是稽核證據可被事後竄改的問題,嚴重度會比一般的越權高。link_findings 要跟 FR-116 C3 對看,C3 已派)→ V1 → V3 → W2 → W1 → V4b → V4a。V4b、W1、W2 建議同一個 runner 連做(第 2 節)。驗證指令(套件側):
cd ~/Projects/Jedicogy/module/jedi-python-package/jedi-oscal-v2/jedi_oscal_v2
# entity:0 個方法、0 個 if/for
grep -rnE " def |^\s+(if|for)\b" domain/entity | wc -l # → 0
# model:0 個方法、驗證器、事件監聽
grep -rnE "def |@validates|event\.listen|hybrid|column_property" infra/model | wc -l # → 0
# mapper:除了「model 是 None 就回 None」之外沒有任何 if/for/try
grep -rnE "^\s+(if|for|while|try)\b" infra/mapper | grep -v "is None" | wc -l # → 0
grep -rn "json\|loads" infra/mapper | wc -l # → 0
# repository 介面:抽象宣告,沒有實作
grep -rnE "^\s+(if|for)\b" domain/repository | wc -l # → 0| 目錄 | 檔數 | 行數 | 為什麼不掃 |
|---|---|---|---|
domain/entity/** |
99 | 2,195 | @dataclass 純欄位宣告,沒有方法、沒有判斷 |
infra/model/** |
54 | 2,921 | SQLAlchemy 欄位與索引宣告;沒有 @validates、沒有事件監聽、沒有計算欄位 |
infra/mapper/<strong> |
54 | 2,671 | to_entity/to_model 一欄一欄搬;唯一判斷是「傳入 None 回 None」;JSON 欄位由 SQLAlchemy JSONB 型別直接轉成 Python 物件,mapper 沒有自己做 json.loads 或遞迴組裝**,所以不算判斷邏輯 |
domain/repository/** |
54 | 802 | 抽象介面(含 9 支空 __init__.py) |
common/** 常數 |
2 | 103 | 錯誤碼與列舉常數 |
jedi_oscal_v2/__init__.py |
1 | 30 | 只有 docstring |
其他 __init__.py |
30 | 13 | |
| 合計 | 294 | 8,735 |
⚠️ 例外提醒:infra/model/base/oscal_party.py 的 uuid 欄位只有索引、沒有唯一限制(:17、:30)。這不是判斷邏輯、本身不掃,但它是 V3「同一個 party uuid 出現在兩份 SSP」那條疑點的根據,V3 研究員要知道。
framework_patterns.py 19 行、adapter_registry.py 21 行)。*_repo_impl.py 45 支,另 9 支是 __init__.py。json.loads:卡片寫 io service「import json」並要求查「json.loads 之前有沒有看大小」→ 查證 io service 只用 json.dumps(匯出),匯入收的是字典、沒有 json.loads。大小防線在主專案上傳層(Word 20MB/Excel 10MB/全站 50MB)。ssp_reference_document_repo_impl.py:確認屬 jedi-compliance-audit,不在本套件。