"""Shared error-string sanitizer (phase 84 — audit SEC-08 / SEC-13). ``sanitize_error`` masks ``user:pass@`` userinfo in an error string so no error surface ever ships an embedded credential into the UI, a response body, or an SSE frame. It is the verbatim lift of the phase-32 sync masker (same regex, same ``*****@`` replacement — byte-identical behavior); ``app/api/sync.py`` keeps the private name ``_sanitize_error`` as an alias import, and the docs-push 502 detail (SEC-08) and the LLM error messages (SEC-13) run through the same function. Audit basis (``.agents/remediation_plan.md``, security audit 2026-09-07): * SEC-08 (Medium) — the docs-push 502 surfaced git's stderr verbatim; ``BOR_DOCS_REPO`` is documented as "any remote (URL or local path)", so an ``https://user:token@host/...`` URL is a normal config shape, and a failed push (revoked token, network) echoes the remote URL in git's stderr straight into the browser and the logs. The sync path already solved exactly this problem — the docs-push and LLM surfaces simply never got the treatment. * SEC-13 (Low) — the LLM error f-strings interpolated ``settings.llm_base_url`` raw; a base URL configured with embedded credentials would echo into SSE ``error`` frames and logs. Same class of leak, same fix. Contract (narrow userinfo-regex only — byte-identical for credential-free text): * Only the git/HTTP ``user:pass@`` userinfo shape is rewritten — a run of userinfo characters, the separating colon, a second run, and the ``@``. Ordinary text survives character for character: plain hosts, ``https://host/...`` URLs without userinfo, prose with a colon + space (``fatal: ...``), emails in prose (no colon userinfo run before the ``@``). The existing sync / git-sources / LLM error-string tests are the tripwire proving the move is behavior-identical. * The replacement shape is ``*****@`` — the existing sync copy, so every already-pinned masked error stays byte-identical. * Idempotent for the masked forms the surfaces produce (sanitizing an already-sanitized git/URL failure string is a no-op). """ from __future__ import annotations import re #: ``user:pass@`` inside any error text (git stderr, endpoint URLs) — #: the narrow userinfo run (git/HTTP convention) that carries #: credentials; everything else is left untouched. _CREDS_RE = re.compile(r"[A-Za-z0-9._~%*-]+:[A-Za-z0-9._~%*-]+@") def sanitize_error(message: str) -> str: """Mask credentials embedded in an error string (no secrets in the UI). Git's stderr is otherwise surfaced verbatim (the admin needs the failing repo and git's reason to fix things) — only the ``user:pass@`` userinfo shape is rewritten, to ``*****@``. """ return _CREDS_RE.sub("*****@", message)