Merge remote-tracking branch 'gitea-ssh/main' into codex/p0-product-manifest-standard-20260629
This commit is contained in:
@@ -49378,3 +49378,18 @@ production browser smoke:
|
||||
|
||||
**未做**:
|
||||
- 沒有讀 secret、token、`.env`、raw sessions / SQLite / auth;沒有寫 credential marker;沒有 host / Docker / Nginx / firewall / K3s / DB 操作;沒有使用 GitHub。
|
||||
|
||||
## 2026-06-29 — 14:04 P0-005 placeholder preflight guard hardening
|
||||
|
||||
**完成內容**:
|
||||
- 修正 `scripts/reboot-recovery/dr-escrow-evidence-checklist.py`,owner response skeleton 改用既有 preflight 可辨識的 fail-closed placeholder,不再輸出可能被誤當真實 ref 的 `<NON_SECRET_REF_FOR_...>`。
|
||||
- 強化 `scripts/reboot-recovery/post-reboot-owner-response-preflight.py`:拒收 `<...>`、`YYYY-MM-DD` 與 fake example ref prefix,避免 placeholder 被改成 `accepted` 後誤過。
|
||||
- 補測 `scripts/reboot-recovery/tests/test_dr_escrow_evidence_checklist.py`,覆蓋未填 skeleton fail-closed 與 angle-bracket placeholder ref 不可 accepted。
|
||||
|
||||
**本地驗證結果**:
|
||||
- `python3.11 -m py_compile scripts/reboot-recovery/dr-escrow-evidence-checklist.py scripts/reboot-recovery/post-reboot-owner-response-preflight.py`:通過。
|
||||
- `python3.11 -m pytest scripts/reboot-recovery/tests/test_dr_escrow_evidence_checklist.py scripts/reboot-recovery/tests/test_post_reboot_owner_response_template.py -q`:`7 passed`。
|
||||
- `python3 scripts/reboot-recovery/dr-escrow-evidence-checklist.py --output /tmp/awoooi-dr-escrow-evidence-checklist-20260629-fixed.json` + `jq empty`:通過。
|
||||
|
||||
**仍維持**:
|
||||
- 未寫 credential marker、未讀 secret / token / `.env` / raw sessions / SQLite / auth,未做 host / Docker / Nginx / firewall / K3s / DB 操作,未使用 GitHub。
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"schema_version": "awoooi_priority_work_order_readback_v1",
|
||||
"generated_at": "2026-06-29T14:00:19+08:00",
|
||||
"generated_at": "2026-06-29T14:05:53+08:00",
|
||||
"status": "p0_005_dr_escrow_checklist_ready_waiting_redacted_refs",
|
||||
"source_refs": {
|
||||
"global_scorecard": "~/.codex/product-runtime-governance-completion-scorecard.snapshot.json",
|
||||
@@ -13,7 +13,7 @@
|
||||
"dr_escrow_evidence_checklist_generator": "scripts/reboot-recovery/dr-escrow-evidence-checklist.py"
|
||||
},
|
||||
"current_head": {
|
||||
"gitea_main_sha": "bd55386e6edc46ce0b188011b171191b7773c5ba",
|
||||
"gitea_main_sha": "c98e6d5e5309dad9f0be4399a3d2e1fe2ec669ca",
|
||||
"latest_successful_deploy_marker": "9362588ce chore(cd): deploy a423301 [skip ci]",
|
||||
"latest_successful_deployed_source_sha": "a4233017ad5fd03977233f3db6a4bb45d71507ed",
|
||||
"latest_source_readiness_commit_sha": "0c8d4e88c39157b92322fa41a92e6b15c317ac49",
|
||||
@@ -118,7 +118,8 @@
|
||||
"credential_marker_write_authorized_count": 0,
|
||||
"secret_value_collection_allowed": false,
|
||||
"checklist_generator_present": true,
|
||||
"checklist_schema_version": "awoooi_dr_escrow_evidence_checklist_v1"
|
||||
"checklist_schema_version": "awoooi_dr_escrow_evidence_checklist_v1",
|
||||
"placeholder_preflight_guard_present": true
|
||||
},
|
||||
"missing_items": [
|
||||
"restic_repository_password",
|
||||
|
||||
@@ -41,11 +41,11 @@ def evidence_item(item_id: str) -> dict[str, Any]:
|
||||
"last_reviewed_at",
|
||||
"contains_secret_value=false",
|
||||
],
|
||||
"accepted_ref_examples": [
|
||||
f"vault-item-id-for-{item_id}",
|
||||
f"sealed-envelope-id-for-{item_id}",
|
||||
f"recovery-checklist-id-for-{item_id}",
|
||||
f"ticket-id-for-{item_id}",
|
||||
"accepted_ref_classes": [
|
||||
"password_manager_item_id",
|
||||
"sealed_envelope_id",
|
||||
"recovery_checklist_id",
|
||||
"review_ticket_id",
|
||||
],
|
||||
"rejected_values": [
|
||||
"passwords",
|
||||
@@ -84,13 +84,13 @@ def owner_response_skeleton() -> dict[str, Any]:
|
||||
"responses": [
|
||||
{
|
||||
"gate_id": GATE_ID,
|
||||
"owner_role": "backup_dr_owner",
|
||||
"owner_team": "platform_security",
|
||||
"owner_role": "owner_role_here",
|
||||
"owner_team": "owner_team_here",
|
||||
"decision": "pending",
|
||||
"decision_reason": "fill_only_after_all_redacted_refs_are_present",
|
||||
"decision_reason": "decision_reason_here",
|
||||
"affected_scope": "P0-005 DR credential escrow evidence",
|
||||
"redacted_evidence_refs": ["<ONE_PARENT_TICKET_OR_REVIEW_REF>"],
|
||||
"followup_owner": "backup_dr_owner",
|
||||
"redacted_evidence_refs": ["redacted_evidence_ref_here"],
|
||||
"followup_owner": "followup_owner_here",
|
||||
"runtime_action_requested": False,
|
||||
"runtime_action_authorized": False,
|
||||
"host_write_requested": False,
|
||||
@@ -102,10 +102,10 @@ def owner_response_skeleton() -> dict[str, Any]:
|
||||
"escrow_items": [
|
||||
{
|
||||
"item_id": item_id,
|
||||
"non_secret_evidence_ref": f"<NON_SECRET_REF_FOR_{item_id}>",
|
||||
"recovery_owner": "backup_dr_owner",
|
||||
"reviewer": "security_reviewer",
|
||||
"last_reviewed_at": "YYYY-MM-DD",
|
||||
"non_secret_evidence_ref": "non_secret_evidence_ref_here",
|
||||
"recovery_owner": "owner_role_here",
|
||||
"reviewer": "reviewer_here",
|
||||
"last_reviewed_at": "pending",
|
||||
"contains_secret_value": False,
|
||||
}
|
||||
for item_id in ITEMS
|
||||
|
||||
@@ -172,7 +172,22 @@ def normalized(value: Any) -> str:
|
||||
|
||||
|
||||
def is_placeholder(value: Any) -> bool:
|
||||
return normalized(value).lower() in PLACEHOLDER_VALUES
|
||||
text = normalized(value)
|
||||
lower = text.lower()
|
||||
if lower in PLACEHOLDER_VALUES:
|
||||
return True
|
||||
if re.fullmatch(r"<[^<>]+>", text):
|
||||
return True
|
||||
if lower in {"yyyy-mm-dd", "yyyy/mm/dd"}:
|
||||
return True
|
||||
return lower.startswith(
|
||||
(
|
||||
"vault-item-id-for-",
|
||||
"sealed-envelope-id-for-",
|
||||
"recovery-checklist-id-for-",
|
||||
"ticket-id-for-",
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def collect_strings(value: Any, path: str = "$") -> list[tuple[str, str]]:
|
||||
|
||||
@@ -8,6 +8,7 @@ from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[3]
|
||||
SCRIPT = ROOT / "scripts" / "reboot-recovery" / "dr-escrow-evidence-checklist.py"
|
||||
PREFLIGHT_SCRIPT = ROOT / "scripts" / "reboot-recovery" / "post-reboot-owner-response-preflight.py"
|
||||
|
||||
ITEMS = {
|
||||
"restic_repository_password",
|
||||
@@ -28,6 +29,28 @@ def load_checklist() -> dict:
|
||||
return json.loads(result.stdout)
|
||||
|
||||
|
||||
def run_preflight(tmp_path: Path, owner_packet: dict, response: dict) -> dict:
|
||||
packet_path = tmp_path / "owner-packet.json"
|
||||
response_path = tmp_path / "owner-response.json"
|
||||
packet_path.write_text(json.dumps(owner_packet, indent=2) + "\n", encoding="utf-8")
|
||||
response_path.write_text(json.dumps(response, indent=2) + "\n", encoding="utf-8")
|
||||
result = subprocess.run(
|
||||
[
|
||||
sys.executable,
|
||||
str(PREFLIGHT_SCRIPT),
|
||||
"--owner-packet-file",
|
||||
str(packet_path),
|
||||
"--response-file",
|
||||
str(response_path),
|
||||
"--json",
|
||||
],
|
||||
text=True,
|
||||
capture_output=True,
|
||||
check=True,
|
||||
)
|
||||
return json.loads(result.stdout)
|
||||
|
||||
|
||||
def test_checklist_is_single_p0_005_intake_packet() -> None:
|
||||
payload = load_checklist()
|
||||
|
||||
@@ -73,3 +96,57 @@ def test_checklist_outputs_marker_dry_run_commands_only() -> None:
|
||||
assert " --evidence-id <NON_SECRET_REF_FOR_" in command
|
||||
assert "--token" not in command
|
||||
assert "password=" not in command.lower()
|
||||
|
||||
|
||||
def test_unfilled_skeleton_fails_closed_against_preflight(tmp_path: Path) -> None:
|
||||
payload = load_checklist()
|
||||
|
||||
preflight = run_preflight(
|
||||
tmp_path,
|
||||
payload["owner_packet"],
|
||||
payload["owner_response_skeleton"],
|
||||
)
|
||||
|
||||
assert preflight["status"] == "blocked_waiting_owner_response_content"
|
||||
assert preflight["owner_response_received_count"] == 0
|
||||
assert preflight["owner_response_accepted_count"] == 0
|
||||
assert preflight["runtime_action_authorized"] is False
|
||||
assert preflight["secret_value_collection_allowed"] is False
|
||||
blockers = "\n".join(preflight["blockers"])
|
||||
assert "credential_escrow_evidence.owner_role_missing" in blockers
|
||||
assert "credential_escrow_evidence.restic_repository_password.non_secret_evidence_ref_missing" in blockers
|
||||
|
||||
|
||||
def test_angle_bracket_placeholder_refs_cannot_be_accepted(tmp_path: Path) -> None:
|
||||
payload = load_checklist()
|
||||
response = payload["owner_response_skeleton"]
|
||||
item = response["responses"][0]
|
||||
item.update(
|
||||
{
|
||||
"owner_role": "backup_dr_owner",
|
||||
"owner_team": "platform_security",
|
||||
"decision": "accepted",
|
||||
"decision_reason": "reviewed redacted evidence refs only",
|
||||
"redacted_evidence_refs": ["review-ticket-20260629"],
|
||||
"followup_owner": "backup_dr_owner",
|
||||
}
|
||||
)
|
||||
for escrow_item in item["escrow_items"]:
|
||||
item_id = escrow_item["item_id"]
|
||||
escrow_item.update(
|
||||
{
|
||||
"non_secret_evidence_ref": f"<NON_SECRET_REF_FOR_{item_id}>",
|
||||
"recovery_owner": "backup_dr_owner",
|
||||
"reviewer": "security_reviewer",
|
||||
"last_reviewed_at": "2026-06-29",
|
||||
}
|
||||
)
|
||||
|
||||
preflight = run_preflight(tmp_path, payload["owner_packet"], response)
|
||||
|
||||
assert preflight["status"] == "blocked_waiting_owner_response_content"
|
||||
assert preflight["owner_response_received_count"] == 0
|
||||
assert preflight["owner_response_accepted_count"] == 0
|
||||
blockers = "\n".join(preflight["blockers"])
|
||||
for item_id in ITEMS:
|
||||
assert f"credential_escrow_evidence.{item_id}.non_secret_evidence_ref_missing" in blockers
|
||||
|
||||
Reference in New Issue
Block a user