Merge remote-tracking branch 'gitea-ssh/main' into codex/ai-agent-controlled-loop-20260629
Some checks failed
CD Pipeline / workflow-shape (push) Successful in 0s
CD Pipeline / cancel-stale-cd (push) Has been skipped
CD Pipeline / tests (push) Successful in 20s
CD Pipeline / build-and-deploy (push) Successful in 6m28s
AI 技術雷達監控 / ai-technology-watch (push) Successful in 42s
CD Pipeline / post-deploy-checks (push) Has been cancelled
Some checks failed
CD Pipeline / workflow-shape (push) Successful in 0s
CD Pipeline / cancel-stale-cd (push) Has been skipped
CD Pipeline / tests (push) Successful in 20s
CD Pipeline / build-and-deploy (push) Successful in 6m28s
AI 技術雷達監控 / ai-technology-watch (push) Successful in 42s
CD Pipeline / post-deploy-checks (push) Has been cancelled
# Conflicts: # apps/api/src/api/v1/agents.py
This commit is contained in:
@@ -260,6 +260,8 @@ jobs:
|
||||
;;
|
||||
apps/api/src/services/gitea_authenticated_inventory_payload_validation.py)
|
||||
;;
|
||||
apps/api/src/services/gitea_owner_coverage_attestation_validation.py)
|
||||
;;
|
||||
apps/api/src/services/gitea_private_inventory_p0_scorecard.py)
|
||||
;;
|
||||
apps/api/src/services/reboot_auto_recovery_slo_scorecard.py)
|
||||
@@ -495,6 +497,7 @@ jobs:
|
||||
src/services/heartbeat_report_service.py \
|
||||
src/services/credential_escrow_evidence_intake_readiness.py \
|
||||
src/services/gitea_authenticated_inventory_payload_validation.py \
|
||||
src/services/gitea_owner_coverage_attestation_validation.py \
|
||||
src/services/gitea_private_inventory_p0_scorecard.py \
|
||||
src/services/reboot_auto_recovery_slo_scorecard.py \
|
||||
src/services/iwooos_security_operating_system.py \
|
||||
|
||||
@@ -341,6 +341,9 @@ from src.services.docker_build_surface_inventory import (
|
||||
from src.services.gitea_authenticated_inventory_payload_validation import (
|
||||
validate_gitea_authenticated_inventory_payload,
|
||||
)
|
||||
from src.services.gitea_owner_coverage_attestation_validation import (
|
||||
validate_gitea_owner_coverage_attestation,
|
||||
)
|
||||
from src.services.gitea_private_inventory_p0_scorecard import (
|
||||
load_latest_gitea_private_inventory_p0_scorecard,
|
||||
)
|
||||
@@ -1147,6 +1150,42 @@ async def validate_gitea_authenticated_inventory_payload_packet(
|
||||
) from exc
|
||||
|
||||
|
||||
@router.post(
|
||||
"/gitea-private-inventory-p0-scorecard/validate-owner-coverage-attestation",
|
||||
response_model=dict[str, Any],
|
||||
summary="驗證 P0-003 Gitea owner coverage attestation 脫敏回覆",
|
||||
description=(
|
||||
"針對單次 owner-provided redacted Gitea coverage attestation response "
|
||||
"packet 進行 no-persist validation;此端點只回傳 owner attestation "
|
||||
"reviewer readiness / needs supplement / quarantined / rejected execution "
|
||||
"分流,不保存 payload、不呼叫 Gitea/GitHub、不收 token value、"
|
||||
"不建立 repo、不改 visibility、不同步 refs、不觸發 workflow、"
|
||||
"不讀 secret、不讀 raw session/SQLite。"
|
||||
),
|
||||
)
|
||||
async def validate_gitea_owner_coverage_attestation_packet(
|
||||
owner_response: dict[str, Any],
|
||||
) -> dict[str, Any]:
|
||||
"""Return no-persist validation for one P0-003 owner attestation packet."""
|
||||
try:
|
||||
payload = await asyncio.to_thread(
|
||||
validate_gitea_owner_coverage_attestation,
|
||||
owner_response,
|
||||
)
|
||||
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_owner_coverage_attestation_invalid", error=str(exc))
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
||||
detail="P0-003 Gitea owner coverage attestation 驗證器無效",
|
||||
) from exc
|
||||
|
||||
|
||||
@router.get(
|
||||
"/github-target-private-backup-evidence-gate",
|
||||
response_model=dict[str, Any],
|
||||
|
||||
@@ -0,0 +1,388 @@
|
||||
"""P0-003 Gitea owner coverage attestation no-persist validation.
|
||||
|
||||
This service validates one owner-provided redacted coverage attestation packet.
|
||||
It loads the committed response templates, accepts only redacted metadata, and
|
||||
never writes repos, refs, secrets, workflow state, or runtime state.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import re
|
||||
from pathlib import Path
|
||||
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,
|
||||
)
|
||||
from src.services.snapshot_paths import default_security_dir
|
||||
|
||||
_SCHEMA_VERSION = "gitea_owner_coverage_attestation_validation_v1"
|
||||
_OWNER_RESPONSE_SCHEMA_VERSION = "gitea_inventory_owner_attestation_response_v1"
|
||||
_OWNER_RESPONSE_PACKET = "gitea-inventory-owner-attestation-response.snapshot.json"
|
||||
_LANE_ID = "s4_9_gitea_inventory_owner_attestation_response"
|
||||
_BLOCKERS_CLEARED_BY_ACCEPTED_ATTESTATION = {
|
||||
"gitea_owner_coverage_attestation_not_received",
|
||||
}
|
||||
_BLOCKERS_CLEARED_AFTER_INVENTORY_RECEIPT_WRITEBACK = {
|
||||
"gitea_repo_inventory_status_not_ok",
|
||||
"gitea_visibility_scope_public_only_or_unknown",
|
||||
"gitea_authenticated_inventory_payload_not_accepted",
|
||||
"gitea_owner_coverage_attestation_not_received",
|
||||
}
|
||||
_FORBIDDEN_TRUE_FIELDS = {
|
||||
"action_buttons_allowed",
|
||||
"change_repo_visibility",
|
||||
"create_gitea_repo",
|
||||
"create_github_repo",
|
||||
"delete_or_archive_gitea_repo",
|
||||
"execution_authorized",
|
||||
"force_push",
|
||||
"gitea_repo_write_authorized",
|
||||
"github_primary_switch_authorized",
|
||||
"refs_sync_allowed",
|
||||
"refs_sync_authorized",
|
||||
"repo_write_allowed",
|
||||
"runtime_action_requested",
|
||||
"runtime_execution_authorized",
|
||||
"secret_value_collection_allowed",
|
||||
"sync_git_refs",
|
||||
"token_value_collection_allowed",
|
||||
"workflow_dispatch_requested",
|
||||
"write_to_gitea",
|
||||
}
|
||||
_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"}
|
||||
|
||||
|
||||
def validate_gitea_owner_coverage_attestation(
|
||||
owner_response: dict[str, Any],
|
||||
scorecard: dict[str, Any] | None = None,
|
||||
security_dir: Path | None = None,
|
||||
) -> dict[str, Any]:
|
||||
"""Validate one redacted P0-003 owner attestation packet without persisting it."""
|
||||
current = scorecard or load_latest_gitea_private_inventory_p0_scorecard()
|
||||
templates = _load_response_templates(security_dir)
|
||||
template_by_item = {
|
||||
str(template.get("attestation_item_id")): template for template in templates
|
||||
}
|
||||
required_item_ids = set(template_by_item)
|
||||
blockers: list[str] = []
|
||||
sensitive_hits = _find_sensitive_strings(owner_response)
|
||||
forbidden_true_fields = _find_forbidden_true_fields(owner_response)
|
||||
|
||||
if owner_response.get("schema_version") != _OWNER_RESPONSE_SCHEMA_VERSION:
|
||||
blockers.append(f"schema_version_not_{_OWNER_RESPONSE_SCHEMA_VERSION}")
|
||||
if str(owner_response.get("lane_id") or "") != _LANE_ID:
|
||||
blockers.append("lane_id_not_s4_9_owner_attestation_response")
|
||||
|
||||
responses = _response_items(owner_response)
|
||||
response_by_item: dict[str, dict[str, Any]] = {}
|
||||
duplicate_item_ids: list[str] = []
|
||||
for index, response in enumerate(responses):
|
||||
item_id = str(response.get("attestation_item_id") or "")
|
||||
if not item_id:
|
||||
blockers.append(f"responses[{index}].attestation_item_id_missing")
|
||||
continue
|
||||
if item_id in response_by_item:
|
||||
duplicate_item_ids.append(item_id)
|
||||
response_by_item[item_id] = response
|
||||
|
||||
unknown_item_ids = sorted(set(response_by_item) - required_item_ids)
|
||||
missing_item_ids = sorted(required_item_ids - set(response_by_item))
|
||||
blockers.extend(f"unknown_attestation_item_id:{item_id}" for item_id in unknown_item_ids)
|
||||
blockers.extend(f"missing_attestation_item_id:{item_id}" for item_id in missing_item_ids)
|
||||
blockers.extend(f"duplicate_attestation_item_id:{item_id}" for item_id in duplicate_item_ids)
|
||||
|
||||
for item_id in sorted(required_item_ids & set(response_by_item)):
|
||||
blockers.extend(
|
||||
_validate_response_item(
|
||||
item_id=item_id,
|
||||
response=response_by_item[item_id],
|
||||
template=template_by_item[item_id],
|
||||
)
|
||||
)
|
||||
|
||||
if forbidden_true_fields:
|
||||
status = "rejected_execution_request"
|
||||
elif sensitive_hits:
|
||||
status = "quarantined_sensitive_payload"
|
||||
elif blockers:
|
||||
status = "needs_supplement"
|
||||
else:
|
||||
status = "accepted_for_owner_coverage_attestation_review_only"
|
||||
|
||||
accepted = status == "accepted_for_owner_coverage_attestation_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_ATTESTATION
|
||||
]
|
||||
if accepted
|
||||
else current_blockers
|
||||
)
|
||||
projected_after_inventory_receipt = (
|
||||
[
|
||||
blocker
|
||||
for blocker in current_blockers
|
||||
if blocker not in _BLOCKERS_CLEARED_AFTER_INVENTORY_RECEIPT_WRITEBACK
|
||||
]
|
||||
if accepted
|
||||
else current_blockers
|
||||
)
|
||||
current_accepted_attestation_count = _as_int(
|
||||
current_rollups.get("owner_coverage_attestation_accepted_count")
|
||||
)
|
||||
projected_accepted_attestation_count = (
|
||||
max(current_accepted_attestation_count, 1)
|
||||
if accepted
|
||||
else current_accepted_attestation_count
|
||||
)
|
||||
|
||||
return {
|
||||
"schema_version": _SCHEMA_VERSION,
|
||||
"status": status,
|
||||
"priority": "P0-003",
|
||||
"scope": "gitea_owner_coverage_attestation_validation",
|
||||
"source_contract": _OWNER_RESPONSE_SCHEMA_VERSION,
|
||||
"source_scorecard_status": current.get("status"),
|
||||
"result": {
|
||||
"accepted_attestation_packet_count": 1 if accepted else 0,
|
||||
"required_response_item_count": len(required_item_ids),
|
||||
"provided_response_item_count": len(response_by_item),
|
||||
"accepted_response_count": len(required_item_ids) if accepted else 0,
|
||||
"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),
|
||||
"projected_active_blocker_count_after_redacted_inventory_receipt_writeback": (
|
||||
len(projected_after_inventory_receipt)
|
||||
),
|
||||
"current_owner_coverage_attestation_accepted_count": (
|
||||
current_accepted_attestation_count
|
||||
),
|
||||
"projected_owner_coverage_attestation_accepted_count": (
|
||||
projected_accepted_attestation_count
|
||||
),
|
||||
"token_value_collection_allowed": False,
|
||||
"secret_value_collection_allowed": False,
|
||||
"repo_write_allowed": False,
|
||||
"refs_sync_allowed": False,
|
||||
"github_primary_switch_authorized": False,
|
||||
"runtime_gate_count": 0,
|
||||
},
|
||||
"blockers": blockers,
|
||||
"missing_attestation_item_ids": missing_item_ids,
|
||||
"unknown_attestation_item_ids": unknown_item_ids,
|
||||
"duplicate_attestation_item_ids": sorted(duplicate_item_ids),
|
||||
"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,
|
||||
"secret_value_collection_allowed": False,
|
||||
"runtime_action_performed": False,
|
||||
"raw_session_or_sqlite_read_performed": False,
|
||||
},
|
||||
"reviewer_readiness": {
|
||||
"schema_version": "gitea_owner_coverage_attestation_reviewer_readiness_v1",
|
||||
"status": (
|
||||
"ready_for_private_inventory_closeout_after_inventory_receipt_writeback"
|
||||
if accepted
|
||||
else "not_ready_for_private_inventory_closeout"
|
||||
),
|
||||
"redacted_owner_attestation_receipt_writeback_ready_count": (
|
||||
1 if accepted else 0
|
||||
),
|
||||
"accepted_response_count": len(required_item_ids) if accepted else 0,
|
||||
"projected_active_blocker_count": len(projected_blockers),
|
||||
"projected_remaining_blockers": projected_blockers,
|
||||
"projected_active_blocker_count_after_redacted_inventory_receipt_writeback": (
|
||||
len(projected_after_inventory_receipt)
|
||||
),
|
||||
"projected_remaining_blockers_after_redacted_inventory_receipt_writeback": (
|
||||
projected_after_inventory_receipt
|
||||
),
|
||||
"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,
|
||||
"secret_value_collection_allowed": False,
|
||||
"payload_persisted": False,
|
||||
"safe_next_step": (
|
||||
"write_redacted_inventory_receipt_and_owner_attestation_receipt_then_close_p0_003"
|
||||
if accepted
|
||||
else "supplement_owner_coverage_attestation_redacted_metadata"
|
||||
),
|
||||
"blocked_operations": [
|
||||
"store_token_value",
|
||||
"store_raw_secret",
|
||||
"gitea_api_write",
|
||||
"repo_write",
|
||||
"refs_sync",
|
||||
"github_api",
|
||||
"github_primary_switch",
|
||||
"workflow_dispatch",
|
||||
"runtime_action",
|
||||
"raw_session_or_sqlite_read",
|
||||
],
|
||||
},
|
||||
"safe_next_step": (
|
||||
"review_redacted_owner_attestation_then_pair_with_inventory_receipt_writeback"
|
||||
if accepted
|
||||
else "supplement_owner_coverage_attestation_redacted_metadata"
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
def _load_response_templates(security_dir: Path | None) -> list[dict[str, Any]]:
|
||||
directory = security_dir or default_security_dir(Path(__file__))
|
||||
path = directory / _OWNER_RESPONSE_PACKET
|
||||
with path.open(encoding="utf-8") as handle:
|
||||
packet = json.load(handle)
|
||||
if packet.get("schema_version") != _OWNER_RESPONSE_SCHEMA_VERSION:
|
||||
raise ValueError(f"{path}: owner response packet schema mismatch")
|
||||
templates = packet.get("response_templates")
|
||||
if not isinstance(templates, list) or len(templates) != 5:
|
||||
raise ValueError(f"{path}: expected five response templates")
|
||||
return [template for template in templates if isinstance(template, dict)]
|
||||
|
||||
|
||||
def _response_items(owner_response: dict[str, Any]) -> list[dict[str, Any]]:
|
||||
for key in ("responses", "owner_responses", "attestation_responses"):
|
||||
value = owner_response.get(key)
|
||||
if isinstance(value, list):
|
||||
return [item for item in value if isinstance(item, dict)]
|
||||
return []
|
||||
|
||||
|
||||
def _validate_response_item(
|
||||
*,
|
||||
item_id: str,
|
||||
response: dict[str, Any],
|
||||
template: dict[str, Any],
|
||||
) -> list[str]:
|
||||
blockers: list[str] = []
|
||||
decision = str(response.get("decision") or "")
|
||||
acceptable_decisions = set(_strings(template.get("acceptable_decisions")))
|
||||
if decision not in acceptable_decisions:
|
||||
blockers.append(f"{item_id}.decision_not_allowed")
|
||||
|
||||
for field in _strings(template.get("required_owner_fields")):
|
||||
value = response.get(field)
|
||||
if field == "evidence_refs":
|
||||
if not _has_redacted_evidence_refs(value):
|
||||
blockers.append(f"{item_id}.evidence_refs_missing_or_not_redacted_list")
|
||||
continue
|
||||
if _is_placeholder(value):
|
||||
blockers.append(f"{item_id}.{field}_missing")
|
||||
|
||||
if _is_placeholder(response.get("decision_reason")):
|
||||
blockers.append(f"{item_id}.decision_reason_missing")
|
||||
return blockers
|
||||
|
||||
|
||||
def _has_redacted_evidence_refs(value: Any) -> bool:
|
||||
if not isinstance(value, list) or not value:
|
||||
return False
|
||||
for item in value:
|
||||
if not isinstance(item, str) or _is_placeholder(item):
|
||||
return False
|
||||
if _url_has_secret(item):
|
||||
return False
|
||||
return 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"}
|
||||
if isinstance(value, list):
|
||||
return not value
|
||||
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_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
|
||||
@@ -10,6 +10,9 @@ from src.api.v1.agents import router
|
||||
from src.services.gitea_authenticated_inventory_payload_validation import (
|
||||
validate_gitea_authenticated_inventory_payload,
|
||||
)
|
||||
from src.services.gitea_owner_coverage_attestation_validation import (
|
||||
validate_gitea_owner_coverage_attestation,
|
||||
)
|
||||
from src.services.gitea_private_inventory_p0_scorecard import (
|
||||
load_latest_gitea_private_inventory_p0_scorecard,
|
||||
)
|
||||
@@ -201,6 +204,105 @@ def test_gitea_authenticated_inventory_payload_endpoint_validates_without_persis
|
||||
assert data["operation_boundaries"]["raw_session_or_sqlite_read_performed"] is False
|
||||
|
||||
|
||||
def test_gitea_owner_coverage_attestation_validator_accepts_redacted_response():
|
||||
payload = validate_gitea_owner_coverage_attestation(
|
||||
_valid_owner_coverage_attestation_payload(),
|
||||
scorecard=_scorecard_readback(),
|
||||
)
|
||||
|
||||
assert payload["schema_version"] == "gitea_owner_coverage_attestation_validation_v1"
|
||||
assert payload["status"] == "accepted_for_owner_coverage_attestation_review_only"
|
||||
assert payload["result"]["accepted_attestation_packet_count"] == 1
|
||||
assert payload["result"]["required_response_item_count"] == 5
|
||||
assert payload["result"]["provided_response_item_count"] == 5
|
||||
assert payload["result"]["accepted_response_count"] == 5
|
||||
assert payload["result"]["current_active_blocker_count"] == 4
|
||||
assert payload["result"]["projected_active_blocker_count"] == 3
|
||||
assert (
|
||||
payload["result"][
|
||||
"projected_active_blocker_count_after_redacted_inventory_receipt_writeback"
|
||||
]
|
||||
== 0
|
||||
)
|
||||
assert payload["result"]["projected_owner_coverage_attestation_accepted_count"] == 1
|
||||
assert payload["result"]["runtime_gate_count"] == 0
|
||||
assert payload["operation_boundaries"]["payload_persisted"] is False
|
||||
assert payload["operation_boundaries"]["gitea_api_called"] is False
|
||||
assert payload["operation_boundaries"]["repo_write_performed"] is False
|
||||
assert payload["operation_boundaries"]["refs_sync_performed"] is False
|
||||
assert payload["operation_boundaries"]["github_api_used"] is False
|
||||
assert payload["operation_boundaries"]["secret_plaintext_read"] is False
|
||||
assert (
|
||||
payload["reviewer_readiness"]["status"]
|
||||
== "ready_for_private_inventory_closeout_after_inventory_receipt_writeback"
|
||||
)
|
||||
assert (
|
||||
payload["reviewer_readiness"][
|
||||
"redacted_owner_attestation_receipt_writeback_ready_count"
|
||||
]
|
||||
== 1
|
||||
)
|
||||
assert payload["reviewer_readiness"]["repo_write_authorized_count"] == 0
|
||||
|
||||
|
||||
def test_gitea_owner_coverage_attestation_endpoint_validates_without_persisting():
|
||||
app = FastAPI()
|
||||
app.include_router(router, prefix="/api/v1")
|
||||
client = TestClient(app)
|
||||
|
||||
response = client.post(
|
||||
"/api/v1/agents/gitea-private-inventory-p0-scorecard/validate-owner-coverage-attestation",
|
||||
json=_valid_owner_coverage_attestation_payload(),
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["status"] == "accepted_for_owner_coverage_attestation_review_only"
|
||||
assert data["result"]["accepted_attestation_packet_count"] == 1
|
||||
assert data["result"]["projected_active_blocker_count"] == 3
|
||||
assert (
|
||||
data["reviewer_readiness"][
|
||||
"redacted_owner_attestation_receipt_writeback_ready_count"
|
||||
]
|
||||
== 1
|
||||
)
|
||||
assert data["operation_boundaries"]["payload_persisted"] is False
|
||||
assert data["operation_boundaries"]["gitea_write_performed"] is False
|
||||
assert data["operation_boundaries"]["raw_session_or_sqlite_read_performed"] is False
|
||||
|
||||
|
||||
def test_gitea_owner_coverage_attestation_validator_quarantines_secret_material():
|
||||
payload = _valid_owner_coverage_attestation_payload()
|
||||
payload["responses"][0]["evidence_refs"] = [
|
||||
"https://gitea.example/wooo/awoooi?token=secret-value"
|
||||
]
|
||||
|
||||
validation = validate_gitea_owner_coverage_attestation(
|
||||
payload,
|
||||
scorecard=_scorecard_readback(),
|
||||
)
|
||||
|
||||
assert validation["status"] == "quarantined_sensitive_payload"
|
||||
assert validation["result"]["accepted_attestation_packet_count"] == 0
|
||||
assert validation["result"]["sensitive_payload_hit_count"] >= 1
|
||||
assert validation["operation_boundaries"]["payload_persisted"] is False
|
||||
|
||||
|
||||
def test_gitea_owner_coverage_attestation_validator_rejects_execution_request():
|
||||
payload = _valid_owner_coverage_attestation_payload()
|
||||
payload["write_to_gitea"] = True
|
||||
|
||||
validation = validate_gitea_owner_coverage_attestation(
|
||||
payload,
|
||||
scorecard=_scorecard_readback(),
|
||||
)
|
||||
|
||||
assert validation["status"] == "rejected_execution_request"
|
||||
assert validation["result"]["accepted_attestation_packet_count"] == 0
|
||||
assert validation["result"]["forbidden_true_field_count"] == 1
|
||||
assert validation["operation_boundaries"]["repo_write_performed"] is False
|
||||
|
||||
|
||||
def test_gitea_authenticated_inventory_payload_validator_quarantines_secret_material():
|
||||
payload = _valid_authenticated_inventory_payload()
|
||||
payload["repos"][0]["clone_url_redacted"] = "https://user:password@example.test/repo.git"
|
||||
@@ -417,6 +519,81 @@ def _valid_authenticated_inventory_payload() -> dict:
|
||||
}
|
||||
|
||||
|
||||
def _valid_owner_coverage_attestation_payload() -> dict:
|
||||
return {
|
||||
"schema_version": "gitea_inventory_owner_attestation_response_v1",
|
||||
"lane_id": "s4_9_gitea_inventory_owner_attestation_response",
|
||||
"runtime_execution_authorized": False,
|
||||
"token_value_collection_allowed": False,
|
||||
"secret_value_collection_allowed": False,
|
||||
"gitea_repo_write_authorized": False,
|
||||
"refs_sync_authorized": False,
|
||||
"github_primary_switch_authorized": False,
|
||||
"action_buttons_allowed": False,
|
||||
"responses": [
|
||||
{
|
||||
"attestation_item_id": "public_only_vs_local_gitea_gap",
|
||||
"owner_role_or_team": "platform-security-owner",
|
||||
"decision": "in_scope",
|
||||
"decision_reason": "admin export must cover the public-only gap",
|
||||
"affected_repos": ["wooo/awoooi", "wooo/ewoooc"],
|
||||
"evidence_refs": [
|
||||
"docs/security/gitea-repo-inventory.snapshot.json",
|
||||
"docs/security/gitea-org-repo-inventory-blocked.snapshot.json",
|
||||
],
|
||||
"followup_owner": "platform-security-owner",
|
||||
},
|
||||
{
|
||||
"attestation_item_id": "org_user_endpoint_identity",
|
||||
"owner_role_or_team": "platform-security-owner",
|
||||
"decision": "in_scope",
|
||||
"decision_reason": "wooo namespace is the canonical Gitea scope",
|
||||
"canonical_namespace": "wooo",
|
||||
"evidence_refs": [
|
||||
"docs/security/gitea-org-repo-inventory-blocked.snapshot.json"
|
||||
],
|
||||
"followup_owner": "platform-security-owner",
|
||||
},
|
||||
{
|
||||
"attestation_item_id": "internal_110_adjacent_scope",
|
||||
"owner_role_or_team": "platform-security-owner",
|
||||
"decision": "in_scope",
|
||||
"decision_reason": "internal adjacent source is covered by redacted metadata",
|
||||
"affected_sources": ["gitea-ssh-main-redacted", "public-route-redacted"],
|
||||
"evidence_refs": [
|
||||
"docs/security/git-remote-refs-bitan-exposure.snapshot.json"
|
||||
],
|
||||
"followup_owner": "platform-security-owner",
|
||||
},
|
||||
{
|
||||
"attestation_item_id": "repo_owner_canonical_scope",
|
||||
"owner_role_or_team": "platform-security-owner",
|
||||
"decision": "in_scope",
|
||||
"decision_reason": "repo owner and canonical source remain Gitea-only",
|
||||
"repo_owner": "wooo",
|
||||
"canonical_source": "gitea",
|
||||
"github_target_candidate": "stopped_retired_do_not_use",
|
||||
"visibility_review_owner": "platform-security-owner",
|
||||
"evidence_refs": [
|
||||
"docs/operations/awoooi-gitea-private-inventory-p0-scorecard.snapshot.json"
|
||||
],
|
||||
},
|
||||
{
|
||||
"attestation_item_id": "legacy_or_inaccessible_repo_disposition",
|
||||
"owner_role_or_team": "platform-security-owner",
|
||||
"decision": "legacy_archived",
|
||||
"decision_reason": "legacy or inaccessible sources stay outside active P0 apply",
|
||||
"affected_repos_or_sources": ["legacy-github-mirror-retired"],
|
||||
"disposition": "stopped_retired_do_not_use",
|
||||
"evidence_refs": [
|
||||
"docs/security/gitea-inventory-owner-attestation-response.snapshot.json"
|
||||
],
|
||||
"followup_owner": "platform-security-owner",
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
def _repo(full_name: str) -> dict:
|
||||
_, name = full_name.split("/", 1)
|
||||
return {
|
||||
|
||||
@@ -12815,11 +12815,36 @@
|
||||
"iwooos": {
|
||||
"eyebrow": "資訊安全網",
|
||||
"title": "IwoooS",
|
||||
"subtitle": "AWOOOI 的 AI 自動化資安閉環:把 Kali、原始碼、主機、告警、候選、執行閘門、驗證器與 AwoooP 證據串成可視化資安態勢。",
|
||||
"subtitle": "AI SOC 資安控制台:集中讀取主機、原始碼、告警、候選處置與驗證器狀態。",
|
||||
"boundary": {
|
||||
"label": "目前邊界",
|
||||
"state": "只讀鏡像 / 先觀測",
|
||||
"detail": "只顯示態勢與缺口;掃描、修復、更新、阻擋仍未開閘。"
|
||||
"state": "AI 受控推進 / critical break-glass",
|
||||
"detail": "低 / 中 / 高風險走 selector、dry-run、rollback 與 verifier;secret、破壞性 DB、重啟、付費 provider 與 refs 破壞維持 break-glass。"
|
||||
},
|
||||
"commandRail": {
|
||||
"eyebrow": "控制面",
|
||||
"title": "AI SOC 工作台",
|
||||
"navLabel": "IwoooS 第一屏控制入口",
|
||||
"metrics": {
|
||||
"controlledApply": {
|
||||
"label": "受控執行"
|
||||
},
|
||||
"automationClosure": {
|
||||
"label": "自動化"
|
||||
},
|
||||
"securityPosture": {
|
||||
"label": "資安態勢"
|
||||
},
|
||||
"breakGlass": {
|
||||
"label": "硬邊界"
|
||||
}
|
||||
},
|
||||
"links": {
|
||||
"evidence": "證據",
|
||||
"decisions": "決策",
|
||||
"scope": "範圍",
|
||||
"owners": "Owner"
|
||||
}
|
||||
},
|
||||
"progressIntegrityRibbon": {
|
||||
"eyebrow": "進度誠實儀表",
|
||||
|
||||
@@ -12815,11 +12815,36 @@
|
||||
"iwooos": {
|
||||
"eyebrow": "資訊安全網",
|
||||
"title": "IwoooS",
|
||||
"subtitle": "AWOOOI 的 AI 自動化資安閉環:把 Kali、原始碼、主機、告警、候選、執行閘門、驗證器與 AwoooP 證據串成可視化資安態勢。",
|
||||
"subtitle": "AI SOC 資安控制台:集中讀取主機、原始碼、告警、候選處置與驗證器狀態。",
|
||||
"boundary": {
|
||||
"label": "目前邊界",
|
||||
"state": "只讀鏡像 / 先觀測",
|
||||
"detail": "只顯示態勢與缺口;掃描、修復、更新、阻擋仍未開閘。"
|
||||
"state": "AI 受控推進 / critical break-glass",
|
||||
"detail": "低 / 中 / 高風險走 selector、dry-run、rollback 與 verifier;secret、破壞性 DB、重啟、付費 provider 與 refs 破壞維持 break-glass。"
|
||||
},
|
||||
"commandRail": {
|
||||
"eyebrow": "控制面",
|
||||
"title": "AI SOC 工作台",
|
||||
"navLabel": "IwoooS 第一屏控制入口",
|
||||
"metrics": {
|
||||
"controlledApply": {
|
||||
"label": "受控執行"
|
||||
},
|
||||
"automationClosure": {
|
||||
"label": "自動化"
|
||||
},
|
||||
"securityPosture": {
|
||||
"label": "資安態勢"
|
||||
},
|
||||
"breakGlass": {
|
||||
"label": "硬邊界"
|
||||
}
|
||||
},
|
||||
"links": {
|
||||
"evidence": "證據",
|
||||
"decisions": "決策",
|
||||
"scope": "範圍",
|
||||
"owners": "Owner"
|
||||
}
|
||||
},
|
||||
"progressIntegrityRibbon": {
|
||||
"eyebrow": "進度誠實儀表",
|
||||
|
||||
@@ -6295,6 +6295,20 @@ const evidenceItems = [
|
||||
'kali-integration-status.snapshot.json',
|
||||
]
|
||||
|
||||
const commandRailMetrics = [
|
||||
{ key: 'controlledApply', value: 'L/M/H', icon: Activity, tone: 'steady' },
|
||||
{ key: 'automationClosure', value: '21/21', icon: Workflow, tone: 'steady' },
|
||||
{ key: 'securityPosture', value: '64%', icon: ShieldCheck, tone: 'warn' },
|
||||
{ key: 'breakGlass', value: 'critical', icon: Lock, tone: 'locked' },
|
||||
] as const
|
||||
|
||||
const commandRailLinks = [
|
||||
{ key: 'evidence', href: '#iwooos-first-layer-evidence', icon: SearchCheck, tone: 'steady' },
|
||||
{ key: 'decisions', href: '#iwooos-decision-gate-visuals', icon: Radar, tone: 'warn' },
|
||||
{ key: 'scope', href: '#iwooos-scope-evidence-visuals', icon: Network, tone: 'steady' },
|
||||
{ key: 'owners', href: '#iwooos-source-control-readiness-board', icon: ClipboardCheck, tone: 'warn' },
|
||||
] as const
|
||||
|
||||
const toneColors = {
|
||||
steady: '#1f7a4d',
|
||||
warn: '#b66a2d',
|
||||
@@ -15504,6 +15518,128 @@ function IwoooSFirstProgressUnlockPathBoard() {
|
||||
)
|
||||
}
|
||||
|
||||
function IwoooSCommandRail() {
|
||||
const t = useTranslations('iwooos.commandRail')
|
||||
|
||||
return (
|
||||
<section
|
||||
data-testid="iwooos-command-rail"
|
||||
style={{ marginBottom: 14, maxWidth: '100%', overflow: 'hidden' }}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
...band,
|
||||
padding: 12,
|
||||
display: 'grid',
|
||||
gridTemplateColumns: 'repeat(auto-fit, minmax(min(100%, 260px), 1fr))',
|
||||
gap: 10,
|
||||
alignItems: 'stretch',
|
||||
background: '#fcfdfa',
|
||||
borderColor: '#dce3d4',
|
||||
}}
|
||||
>
|
||||
<div style={{ minWidth: 0, display: 'grid', alignContent: 'center', gap: 4 }}>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 8, color: '#46624a', fontSize: 11, fontWeight: 800 }}>
|
||||
<ShieldCheck size={15} />
|
||||
<span>{t('eyebrow')}</span>
|
||||
</div>
|
||||
<h2 style={{ margin: 0, fontSize: 18, lineHeight: 1.15, color: '#141413' }}>{t('title')}</h2>
|
||||
</div>
|
||||
|
||||
<div
|
||||
style={{
|
||||
display: 'grid',
|
||||
gridTemplateColumns: 'repeat(auto-fit, minmax(106px, 1fr))',
|
||||
gap: 8,
|
||||
minWidth: 0,
|
||||
}}
|
||||
>
|
||||
{commandRailMetrics.map(item => {
|
||||
const Icon = item.icon
|
||||
|
||||
return (
|
||||
<div
|
||||
key={item.key}
|
||||
style={{
|
||||
border: '0.5px solid #dbe5d6',
|
||||
borderRadius: 8,
|
||||
background: '#fff',
|
||||
minHeight: 58,
|
||||
padding: 9,
|
||||
display: 'grid',
|
||||
gap: 5,
|
||||
minWidth: 0,
|
||||
}}
|
||||
>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: 6, minWidth: 0 }}>
|
||||
<Icon size={14} color={toneColors[item.tone]} />
|
||||
<span
|
||||
style={{
|
||||
color: '#626b5c',
|
||||
fontSize: 10,
|
||||
fontWeight: 800,
|
||||
minWidth: 0,
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
whiteSpace: 'nowrap',
|
||||
}}
|
||||
>
|
||||
{t(`metrics.${item.key}.label` as never)}
|
||||
</span>
|
||||
</div>
|
||||
<strong style={{ color: toneColors[item.tone], fontSize: 16, lineHeight: 1.05 }}>{item.value}</strong>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
|
||||
<nav
|
||||
aria-label={t('navLabel')}
|
||||
style={{
|
||||
display: 'grid',
|
||||
gridTemplateColumns: 'repeat(auto-fit, minmax(92px, 1fr))',
|
||||
gap: 8,
|
||||
alignContent: 'center',
|
||||
minWidth: 0,
|
||||
}}
|
||||
>
|
||||
{commandRailLinks.map(item => {
|
||||
const Icon = item.icon
|
||||
|
||||
return (
|
||||
<a
|
||||
key={item.key}
|
||||
href={item.href}
|
||||
style={{
|
||||
border: '0.5px solid #d6dfd2',
|
||||
borderRadius: 8,
|
||||
background: '#f7faf3',
|
||||
color: '#213823',
|
||||
minHeight: 42,
|
||||
padding: '8px 9px',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
gap: 6,
|
||||
fontSize: 11,
|
||||
fontWeight: 900,
|
||||
textDecoration: 'none',
|
||||
minWidth: 0,
|
||||
}}
|
||||
>
|
||||
<Icon size={14} color={toneColors[item.tone]} />
|
||||
<span style={{ minWidth: 0, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
|
||||
{t(`links.${item.key}` as never)}
|
||||
</span>
|
||||
</a>
|
||||
)
|
||||
})}
|
||||
</nav>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
|
||||
function IwoooSProgressIntegrityRibbon() {
|
||||
const t = useTranslations('iwooos.progressIntegrityRibbon')
|
||||
const textWrap = { overflowWrap: 'anywhere' as const, wordBreak: 'break-word' as const }
|
||||
@@ -23548,6 +23684,7 @@ export default function IwoooSPage({ params }: { params: { locale: string } }) {
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<IwoooSCommandRail />
|
||||
<IwoooSProgressIntegrityRibbon />
|
||||
<IwoooSExecutiveSnapshotBoard />
|
||||
<IwoooSAgentBountySecurityOnboardingBoard />
|
||||
|
||||
@@ -79,7 +79,7 @@ spec:
|
||||
- name: AWOOOI_BUILD_COMMIT_SHA
|
||||
# 2026-06-29 Codex: CD rewrites this to the deployed image tag so
|
||||
# production deploy readback does not rely on a stale static snapshot.
|
||||
value: "d14a25f93ca95e4fe96fc339c4bf35ceec5bbe56"
|
||||
value: "b9293b76b56cd327f34b4f2fb723674665f69a1c"
|
||||
- name: USE_AI_ROUTER
|
||||
value: "true"
|
||||
- name: ENABLE_NEMOTRON_COLLABORATION
|
||||
|
||||
@@ -41,7 +41,7 @@ resources:
|
||||
images:
|
||||
- name: 192.168.0.110:5000/library/api:IMAGE_TAG_PLACEHOLDER
|
||||
newName: 192.168.0.110:5000/awoooi/api
|
||||
newTag: d14a25f93ca95e4fe96fc339c4bf35ceec5bbe56
|
||||
newTag: b9293b76b56cd327f34b4f2fb723674665f69a1c
|
||||
- name: 192.168.0.110:5000/library/web:IMAGE_TAG_PLACEHOLDER
|
||||
newName: 192.168.0.110:5000/awoooi/web
|
||||
newTag: d14a25f93ca95e4fe96fc339c4bf35ceec5bbe56
|
||||
newTag: b9293b76b56cd327f34b4f2fb723674665f69a1c
|
||||
|
||||
@@ -121,6 +121,7 @@ def test_gitea_private_inventory_scorecard_stays_on_controlled_runtime_profile()
|
||||
expected_sources = [
|
||||
"docs/operations/awoooi-gitea-private-inventory-p0-scorecard.snapshot.json)",
|
||||
"apps/api/src/services/gitea_authenticated_inventory_payload_validation.py)",
|
||||
"apps/api/src/services/gitea_owner_coverage_attestation_validation.py)",
|
||||
"apps/api/src/services/gitea_private_inventory_p0_scorecard.py)",
|
||||
"apps/api/tests/test_gitea_private_inventory_p0_scorecard_api.py)",
|
||||
"docs/operations/awoooi-gitea-authenticated-inventory-payload-validation.snapshot.json)",
|
||||
@@ -131,6 +132,7 @@ def test_gitea_private_inventory_scorecard_stays_on_controlled_runtime_profile()
|
||||
"scripts/security/gitea-authenticated-inventory-payload-validator.py)",
|
||||
"scripts/security/tests/test_gitea_private_inventory_p0_scorecard.py)",
|
||||
"src/services/gitea_authenticated_inventory_payload_validation.py",
|
||||
"src/services/gitea_owner_coverage_attestation_validation.py",
|
||||
"src/services/gitea_private_inventory_p0_scorecard.py",
|
||||
"tests/test_gitea_private_inventory_p0_scorecard_api.py",
|
||||
"scripts/security/tests/test_gitea_authenticated_inventory_payload_validator.py)",
|
||||
|
||||
Reference in New Issue
Block a user