fix(mcp): remediate B8, B6/B11, and B15 break-glass restart blockers (#664)
- B8: Correct redaction boundary for GITEA_TOKEN= and URI userinfo without destroying adjacent audit evidence or benign sec- text - B6/B11: Remove false restart execution claims from default executor when GITEA_SANCTIONED_RESTART_HOOK is non-empty - B15: Document deployable production grant set (runtime.break_glass_restart and gitea.issue.create) for prgs-controller - Preserve B13, B1, B14 and previously accepted corrections
This commit is contained in:
+22
-15
@@ -76,17 +76,16 @@ _BARE_SECRET_PATTERN = re.compile(
|
||||
r')\b'
|
||||
)
|
||||
|
||||
# Key/value credentials embedded in free text (password=..., api_key: ..., etc.).
|
||||
# Value group accepts quoted strings, Authorization scheme + credential
|
||||
# (Bearer/Basic/Token + token), or a single non-space run.
|
||||
# Key/value credentials embedded in free text (password=..., api_key: ..., GITEA_TOKEN=..., etc.).
|
||||
# Group 1 captures the key name (e.g. GITEA_TOKEN, password, api_key).
|
||||
# Group 2 captures delimiter/whitespace (=, : ).
|
||||
# Group 3 captures the secret value, stopping at whitespace or non-secret delimiters (&, ;, ,, quotes, closing brackets).
|
||||
_ASSIGNMENT_SECRET_PATTERN = re.compile(
|
||||
r'(?i)\b('
|
||||
r'token|password|passwd|pwd|secret|api[_-]?key|access[_-]?key|'
|
||||
r'client[_-]?secret|private[_-]?key|authorization|credential'
|
||||
r')\b(\s*[:=]\s*)('
|
||||
r'(?i)\b([A-Za-z0-9_]*?(?:token|password|passwd|pwd|secret|api[_-]?key|access[_-]?key|'
|
||||
r'client[_-]?secret|private[_-]?key|authorization|credential))\b(\s*[:=]\s*)('
|
||||
r'"[^"]*"|\'[^\']*\'|'
|
||||
r'(?:Bearer|Basic|Token)\s+\S+|'
|
||||
r'\S+'
|
||||
r'(?:Bearer|Basic|Token)\s+[^\s;&,"\'\)\}\]\>]+|'
|
||||
r'[^\s;&,"\'\)\}\]\>]+'
|
||||
r')'
|
||||
)
|
||||
|
||||
@@ -120,7 +119,8 @@ def redact_urls(text: str) -> str:
|
||||
if not isinstance(text, str) or not text:
|
||||
return text
|
||||
|
||||
url_pattern = re.compile(r'(https?://[^\s)>\]}]+)', re.IGNORECASE)
|
||||
# Match any URI scheme (http, https, postgres, mysql, mongodb, redis, etc.)
|
||||
url_pattern = re.compile(r'([a-z0-9\+\.\-]+://[^\s)>\]}]+)', re.IGNORECASE)
|
||||
|
||||
def replace_url(match):
|
||||
url_str = match.group(1)
|
||||
@@ -134,11 +134,11 @@ def redact_urls(text: str) -> str:
|
||||
is_synthetic = True
|
||||
break
|
||||
|
||||
if is_synthetic:
|
||||
# Rebuild synthetic URL to redact any credentials or query secrets
|
||||
if is_synthetic or (parsed.username or parsed.password) or parsed.scheme.lower() not in ("http", "https"):
|
||||
# Rebuild URL to redact any credentials or query secrets
|
||||
new_netloc = parsed.netloc
|
||||
if parsed.username or parsed.password:
|
||||
netloc_clean = parsed.hostname
|
||||
netloc_clean = parsed.hostname or ""
|
||||
if parsed.port:
|
||||
netloc_clean = f"{netloc_clean}:{parsed.port}"
|
||||
new_netloc = f"[REDACTED_USER]:[REDACTED_PASS]@{netloc_clean}"
|
||||
@@ -177,11 +177,17 @@ def redact_urls(text: str) -> str:
|
||||
|
||||
def _mask_assignment(match: re.Match) -> str:
|
||||
"""Keep the key and separator; replace only the secret value."""
|
||||
val = match.group(3)
|
||||
if val.startswith(REDACTED) or val.startswith("%5BREDACTED") or val.startswith("[REDACTED"):
|
||||
return f"{match.group(1)}{match.group(2)}{val}"
|
||||
return f"{match.group(1)}{match.group(2)}{REDACTED}"
|
||||
|
||||
|
||||
def _mask_conn_secret(match: re.Match) -> str:
|
||||
"""Keep the connection-string key; replace only the credential value."""
|
||||
val = match.group(2)
|
||||
if val.startswith(REDACTED) or val.startswith("%5BREDACTED") or val.startswith("[REDACTED"):
|
||||
return f"{match.group(1)}{val}"
|
||||
return f"{match.group(1)}{REDACTED}"
|
||||
|
||||
|
||||
@@ -195,7 +201,8 @@ def _redact_str(text):
|
||||
"""
|
||||
if not isinstance(text, str) or not text:
|
||||
return text
|
||||
out = _BARE_SECRET_PATTERN.sub(REDACTED, text)
|
||||
out = redact_urls(text)
|
||||
out = _BARE_SECRET_PATTERN.sub(REDACTED, out)
|
||||
out = _ASSIGNMENT_SECRET_PATTERN.sub(_mask_assignment, out)
|
||||
out = _CONN_STRING_SECRET_PATTERN.sub(_mask_conn_secret, out)
|
||||
out_lower = out.lower()
|
||||
@@ -212,7 +219,7 @@ def _redact_str(text):
|
||||
out = out[:i] + prefix + REDACTED + out[j:]
|
||||
out_lower = out.lower()
|
||||
idx = i + len(prefix) + len(REDACTED)
|
||||
return redact_urls(out)
|
||||
return out
|
||||
|
||||
|
||||
def redact(value):
|
||||
|
||||
Reference in New Issue
Block a user