feat(security): inventory runtime supply chain

This commit is contained in:
ogt
2026-07-14 14:34:38 +08:00
parent d4bc3cb453
commit ca8771a210
4 changed files with 560 additions and 5 deletions

View File

@@ -364,6 +364,10 @@ def _build_security_program_domains(
ai_stage_percent: int,
ai_evidence_count: int,
audit_evidence_count: int,
package_digest_pinned_percent: int,
github_supply_chain_compliant_percent: int,
forbidden_github_supply_chain_asset_count: int,
package_digest_unpinned_count: int,
) -> list[dict[str, Any]]:
scope_by_id = {row["scope_id"]: row for row in asset_scopes}
function_by_id = {row["function_id"]: row for row in control_functions}
@@ -387,6 +391,13 @@ def _build_security_program_domains(
def average(*values: int) -> int:
return round(sum(values) / len(values)) if values else 0
if forbidden_github_supply_chain_asset_count > 0:
supply_chain_gap_code = "forbidden_github_supply_chain_source_detected"
elif package_digest_unpinned_count > 0:
supply_chain_gap_code = "runtime_image_digest_pin_missing"
else:
supply_chain_gap_code = "sbom_signature_dependency_evidence_missing"
domain_values = (
(
"governance_risk",
@@ -473,9 +484,11 @@ def _build_security_program_domains(
average(
scope_percent("supply_chain"),
compliance_percent("cve_scan"),
package_digest_pinned_percent,
github_supply_chain_compliant_percent,
),
scope_assets("supply_chain") + compliance_total("cve_scan"),
"sbom_signature_dependency_evidence_missing",
supply_chain_gap_code,
),
(
"telemetry_detection",
@@ -654,6 +667,14 @@ def build_iwooos_security_asset_control_plane(
stale_total = sum(
int(_value(row, "stale_count", 0) or 0) for row in inventory_source
)
forbidden_github_supply_chain_asset_count = sum(
int(_value(row, "forbidden_github_source_count", 0) or 0)
for row in inventory_source
)
package_digest_unpinned_count = sum(
int(_value(row, "unpinned_package_count", 0) or 0)
for row in inventory_source
)
present_types = {
asset_type for asset_type in _ASSET_TYPES if by_type.get(asset_type, 0) > 0
}
@@ -849,6 +870,22 @@ def build_iwooos_security_asset_control_plane(
+ audit["mcp_tool_audit_count_24h"]
+ audit["automation_operation_count_24h"]
),
package_digest_pinned_percent=_percent(
max(0, by_type.get("package", 0) - package_digest_unpinned_count),
by_type.get("package", 0),
),
github_supply_chain_compliant_percent=_percent(
max(
0,
by_type.get("package", 0)
- forbidden_github_supply_chain_asset_count,
),
by_type.get("package", 0),
),
forbidden_github_supply_chain_asset_count=(
forbidden_github_supply_chain_asset_count
),
package_digest_unpinned_count=package_digest_unpinned_count,
)
work_items: list[dict[str, Any]] = []
@@ -886,6 +923,24 @@ def build_iwooos_security_asset_control_plane(
"依 scope connector 接入網站、資料、供應鏈、安全工具與 AI 資產。",
)
)
supply_chain_policy_gap_count = (
forbidden_github_supply_chain_asset_count
+ package_digest_unpinned_count
)
if supply_chain_policy_gap_count > 0:
work_items.append(
_work_item(
"AIA-P0-006-02A",
"P0",
"supply_chain_policy",
"封閉 runtime image 供應鏈",
supply_chain_policy_gap_count,
(
"以內部 registry 或 repo-owned immutable artifact 替換禁用來源,"
"補 digest pin、canary rollout 與獨立 verifier不得連線 GitHub。"
),
)
)
if coverage_total == 0 or coverage_unknown > 0:
work_items.append(
_work_item(
@@ -975,6 +1030,7 @@ def build_iwooos_security_asset_control_plane(
sum(domain["controlled_percent"] for domain in security_program_domains)
/ len(security_program_domains)
)
package_inventory_evidenced = by_type.get("package", 0) > 0
strict_runtime_closure_percent = 100 if same_run_closed_loop_proven else 0
completion_dimensions = [
{
@@ -1034,6 +1090,10 @@ def build_iwooos_security_asset_control_plane(
"stale_asset_count": stale_total,
"orphan_asset_count": orphan_asset_count,
"relationship_count": relationship_count,
"package_digest_unpinned_count": package_digest_unpinned_count,
"forbidden_github_supply_chain_asset_count": (
forbidden_github_supply_chain_asset_count
),
"automation_coverage_green_percent": _percent(
coverage_green, coverage_total
),
@@ -1092,6 +1152,30 @@ def build_iwooos_security_asset_control_plane(
"same_run_closed_loop_count": 0,
},
"security_audit": audit,
"supply_chain": {
"package_asset_count": by_type.get("package", 0),
"package_inventory_evidenced": package_inventory_evidenced,
"package_digest_pinned_count": max(
0,
by_type.get("package", 0) - package_digest_unpinned_count,
),
"package_digest_unpinned_count": package_digest_unpinned_count,
"forbidden_github_supply_chain_asset_count": (
forbidden_github_supply_chain_asset_count
),
"github_freeze_compliant": (
package_inventory_evidenced
and forbidden_github_supply_chain_asset_count == 0
),
"runtime_image_policy_compliant": (
package_inventory_evidenced
and forbidden_github_supply_chain_asset_count == 0
and package_digest_unpinned_count == 0
),
"raw_image_identity_returned": False,
"registry_api_called": False,
"github_api_used": False,
},
"source_health": source_health_rows,
"work_items": work_items,
"boundaries": {
@@ -1131,6 +1215,8 @@ def build_unavailable_iwooos_security_asset_control_plane(
"stale_asset_count": 0,
"orphan_asset_count": 0,
"relationship_count": 0,
"package_digest_unpinned_count": 0,
"forbidden_github_supply_chain_asset_count": 0,
"automation_coverage_green_percent": 0,
"automation_coverage_unknown_count": 0,
"compliance_violation_count": 0,
@@ -1282,6 +1368,18 @@ def build_unavailable_iwooos_security_asset_control_plane(
"accepted_ai_trace_count_24h": 0,
"immutable_external_audit_store_evidenced": False,
},
"supply_chain": {
"package_asset_count": 0,
"package_inventory_evidenced": False,
"package_digest_pinned_count": 0,
"package_digest_unpinned_count": 0,
"forbidden_github_supply_chain_asset_count": 0,
"github_freeze_compliant": False,
"runtime_image_policy_compliant": False,
"raw_image_identity_returned": False,
"registry_api_called": False,
"github_api_used": False,
},
"source_health": [
{
"source_id": source_id,
@@ -1489,7 +1587,21 @@ class IwoooSSecurityAssetControlPlaneService:
) AS unowned_count,
count(*) FILTER (
WHERE last_seen_at < NOW() - INTERVAL '2 hours'
) AS stale_count
) AS stale_count,
count(*) FILTER (
WHERE asset_type = 'package'
AND COALESCE(
metadata->>'forbidden_source_domain_detected',
''
) <> ''
) AS forbidden_github_source_count,
count(*) FILTER (
WHERE asset_type = 'package'
AND COALESCE(
metadata->>'digest_pinned',
'false'
) <> 'true'
) AS unpinned_package_count
FROM asset_inventory
WHERE lifecycle_state = 'active'
GROUP BY asset_type