feat(api): validate gitea private inventory payloads
Some checks failed
CD Pipeline / workflow-shape (push) Successful in 0s
CD Pipeline / cancel-stale-cd (push) Has been skipped
CD Pipeline / tests (push) Failing after 2m46s
CD Pipeline / build-and-deploy (push) Has been skipped
CD Pipeline / post-deploy-checks (push) Has been skipped

This commit is contained in:
Your Name
2026-06-29 19:09:27 +08:00
parent de0647f911
commit 7b31e68a7b
4 changed files with 536 additions and 0 deletions

View File

@@ -341,6 +341,9 @@ from src.services.docker_build_surface_inventory import (
from src.services.gitea_private_inventory_p0_scorecard import (
load_latest_gitea_private_inventory_p0_scorecard,
)
from src.services.gitea_authenticated_inventory_payload_validation import (
validate_gitea_authenticated_inventory_payload,
)
from src.services.gitea_workflow_runner_health import (
load_latest_gitea_workflow_runner_health,
)
@@ -1073,6 +1076,42 @@ async def get_gitea_private_inventory_p0_scorecard() -> dict[str, Any]:
) from exc
@router.post(
"/gitea-private-inventory-p0-scorecard/validate-authenticated-inventory-payload",
response_model=dict[str, Any],
summary="驗證 P0-003 Gitea authenticated/admin inventory 脫敏 payload",
description=(
"針對單次 owner-provided redacted Gitea authenticated/admin inventory "
"payload 進行 no-persist validation此端點只回傳 private inventory "
"reviewer readiness / needs supplement / quarantined / rejected execution "
"分流,不保存 payload、不呼叫 Gitea/GitHub、不收 token value、"
"不建立 repo、不改 visibility、不同步 refs、不觸發 workflow、"
"不讀 secret、不讀 raw session/SQLite。"
),
)
async def validate_gitea_authenticated_inventory_payload_packet(
inventory_payload: dict[str, Any],
) -> dict[str, Any]:
"""Return no-persist validation for one P0-003 redacted inventory payload."""
try:
payload = await asyncio.to_thread(
validate_gitea_authenticated_inventory_payload,
inventory_payload,
)
return redact_public_lan_topology(payload)
except FileNotFoundError as exc:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=str(exc),
) from exc
except (json.JSONDecodeError, ValueError) as exc:
logger.error("gitea_authenticated_inventory_payload_invalid", error=str(exc))
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="P0-003 Gitea authenticated inventory payload 驗證器無效",
) from exc
@router.get(
"/github-target-private-backup-evidence-gate",
response_model=dict[str, Any],

View File

@@ -0,0 +1,328 @@
"""P0-003 Gitea authenticated/admin inventory payload validation.
This service validates one redacted Gitea inventory payload for reviewer
readiness. It never calls Gitea or GitHub, never stores token values, and never
writes repos, refs, secrets, workflow state, or runtime state.
"""
from __future__ import annotations
import re
from typing import Any
from urllib.parse import parse_qsl, urlsplit
from src.services.gitea_private_inventory_p0_scorecard import (
load_latest_gitea_private_inventory_p0_scorecard,
)
_SCHEMA_VERSION = "gitea_authenticated_inventory_payload_validation_v1"
_PAYLOAD_SCHEMA_VERSION = "gitea_repo_inventory_v1"
_ACCEPTED_VISIBILITY_SCOPES = {"authenticated", "admin_export"}
_REQUIRED_ATTESTATIONS = {
"no_token_value",
"no_write_token",
"no_webhook_secret",
"no_deploy_key_private_key",
"no_runner_registration_token",
"no_cookie_or_session",
"no_gitea_db_dump",
"no_git_object_pack",
}
_FORBIDDEN_TRUE_FIELDS = {
"repo_write_allowed",
"refs_sync_allowed",
"github_primary_switch_authorized",
"runtime_execution_authorized",
"write_to_gitea",
"create_gitea_repo",
"delete_or_archive_gitea_repo",
"sync_git_refs",
"force_push",
}
_SECRET_PATTERNS = {
"authorization_header": re.compile(r"Authorization\s*:", re.IGNORECASE),
"bearer_token": re.compile(r"Bearer\s+[A-Za-z0-9._~+/=-]{12,}", re.IGNORECASE),
"cookie_header": re.compile(r"\bCookie\s*:", re.IGNORECASE),
"password_assignment": re.compile(r"\bpassword\s*[:=]\s*[^,\s]+", re.IGNORECASE),
"private_key": re.compile(r"-----BEGIN [A-Z ]*PRIVATE KEY-----"),
"token_assignment": re.compile(r"\btoken\s*[:=]\s*[^,\s]+", re.IGNORECASE),
}
_SECRET_QUERY_KEYS = {"access_token", "auth", "key", "password", "secret", "token"}
_BLOCKERS_CLEARED_BY_ACCEPTED_PAYLOAD = {
"gitea_repo_inventory_status_not_ok",
"gitea_visibility_scope_public_only_or_unknown",
"gitea_authenticated_inventory_payload_not_accepted",
}
def validate_gitea_authenticated_inventory_payload(
inventory_payload: dict[str, Any],
scorecard: dict[str, Any] | None = None,
) -> dict[str, Any]:
"""Validate one redacted P0-003 inventory payload without persisting it."""
current = scorecard or load_latest_gitea_private_inventory_p0_scorecard()
blockers: list[str] = []
sensitive_hits = _find_sensitive_strings(inventory_payload)
forbidden_true_fields = _find_forbidden_true_fields(inventory_payload)
if inventory_payload.get("schema_version") != _PAYLOAD_SCHEMA_VERSION:
blockers.append(f"schema_version_not_{_PAYLOAD_SCHEMA_VERSION}")
if inventory_payload.get("status") != "ok":
blockers.append("status_not_ok")
visibility_scope = str(inventory_payload.get("visibility_scope") or "")
if visibility_scope not in _ACCEPTED_VISIBILITY_SCOPES:
blockers.append("visibility_scope_not_authenticated_or_admin_export")
repos = [
repo for repo in _as_list(inventory_payload.get("repos")) if isinstance(repo, dict)
]
repo_count = _as_int(inventory_payload.get("repo_count"))
if repo_count != len(repos):
blockers.append("repo_count_mismatch")
if repo_count < 4:
blockers.append("repo_count_below_current_public_floor")
blockers.extend(_validate_repos(repos))
if _is_placeholder(inventory_payload.get("coverage_gap_explanation")):
blockers.append("coverage_gap_explanation_missing")
blockers.extend(
_validate_redaction_attestation(inventory_payload.get("redaction_attestation"))
)
if forbidden_true_fields:
status = "rejected_execution_request"
elif sensitive_hits:
status = "quarantined_sensitive_payload"
elif blockers:
status = "needs_supplement"
else:
status = "accepted_for_private_inventory_review_only"
accepted = status == "accepted_for_private_inventory_review_only"
current_rollups = _as_dict(current.get("rollups"))
current_blockers = _strings(current.get("active_blockers"))
projected_blockers = (
[
blocker
for blocker in current_blockers
if blocker not in _BLOCKERS_CLEARED_BY_ACCEPTED_PAYLOAD
]
if accepted
else current_blockers
)
current_accepted_payload_count = _as_int(
current_rollups.get("accepted_inventory_payload_count")
)
projected_accepted_payload_count = (
max(current_accepted_payload_count, 1) if accepted else current_accepted_payload_count
)
owner_attestation_accepted_count = _as_int(
current_rollups.get("owner_coverage_attestation_accepted_count")
)
return {
"schema_version": _SCHEMA_VERSION,
"status": status,
"priority": "P0-003",
"scope": "gitea_authenticated_inventory_payload_validation",
"source_scorecard_status": current.get("status"),
"result": {
"accepted_payload_count": 1 if accepted else 0,
"repo_count": repo_count,
"visible_repo_count": len(repos),
"blocker_count": len(blockers),
"sensitive_payload_hit_count": len(sensitive_hits),
"forbidden_true_field_count": len(forbidden_true_fields),
"current_active_blocker_count": len(current_blockers),
"projected_active_blocker_count": len(projected_blockers),
"current_accepted_inventory_payload_count": current_accepted_payload_count,
"projected_accepted_inventory_payload_count": projected_accepted_payload_count,
"projected_gitea_repo_inventory_status": (
"ok" if accepted else current_rollups.get("gitea_repo_inventory_status")
),
"projected_gitea_visibility_scope": (
visibility_scope
if accepted
else current_rollups.get("gitea_visibility_scope")
),
"owner_coverage_attestation_still_required_count": (
0 if owner_attestation_accepted_count > 0 else 1
),
"token_value_collection_allowed": False,
"repo_write_allowed": False,
"refs_sync_allowed": False,
"github_primary_switch_authorized": False,
"runtime_gate_count": 0,
},
"blockers": blockers,
"sensitive_payload_hits": sensitive_hits,
"forbidden_true_fields": forbidden_true_fields,
"operation_boundaries": {
"payload_persisted": False,
"gitea_api_called": False,
"gitea_write_performed": False,
"repo_write_performed": False,
"refs_sync_performed": False,
"github_api_used": False,
"github_cli_used": False,
"secret_plaintext_read": False,
"token_value_collection_allowed": False,
"runtime_action_performed": False,
"raw_session_or_sqlite_read_performed": False,
},
"reviewer_readiness": {
"schema_version": "gitea_private_inventory_reviewer_readiness_v1",
"status": (
"ready_for_owner_coverage_attestation_review"
if accepted
else "not_ready_for_owner_coverage_attestation_review"
),
"accepted_repo_count": repo_count if accepted else 0,
"projected_active_blocker_count": len(projected_blockers),
"projected_remaining_blockers": projected_blockers,
"redacted_inventory_receipt_writeback_ready_count": 1 if accepted else 0,
"owner_coverage_attestation_still_required_count": (
0 if owner_attestation_accepted_count > 0 else 1
),
"repo_write_authorized_count": 0,
"refs_sync_authorized_count": 0,
"github_primary_switch_authorized_count": 0,
"runtime_gate_count": 0,
"token_value_collection_allowed": False,
"payload_persisted": False,
"safe_next_step": (
"write_redacted_inventory_review_receipt_then_collect_owner_coverage_attestation"
if accepted
else "supplement_authenticated_or_admin_export_redacted_inventory_payload"
),
"blocked_operations": [
"store_token_value",
"gitea_api_write",
"repo_write",
"refs_sync",
"github_api",
"github_primary_switch",
"workflow_dispatch",
"raw_session_or_sqlite_read",
],
},
"safe_next_step": (
"review_redacted_inventory_payload_then_collect_owner_coverage_attestation"
if accepted
else "supplement_authenticated_or_admin_export_redacted_inventory_payload"
),
}
def _validate_repos(repos: list[dict[str, Any]]) -> list[str]:
blockers: list[str] = []
seen: set[str] = set()
for index, repo in enumerate(repos):
identity = str(repo.get("full_name") or repo.get("gitea_repo") or "")
if not identity:
blockers.append(f"repos[{index}].identity_missing")
elif identity in seen:
blockers.append(f"repos[{index}].identity_duplicate")
seen.add(identity)
for key in ("name", "default_branch", "clone_url_redacted", "ssh_url_redacted"):
if _is_placeholder(repo.get(key)):
blockers.append(f"repos[{index}].{key}_missing")
owner = repo.get("owner")
if _is_placeholder(owner) and _is_placeholder(_as_dict(owner).get("login")):
blockers.append(f"repos[{index}].owner_missing")
for key in ("private", "archived", "empty"):
if not isinstance(repo.get(key), bool):
blockers.append(f"repos[{index}].{key}_not_boolean")
for key in ("clone_url_redacted", "ssh_url_redacted"):
value = str(repo.get(key) or "")
if _url_has_secret(value):
blockers.append(f"repos[{index}].{key}_not_redacted")
return blockers
def _validate_redaction_attestation(value: Any) -> list[str]:
attestation = _as_dict(value)
if not attestation:
return ["redaction_attestation_missing"]
return [
f"redaction_attestation.{key}_not_true"
for key in sorted(_REQUIRED_ATTESTATIONS)
if attestation.get(key) is not True
]
def _find_sensitive_strings(value: Any) -> list[str]:
hits: list[str] = []
def walk(node: Any, path: str) -> None:
if isinstance(node, dict):
for key, item in node.items():
walk(item, f"{path}.{key}" if path else str(key))
elif isinstance(node, list):
for index, item in enumerate(node):
walk(item, f"{path}[{index}]")
elif isinstance(node, str):
for name, pattern in _SECRET_PATTERNS.items():
if pattern.search(node):
hits.append(f"{path}:{name}")
if _url_has_secret(node):
hits.append(f"{path}:url_contains_secret_material")
walk(value, "")
return sorted(set(hits))
def _find_forbidden_true_fields(value: Any) -> list[str]:
hits: list[str] = []
def walk(node: Any, path: str) -> None:
if isinstance(node, dict):
for key, item in node.items():
next_path = f"{path}.{key}" if path else str(key)
if key in _FORBIDDEN_TRUE_FIELDS and item is True:
hits.append(next_path)
walk(item, next_path)
elif isinstance(node, list):
for index, item in enumerate(node):
walk(item, f"{path}[{index}]")
walk(value, "")
return sorted(hits)
def _url_has_secret(value: str) -> bool:
if "://" not in value:
return False
parsed = urlsplit(value)
if parsed.username or parsed.password:
return True
return any(key.lower() in _SECRET_QUERY_KEYS for key, _ in parse_qsl(parsed.query))
def _is_placeholder(value: Any) -> bool:
if value is None:
return True
if isinstance(value, str):
return value.strip().lower() in {"", "pending", "todo", "tbd", "n/a", "na"}
return False
def _strings(value: Any) -> list[str]:
if not isinstance(value, list):
return []
return [str(item) for item in value if item is not None]
def _as_list(value: Any) -> list[Any]:
return value if isinstance(value, list) else []
def _as_dict(value: Any) -> dict[str, Any]:
return value if isinstance(value, dict) else {}
def _as_int(value: Any) -> int:
try:
return int(value)
except (TypeError, ValueError):
return 0

View File

@@ -40,6 +40,7 @@ def load_latest_gitea_private_inventory_p0_scorecard(
def _build_payload(scorecard: dict[str, Any], path: Path) -> dict[str, Any]:
gitea_inventory = _dict(scorecard.get("gitea_inventory"))
import_acceptance = _dict(scorecard.get("authenticated_import_acceptance"))
payload_validation = _dict(scorecard.get("authenticated_payload_validation"))
coverage_attestation = _dict(scorecard.get("coverage_attestation"))
owner_response_validation = _dict(scorecard.get("owner_response_validation"))
product_coverage = _dict(scorecard.get("product_row_coverage"))
@@ -70,6 +71,7 @@ def _build_payload(scorecard: dict[str, Any], path: Path) -> dict[str, Any]:
},
"gitea_inventory": gitea_inventory,
"authenticated_import_acceptance": import_acceptance,
"authenticated_payload_validation": payload_validation,
"coverage_attestation": coverage_attestation,
"owner_response_validation": owner_response_validation,
"product_row_coverage": product_coverage,
@@ -89,6 +91,15 @@ def _build_payload(scorecard: dict[str, Any], path: Path) -> dict[str, Any]:
"accepted_inventory_payload_count": _int(
import_acceptance.get("accepted_payload_count")
),
"authenticated_payload_validation_status": str(
payload_validation.get("status") or "unknown"
),
"authenticated_payload_validation_accepted_count": _int(
payload_validation.get("accepted_payload_count")
),
"authenticated_payload_validation_blocker_count": _int(
payload_validation.get("blocker_count")
),
"owner_coverage_attestation_received_count": _int(
coverage_attestation.get("received_attestation_count")
),