Files
awoooi/scripts/security/high-value-config-owner-request-draft.py

233 lines
8.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""
IwoooS 高價值配置 owner request 草稿包產生器。
本工具讀取 owner packet intake preflight snapshot產生可人工送件前核對
的 request draft 與 handoff envelope。它不送件、不確認收件人、不建立
audit event、不寫 reviewer queue、不開 runtime gate。
"""
from __future__ import annotations
import argparse
import json
import subprocess
import sys
from datetime import datetime, timedelta, timezone
from pathlib import Path
from typing import Any
TAIPEI = timezone(timedelta(hours=8))
HANDOFF_ENVELOPE_FIELDS = [
"request_id",
"stage_id",
"packet_id",
"recipient_role_or_team",
"sender_role_or_team",
"requested_response_window",
"allowed_response_format",
"redacted_evidence_refs",
"forbidden_payloads",
"followup_owner",
"not_approval",
]
FORBIDDEN_PAYLOADS = [
"token",
"secret",
"private_key",
"cookie",
"session",
"authorization_header",
"runner_token",
"webhook_secret",
"db_dump",
"repo_archive",
"git_object_pack",
"raw_sensitive_live_config",
]
NOT_APPROVAL_STATEMENT = (
"本草稿不是 request sent、不是 owner response received、不是 reviewer accepted、"
"不是 Nginx reload、DNS / TLS change、workflow 修改、host write、active scan、"
"production write 或 runtime gate 授權。"
)
def git_short_sha(root: Path) -> str:
try:
result = subprocess.run(
["git", "rev-parse", "--short", "HEAD"],
cwd=root,
check=True,
capture_output=True,
text=True,
)
return result.stdout.strip()
except Exception:
return "unknown"
def load_json(path: Path) -> dict[str, Any]:
return json.loads(path.read_text(encoding="utf-8"))
def request_id_for(packet: dict[str, Any]) -> str:
return packet["packet_id"].replace("high_value_config_owner_packet:", "high_value_config_owner_request:")
def request_draft(packet: dict[str, Any], intake_report: dict[str, Any]) -> dict[str, Any]:
request_id = request_id_for(packet)
return {
"request_id": request_id,
"status": "draft_not_dispatched",
"stage_id": "P0-14",
"packet_id": packet["packet_id"],
"category_id": packet["category_id"],
"label": packet["label"],
"priority": packet["priority"],
"control_tier": packet["control_tier"],
"required_gate": packet["required_gate"],
"recipient_role_or_team": "pending_owner_role_or_team",
"sender_role_or_team": "iwooos_security_reviewer",
"requested_response_window": "not_scheduled",
"allowed_response_format": {
"fields": packet["required_owner_fields"],
"allowed_decisions": intake_report.get("allowed_decisions", []),
"redacted_evidence_refs_only": True,
},
"redacted_evidence_refs": [
"docs/security/high-value-config-owner-packet.snapshot.json",
"docs/security/high-value-config-owner-packet-intake-preflight.snapshot.json",
],
"affected_files": packet.get("affected_files", []),
"required_validation": packet.get("required_validation", []),
"forbidden_payloads": FORBIDDEN_PAYLOADS,
"blocked_requests": intake_report.get("blocked_requests", []),
"handoff_envelope_fields": HANDOFF_ENVELOPE_FIELDS,
"followup_owner": "pending_followup_owner",
"not_approval": True,
"not_approval_statement": NOT_APPROVAL_STATEMENT,
"request_sent": False,
"recipient_confirmed": False,
"audit_event_emitted": False,
"received_response": False,
"accepted_response": False,
"rejected_response": False,
"reviewer_queue_write": False,
"runtime_gate": False,
"action_buttons_allowed": False,
"secret_value_collection_allowed": False,
"production_write_authorized": False,
}
def build_report(root: Path, intake_report: dict[str, Any], generated_at: str | None) -> dict[str, Any]:
report_time = generated_at or datetime.now(TAIPEI).isoformat(timespec="seconds")
intake_packets = intake_report.get("intake_packets", [])
drafts = [request_draft(packet, intake_report) for packet in intake_packets]
c0_drafts = [draft for draft in drafts if draft.get("control_tier") == "C0"]
c1_drafts = [draft for draft in drafts if draft.get("control_tier") == "C1"]
required_owner_field_total = sum(len(draft["allowed_response_format"]["fields"]) for draft in drafts)
return {
"schema_version": "high_value_config_owner_request_draft_v1",
"generated_at": report_time,
"git_commit": git_short_sha(root),
"source_intake_preflight_schema_version": intake_report.get("schema_version"),
"source_intake_preflight_status": intake_report.get("status"),
"status": "owner_request_draft_ready_not_dispatched",
"summary": {
"request_draft_count": len(drafts),
"c0_request_draft_count": len(c0_drafts),
"c1_request_draft_count": len(c1_drafts),
"dispatch_preflight_check_count": intake_report.get("summary", {}).get(
"dispatch_preflight_check_count", 0
),
"reviewer_intake_lane_count": intake_report.get("summary", {}).get("reviewer_intake_lane_count", 0),
"handoff_envelope_field_count": len(HANDOFF_ENVELOPE_FIELDS),
"required_owner_field_total": required_owner_field_total,
"forbidden_payload_count": len(FORBIDDEN_PAYLOADS),
"blocked_request_count": len(intake_report.get("blocked_requests", [])),
"request_sent_count": 0,
"recipient_confirmed_count": 0,
"audit_event_emitted_count": 0,
"received_response_count": 0,
"accepted_response_count": 0,
"rejected_response_count": 0,
"reviewer_queue_write_count": 0,
"runtime_gate_count": 0,
"action_button_count": 0,
},
"execution_boundaries": {
"dispatch_authorized": False,
"request_sent": False,
"recipient_confirmed": False,
"audit_event_emitted": False,
"reviewer_queue_write": False,
"runtime_execution_authorized": False,
"secret_value_collection_allowed": False,
"production_write_authorized": False,
"host_write_authorized": False,
"nginx_reload_authorized": False,
"dns_tls_change_authorized": False,
"workflow_modification_authorized": False,
"active_scan_authorized": False,
"action_buttons_allowed": False,
"not_authorization": True,
},
"handoff_envelope_fields": HANDOFF_ENVELOPE_FIELDS,
"forbidden_payloads": FORBIDDEN_PAYLOADS,
"not_approval_statement": NOT_APPROVAL_STATEMENT,
"request_drafts": drafts,
"send_after_conditions": [
"必須先重新確認 gitea/main、P0 總帳與另一個 AwoooP Session 基線。",
"只能送出脫敏欄位與禁止條款,不得附 secret value、raw payload 或執行命令。",
"只有真實人工送件 metadata 存在時,才能另行記錄 request_sent_count。",
"送件後不得同步拉高 received / accepted / rejected / reviewer queue / runtime gate。",
],
}
def main() -> int:
parser = argparse.ArgumentParser(description="IwoooS 高價值配置 owner request 草稿包產生器")
parser.add_argument("--root", default=".", help="repo root")
parser.add_argument(
"--intake-preflight-report",
default="docs/security/high-value-config-owner-packet-intake-preflight.snapshot.json",
help="high-value-config-owner-packet-intake-preflight.py 輸出的 JSON",
)
parser.add_argument("--output", help="寫出 JSON 報告")
parser.add_argument("--generated-at", help="固定報告時間,供 committed snapshot 使用")
args = parser.parse_args()
root = Path(args.root).resolve()
intake_report = load_json(root / args.intake_preflight_report)
report = build_report(root, intake_report, args.generated_at)
payload = json.dumps(report, ensure_ascii=False, indent=2, sort_keys=True)
if args.output:
output = Path(args.output)
output.parent.mkdir(parents=True, exist_ok=True)
output.write_text(payload + "\n", encoding="utf-8")
else:
print(payload)
summary = report["summary"]
print(
"HIGH_VALUE_CONFIG_OWNER_REQUEST_DRAFT_OK "
f"drafts={summary['request_draft_count']} "
f"c0={summary['c0_request_draft_count']} "
f"fields={summary['required_owner_field_total']} "
f"sent={summary['request_sent_count']} "
f"runtime_gate={summary['runtime_gate_count']}",
file=sys.stderr,
)
return 0
if __name__ == "__main__":
sys.exit(main())