feat(github): add safe credential intake readiness
This commit is contained in:
@@ -93,6 +93,14 @@ def build_github_target_private_backup_evidence_gate(
|
||||
}
|
||||
owner_summary = _dict(owner_response.get("summary"))
|
||||
owner_response_intake_readiness = _owner_response_intake_readiness(owner_response)
|
||||
safe_credential_evidence_intake_readiness = (
|
||||
_safe_credential_evidence_intake_readiness(
|
||||
owner_response=owner_response,
|
||||
required_target_count=len(
|
||||
[row for row in decisions if row.get("approval_required") is True]
|
||||
),
|
||||
)
|
||||
)
|
||||
owner_template_by_repo = {
|
||||
str(row.get("github_repo")): _dict(row)
|
||||
for row in _list(owner_response.get("response_templates"))
|
||||
@@ -214,6 +222,25 @@ def build_github_target_private_backup_evidence_gate(
|
||||
- private_backup_verified_count,
|
||||
"safe_credential_required_count": len(approval_required_targets),
|
||||
"safe_credential_accepted_evidence_count": 0,
|
||||
"safe_credential_evidence_intake_ready": safe_credential_evidence_intake_readiness[
|
||||
"intake_ready"
|
||||
],
|
||||
"safe_credential_required_redacted_evidence_ref_count": safe_credential_evidence_intake_readiness[
|
||||
"required_redacted_evidence_ref_count"
|
||||
],
|
||||
"safe_credential_evidence_ref_rule_count": safe_credential_evidence_intake_readiness[
|
||||
"evidence_ref_rule_count"
|
||||
],
|
||||
"safe_credential_redaction_example_count": safe_credential_evidence_intake_readiness[
|
||||
"redaction_example_count"
|
||||
],
|
||||
"safe_credential_forbidden_payload_count": safe_credential_evidence_intake_readiness[
|
||||
"forbidden_payload_count"
|
||||
],
|
||||
"safe_credential_quarantine_lane_count": safe_credential_evidence_intake_readiness[
|
||||
"quarantine_lane_count"
|
||||
],
|
||||
"safe_credential_raw_payload_storage_allowed": False,
|
||||
"owner_response_received_count": _int(
|
||||
owner_summary.get("received_response_count")
|
||||
),
|
||||
@@ -264,6 +291,7 @@ def build_github_target_private_backup_evidence_gate(
|
||||
"public_repo_allowed": False,
|
||||
},
|
||||
"owner_response_intake_readiness": owner_response_intake_readiness,
|
||||
"safe_credential_evidence_intake_readiness": safe_credential_evidence_intake_readiness,
|
||||
"targets": targets,
|
||||
"acceptance_requirements": _acceptance_requirements(owner_response),
|
||||
"rejection_rules": _rejection_rules(owner_response),
|
||||
@@ -367,6 +395,21 @@ def _build_target(
|
||||
"private_visibility_owner_evidence_ref": connector_readback.get("evidence_ref"),
|
||||
"safe_credential_evidence_status": "not_collected",
|
||||
"safe_credential_evidence_ref": None,
|
||||
"safe_credential_evidence_intake_ready": approval_required,
|
||||
"safe_credential_evidence_submission_status": "waiting_redacted_evidence_ref"
|
||||
if approval_required
|
||||
else "not_required",
|
||||
"safe_credential_required_redacted_evidence_ref": approval_required,
|
||||
"safe_credential_allowed_evidence_ref_types": [
|
||||
"repo_path",
|
||||
"snapshot_path",
|
||||
"redacted_metadata_pointer",
|
||||
]
|
||||
if approval_required
|
||||
else [],
|
||||
"safe_credential_raw_payload_storage_allowed": False,
|
||||
"safe_credential_private_clone_url_collection_allowed": False,
|
||||
"safe_credential_secret_value_collection_allowed": False,
|
||||
"owner_response_template_id": owner_response_template.get("template_id"),
|
||||
"owner_response_submission_status": "waiting_owner_response"
|
||||
if approval_required
|
||||
@@ -687,6 +730,9 @@ def _require_owner_response_intake_consistency(
|
||||
_dict(row) for row in _list(payload.get("intake_preflight_checks"))
|
||||
]
|
||||
acceptance_checks = [_dict(row) for row in _list(payload.get("acceptance_checks"))]
|
||||
redaction_examples = [
|
||||
_dict(row) for row in _list(payload.get("owner_response_redaction_examples"))
|
||||
]
|
||||
summary = _dict(payload.get("summary"))
|
||||
|
||||
if (
|
||||
@@ -712,6 +758,10 @@ def _require_owner_response_intake_consistency(
|
||||
raise ValueError(f"{label}: intake preflight check count must match checks")
|
||||
if _int(summary.get("acceptance_check_count")) != len(acceptance_checks):
|
||||
raise ValueError(f"{label}: acceptance check count must match checks")
|
||||
if _int(summary.get("owner_response_redaction_example_count")) != len(
|
||||
redaction_examples
|
||||
):
|
||||
raise ValueError(f"{label}: redaction example count must match examples")
|
||||
|
||||
template_ids = {
|
||||
str(row.get("template_id")) for row in templates if row.get("template_id")
|
||||
@@ -723,12 +773,18 @@ def _require_owner_response_intake_consistency(
|
||||
)
|
||||
|
||||
executable_rows = [
|
||||
str(row.get("template_id") or row.get("check_id") or "unknown")
|
||||
str(
|
||||
row.get("template_id")
|
||||
or row.get("check_id")
|
||||
or row.get("example_id")
|
||||
or "unknown"
|
||||
)
|
||||
for row in [
|
||||
*templates,
|
||||
*collection_checks,
|
||||
*preflight_checks,
|
||||
*acceptance_checks,
|
||||
*redaction_examples,
|
||||
]
|
||||
if row.get("execution_authorized") is not False
|
||||
]
|
||||
@@ -737,6 +793,31 @@ def _require_owner_response_intake_consistency(
|
||||
f"{label}: owner response intake rows must remain non-executing: {executable_rows}"
|
||||
)
|
||||
|
||||
raw_payload_examples = [
|
||||
str(row.get("example_id") or "unknown")
|
||||
for row in redaction_examples
|
||||
if row.get("stored_raw_payload_allowed") is not False
|
||||
]
|
||||
if raw_payload_examples:
|
||||
raise ValueError(
|
||||
f"{label}: redaction examples must not allow raw payload storage: {raw_payload_examples}"
|
||||
)
|
||||
|
||||
required_forbidden_payloads = {
|
||||
"token_value",
|
||||
"secret_value",
|
||||
"private_clone_url_credential",
|
||||
"repo_archive",
|
||||
"git_object_pack",
|
||||
}
|
||||
missing_forbidden_payloads = sorted(
|
||||
required_forbidden_payloads - set(_strings(packet.get("forbidden_payloads")))
|
||||
)
|
||||
if missing_forbidden_payloads:
|
||||
raise ValueError(
|
||||
f"{label}: forbidden payloads missing safe credential blockers: {missing_forbidden_payloads}"
|
||||
)
|
||||
|
||||
|
||||
def _owner_response_intake_readiness(owner_response: dict[str, Any]) -> dict[str, Any]:
|
||||
summary = _dict(owner_response.get("summary"))
|
||||
@@ -805,6 +886,104 @@ def _owner_response_intake_readiness(owner_response: dict[str, Any]) -> dict[str
|
||||
}
|
||||
|
||||
|
||||
def _safe_credential_evidence_intake_readiness(
|
||||
*,
|
||||
owner_response: dict[str, Any],
|
||||
required_target_count: int,
|
||||
) -> dict[str, Any]:
|
||||
summary = _dict(owner_response.get("summary"))
|
||||
packet = _dict(owner_response.get("owner_response_request_packet"))
|
||||
evidence_ref_rules = _strings(packet.get("evidence_ref_rules"))
|
||||
redaction_examples = [
|
||||
_redaction_example_summary(row)
|
||||
for row in _list(owner_response.get("owner_response_redaction_examples"))
|
||||
]
|
||||
forbidden_payloads = _strings(packet.get("forbidden_payloads"))
|
||||
checks = [
|
||||
*[
|
||||
_dict(row)
|
||||
for row in _list(owner_response.get("owner_response_collection_checks"))
|
||||
],
|
||||
*[_dict(row) for row in _list(owner_response.get("intake_preflight_checks"))],
|
||||
*[_dict(row) for row in _list(owner_response.get("acceptance_checks"))],
|
||||
]
|
||||
quarantine_lanes = sorted(
|
||||
{
|
||||
str(row.get("failure_lane"))
|
||||
for row in checks
|
||||
if str(row.get("failure_lane") or "").startswith("quarantine")
|
||||
}
|
||||
)
|
||||
accepted_evidence_count = 0
|
||||
required_redacted_evidence_ref_count = (
|
||||
required_target_count - accepted_evidence_count
|
||||
)
|
||||
required_forbidden_payloads = {
|
||||
"token_value",
|
||||
"secret_value",
|
||||
"private_clone_url_credential",
|
||||
"repo_archive",
|
||||
"git_object_pack",
|
||||
}
|
||||
intake_ready = (
|
||||
packet.get("execution_authorized") is False
|
||||
and packet.get("not_approval") is True
|
||||
and bool(evidence_ref_rules)
|
||||
and bool(redaction_examples)
|
||||
and _int(summary.get("owner_response_redaction_example_count"))
|
||||
== len(redaction_examples)
|
||||
and required_forbidden_payloads.issubset(set(forbidden_payloads))
|
||||
)
|
||||
|
||||
return {
|
||||
"status": "ready_to_collect_redacted_evidence_refs_not_credentials"
|
||||
if intake_ready
|
||||
else "blocked_safe_credential_evidence_intake_contract_incomplete",
|
||||
"intake_ready": intake_ready,
|
||||
"required_target_count": required_target_count,
|
||||
"accepted_evidence_count": accepted_evidence_count,
|
||||
"required_redacted_evidence_ref_count": required_redacted_evidence_ref_count,
|
||||
"allowed_evidence_ref_types": [
|
||||
"repo_path",
|
||||
"snapshot_path",
|
||||
"redacted_metadata_pointer",
|
||||
],
|
||||
"evidence_ref_rule_count": len(evidence_ref_rules),
|
||||
"evidence_ref_rules": evidence_ref_rules,
|
||||
"redaction_example_count": len(redaction_examples),
|
||||
"redaction_examples": redaction_examples,
|
||||
"forbidden_payload_count": len(forbidden_payloads),
|
||||
"forbidden_payloads": forbidden_payloads,
|
||||
"quarantine_lane_count": len(quarantine_lanes),
|
||||
"quarantine_lanes": quarantine_lanes,
|
||||
"allowed_submission_modes": _strings(packet.get("allowed_submission_modes")),
|
||||
"stored_raw_payload_allowed": False,
|
||||
"secret_value_collection_allowed": False,
|
||||
"private_clone_url_collection_allowed": False,
|
||||
"credential_value_collection_allowed": False,
|
||||
"target_owner_request_dispatch_authorized": False,
|
||||
"action_buttons_allowed": False,
|
||||
"execution_authorized": False,
|
||||
"not_approval": True,
|
||||
}
|
||||
|
||||
|
||||
def _redaction_example_summary(value: Any) -> dict[str, Any]:
|
||||
row = _dict(value)
|
||||
return {
|
||||
"example_id": str(row.get("example_id") or ""),
|
||||
"display_order": _int(row.get("display_order")),
|
||||
"example_status": str(row.get("example_status") or ""),
|
||||
"category": str(row.get("category") or ""),
|
||||
"safe_response_shape": _strings(row.get("safe_response_shape")),
|
||||
"required_redactions": _strings(row.get("required_redactions")),
|
||||
"forbidden_raw_values": _strings(row.get("forbidden_raw_values")),
|
||||
"stored_raw_payload_allowed": False,
|
||||
"execution_authorized": False,
|
||||
"not_approval": row.get("not_approval") is True,
|
||||
}
|
||||
|
||||
|
||||
def _owner_response_template_summary(value: Any) -> dict[str, Any]:
|
||||
row = _dict(value)
|
||||
return {
|
||||
@@ -888,6 +1067,6 @@ def _strings(value: Any) -> list[str]:
|
||||
def _int(value: Any) -> int:
|
||||
if isinstance(value, bool):
|
||||
return int(value)
|
||||
if isinstance(value, (int, float)):
|
||||
if isinstance(value, int | float):
|
||||
return int(value)
|
||||
return 0
|
||||
|
||||
Reference in New Issue
Block a user