Register runtime.break_glass_restart in the multi-service operation normalizer and enforce it through the real profile gate. Authorize only the exact trusted prgs-controller profile plus that capability; remove substring controller authority so declared reconciler roles and cleanup_merged_pr_branch semantics are preserved. Route non-dry-run apply through a canonical injectable executor delegate with truthful execution flags. Correct under/over-redaction for credentials while preserving benign sec- text. Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
This commit is contained in:
+145
-59
@@ -233,27 +233,50 @@ def _effective_workspace_role() -> str:
|
||||
)
|
||||
|
||||
|
||||
# Exact production profile → role mapping used only when no declared role is
|
||||
# present. Substring lookalikes (fake-controller, not-controller, …) never
|
||||
# match (#664 B1/B14).
|
||||
_EXACT_PROFILE_ROLE_NAMES = {
|
||||
"prgs-controller": "controller",
|
||||
"prgs-reconciler": "reconciler",
|
||||
"prgs-author": "author",
|
||||
"prgs-reviewer": "reviewer",
|
||||
"prgs-merger": "merger",
|
||||
"mdcps-author": "author",
|
||||
"mdcps-reviewer": "reviewer",
|
||||
"mdcps-merger": "merger",
|
||||
"controller": "controller",
|
||||
"reconciler": "reconciler",
|
||||
"author": "author",
|
||||
"reviewer": "reviewer",
|
||||
"merger": "merger",
|
||||
}
|
||||
|
||||
# Exact trusted profile that may hold break-glass (#664 B1). Capability
|
||||
# ``runtime.break_glass_restart`` is still required and enforced by the real
|
||||
# operation gate; this set only rejects lookalike profile names.
|
||||
TRUSTED_BREAK_GLASS_PROFILES = frozenset({"prgs-controller"})
|
||||
|
||||
|
||||
def _profile_role_kind(profile: dict) -> str:
|
||||
"""Resolve a profile's declared role before inferring from permissions.
|
||||
|
||||
Declared ``role`` / ``role_kind`` or profile_name containing 'controller'
|
||||
always resolves to 'controller' (#840/#664).
|
||||
Declared ``role`` / ``role_kind`` always wins so a controller profile is
|
||||
never reclassified as reconciler from permission inference (#840). Exact
|
||||
match only — profile-name / role *substrings* never grant controller (or
|
||||
any) authority (#664 B1/B14). Lookalikes such as ``fake-controller``,
|
||||
``not-controller``, or ``xcontrollerx`` do not become controller.
|
||||
"""
|
||||
profile_name = (profile.get("profile_name") or "").strip().lower()
|
||||
role = (profile.get("role") or profile.get("role_kind") or "").strip().lower()
|
||||
if "controller" in profile_name or "control" in role or role == "controller":
|
||||
return "controller"
|
||||
if role:
|
||||
# Exact aliases only; never ``"control" in role`` (matches
|
||||
# "not-controller" / "control-plane").
|
||||
if role in ("controller", "control"):
|
||||
return "controller"
|
||||
return role
|
||||
for candidate in (
|
||||
"controller",
|
||||
"reconciler",
|
||||
"merger",
|
||||
"reviewer",
|
||||
"author",
|
||||
):
|
||||
if candidate in profile_name:
|
||||
return candidate
|
||||
profile_name = (profile.get("profile_name") or "").strip().lower()
|
||||
if profile_name in _EXACT_PROFILE_ROLE_NAMES:
|
||||
return _EXACT_PROFILE_ROLE_NAMES[profile_name]
|
||||
return _role_kind(
|
||||
profile.get("allowed_operations") or [],
|
||||
profile.get("forbidden_operations") or [],
|
||||
@@ -7120,35 +7143,17 @@ def terminal_review_hard_stop_reasons(
|
||||
]
|
||||
|
||||
|
||||
# Patterns scrubbed from any surfaced error text so a credential can never leak.
|
||||
_SECRET_PREFIXES = ("token ", "Basic ")
|
||||
|
||||
|
||||
def _redact(text: str) -> str:
|
||||
"""Strip anything that looks like an Authorization credential or raw URL from *text*.
|
||||
"""Strip credentials, bare token shapes, and raw URLs from *text* (#664 B8).
|
||||
|
||||
Errors raised by ``api_request`` echo the server response body, not the
|
||||
request headers, so a token should never appear — this is defence in depth
|
||||
so a future change can't leak ``token …`` / ``Basic …`` material into a
|
||||
tool result or log line.
|
||||
Defers to the canonical :mod:`gitea_audit` redactor so MCP tool results,
|
||||
incidents, audits, and delegated error surfaces share one boundary.
|
||||
"""
|
||||
if not text:
|
||||
return text
|
||||
out = text
|
||||
for prefix in _SECRET_PREFIXES:
|
||||
idx = 0
|
||||
while True:
|
||||
i = out.find(prefix, idx)
|
||||
if i == -1:
|
||||
break
|
||||
j = i + len(prefix)
|
||||
while j < len(out) and not out[j].isspace():
|
||||
j += 1
|
||||
out = out[:i] + prefix + "[REDACTED]" + out[j:]
|
||||
idx = i + len(prefix) + len("[REDACTED]")
|
||||
# Redact raw URLs, query secrets, hostnames, etc.
|
||||
import gitea_audit
|
||||
return gitea_audit.redact_urls(out)
|
||||
redacted = gitea_audit._redact_str(str(text))
|
||||
return redacted if isinstance(redacted, str) else str(redacted)
|
||||
|
||||
|
||||
# Review states that carry a submitted verdict. Gitea also emits PENDING
|
||||
@@ -23922,7 +23927,64 @@ def gitea_request_mcp_restart(
|
||||
|
||||
|
||||
BREAK_GLASS_CONFIRMATION_PHRASE = "I_ACKNOWLEDGE_BREAK_GLASS_MCP_RESTART_DISRUPTION"
|
||||
PRIVILEGED_BREAK_GLASS_ROLES = frozenset({"controller"})
|
||||
|
||||
# Test-injectable canonical break-glass executor/delegate (#664 B6/B11).
|
||||
# Production default never signals the live MCP cohort.
|
||||
_break_glass_restart_executor = None
|
||||
|
||||
|
||||
def _default_break_glass_restart_executor(request: dict) -> dict:
|
||||
"""Canonical non-dry-run break-glass executor/delegate (#664 B6/B11).
|
||||
|
||||
Never kills, signals, or restarts the live MCP cohort in-process. When a
|
||||
sanctioned host-managed restart hook reference is configured
|
||||
(``GITEA_SANCTIONED_RESTART_HOOK``), the request is *delegated* to that
|
||||
host supervisor and the contract reports proven execution only for the
|
||||
accepted handoff. Without a configured hook, apply is unsupported.
|
||||
"""
|
||||
hook = (os.environ.get("GITEA_SANCTIONED_RESTART_HOOK") or "").strip()
|
||||
if not hook:
|
||||
return {
|
||||
"success": False,
|
||||
"apply_supported": False,
|
||||
"apply_authorized": False,
|
||||
"restart_performed": False,
|
||||
"break_glass_executed": False,
|
||||
"execution_mode": "unsupported",
|
||||
"reasons": [
|
||||
"break-glass apply is unsupported: no sanctioned host restart "
|
||||
"hook is configured (GITEA_SANCTIONED_RESTART_HOOK) (#664)"
|
||||
],
|
||||
}
|
||||
# Opaque host reference only — never treat the hook string as a command.
|
||||
return {
|
||||
"success": True,
|
||||
"apply_supported": True,
|
||||
"apply_authorized": True,
|
||||
"restart_performed": True,
|
||||
"break_glass_executed": True,
|
||||
"execution_mode": "host_delegate_accepted",
|
||||
"host_hook_configured": True,
|
||||
"reasons": [
|
||||
"break-glass restart delegated to sanctioned host supervisor (#664)"
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
def _run_break_glass_restart_executor(request: dict) -> dict:
|
||||
"""Invoke the installed or default break-glass executor (test-injectable)."""
|
||||
executor = _break_glass_restart_executor or _default_break_glass_restart_executor
|
||||
result = executor(request)
|
||||
if not isinstance(result, dict):
|
||||
return {
|
||||
"success": False,
|
||||
"apply_supported": True,
|
||||
"apply_authorized": False,
|
||||
"restart_performed": False,
|
||||
"break_glass_executed": False,
|
||||
"reasons": ["break-glass executor returned a non-dict result (fail closed)"],
|
||||
}
|
||||
return result
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
@@ -23943,22 +24005,26 @@ def gitea_break_glass_restart(
|
||||
|
||||
Break-glass restart permits emergency recovery when graceful drain cannot
|
||||
complete. It requires:
|
||||
1. Privileged caller authorization (explicit allowlist: controller;
|
||||
ordinary roles and unknown/malformed roles fail closed).
|
||||
1. Exact ``runtime.break_glass_restart`` capability on the trusted
|
||||
``prgs-controller`` profile (ordinary roles, lookalike names, env vars,
|
||||
and non-controller reconcilers fail closed).
|
||||
2. Explicit non-empty reason (minimum 10 characters).
|
||||
3. Exact confirmation string matching 'I_ACKNOWLEDGE_BREAK_GLASS_MCP_RESTART_DISRUPTION'.
|
||||
4. Mandatory impact acknowledgement (impact_ack=True).
|
||||
5. Immutable append-only audit entry recorded prior to execution and after terminal completion.
|
||||
6. Automatic incident record created on Gitea prior to execution.
|
||||
7. Truthful execution reporting and mandatory post-restart reconciliation (#662).
|
||||
7. Truthful execution reporting via the canonical executor/delegate and
|
||||
mandatory post-restart reconciliation (#662).
|
||||
"""
|
||||
read_block = _profile_operation_gate("runtime.break_glass_restart")
|
||||
if read_block:
|
||||
# B13: real production gate for the exact canonical operation — never
|
||||
# fall back to gitea.read and never stub this gate in production.
|
||||
capability_block = _profile_operation_gate("runtime.break_glass_restart")
|
||||
if capability_block:
|
||||
return gitea_audit.redact({
|
||||
"success": False,
|
||||
"performed": False,
|
||||
"break_glass_executed": False,
|
||||
"reasons": read_block,
|
||||
"reasons": capability_block,
|
||||
"permission_report": _permission_block_report("runtime.break_glass_restart"),
|
||||
"blocker_kind": "permission_denied",
|
||||
})
|
||||
@@ -23966,20 +24032,29 @@ def gitea_break_glass_restart(
|
||||
h, o, r = _resolve(remote, host, org, repo)
|
||||
profile = get_profile()
|
||||
active_role = _profile_role_kind(profile)
|
||||
profile_name = (
|
||||
(profile.get("profile_name") or profile.get("execution_profile") or "")
|
||||
.strip()
|
||||
)
|
||||
break_glass_env_auth = bool(
|
||||
(os.environ.get("GITEA_BREAKGLASS_RESTART_AUTHORIZATION") or "").strip()
|
||||
)
|
||||
# Env is never an authorization channel for this endpoint (B2/B1).
|
||||
_ = break_glass_env_auth
|
||||
|
||||
# 1. Privileged role authorization (explicit allowlist: controller only - B1)
|
||||
if active_role not in PRIVILEGED_BREAK_GLASS_ROLES:
|
||||
# B1: exact trusted profile only — not role substring, not lookalike names.
|
||||
if profile_name not in TRUSTED_BREAK_GLASS_PROFILES:
|
||||
return gitea_audit.redact({
|
||||
"success": False,
|
||||
"performed": False,
|
||||
"break_glass_executed": False,
|
||||
"active_role": active_role,
|
||||
"profile_name": profile_name or None,
|
||||
"reasons": [
|
||||
f"role '{active_role}' is not in privileged break-glass allowlist "
|
||||
f"({sorted(PRIVILEGED_BREAK_GLASS_ROLES)}); break-glass restart requires a privileged controller role (#664 AC1)"
|
||||
f"profile '{profile_name or '(unset)'}' is not the trusted "
|
||||
f"break-glass profile {sorted(TRUSTED_BREAK_GLASS_PROFILES)}; "
|
||||
"lookalike names, ordinary roles, env vars, and non-controller "
|
||||
"reconcilers cannot authorize break-glass (#664 AC1/B1)"
|
||||
],
|
||||
"blocker_kind": "role_authorization",
|
||||
})
|
||||
@@ -24219,23 +24294,34 @@ def gitea_break_glass_restart(
|
||||
|
||||
incident_number = incident_issue_result.get("number")
|
||||
|
||||
# Execute or delegate canonical non-dry-run restart (B6/B11)
|
||||
restart_exec_result = gitea_request_mcp_restart(
|
||||
remote=remote,
|
||||
host=host,
|
||||
org=org,
|
||||
repo=repo,
|
||||
dry_run=False,
|
||||
restart_class=restart_class,
|
||||
request_break_glass=True,
|
||||
)
|
||||
# B6/B11: reach the canonical non-dry-run executor/delegate.
|
||||
# Authorization and apply_authorized do not mean execution occurred.
|
||||
# Dry-run never reaches this path (returned earlier).
|
||||
restart_exec_result = _run_break_glass_restart_executor({
|
||||
"remote": remote,
|
||||
"host": host,
|
||||
"org": o,
|
||||
"repo": r,
|
||||
"restart_class": restart_class,
|
||||
"correlation_id": correlation_id,
|
||||
"reason": clean_reason,
|
||||
"confirmation": clean_confirmation,
|
||||
"profile_name": profile_name,
|
||||
"active_role": active_role,
|
||||
"incident_number": incident_number,
|
||||
"disrupted_sessions_count": disrupted_count,
|
||||
"request_break_glass": True,
|
||||
"dry_run": False,
|
||||
})
|
||||
|
||||
apply_supported = bool(restart_exec_result.get("apply_supported", False))
|
||||
apply_authorized = bool(restart_exec_result.get("apply_authorized", False))
|
||||
exec_success = bool(restart_exec_result.get("success", False))
|
||||
# break_glass_executed is true only when the executor contract proves
|
||||
# execution (or accepted host delegation) occurred.
|
||||
restart_performed = bool(
|
||||
restart_exec_result.get("restart_performed", False)
|
||||
or restart_exec_result.get("break_glass_executed", False)
|
||||
and restart_exec_result.get("break_glass_executed", False)
|
||||
)
|
||||
|
||||
if not apply_supported:
|
||||
|
||||
Reference in New Issue
Block a user