phase: 84_docs_push_error_sanitization
**Phase 84 — final verification pass: all green, no defects found** - Verified implementation: `app/core/errors.py` (verbatim lift of sync masker), `app/api/sync.py` alias import, docs-push 502 `detail=sanitize_error(str(exc))`, all five `llm.py` error sites sanitized; new/extended test pins in place - Tests: `uv run pytest` → **1714 passed, 0 failed**; targeted pins (new unit ×2 + integration ×1, existing 502 pin) → 13 passed; sync/git-sources regression → 67 passed - Coverage: `uv run pytest --cov=app --cov-report=term-missing` → **99%** (`app/core/errors.py` 100%, `app/rag/llm.py` 100%) — >90% met - E2E isolation: `uv run pytest tests/e2e/test_smoke.py -v --no-cov` → **3 passed** - Lint/types: `uv run ruff check .` → clean; `uv run pyright` → **0 errors** - Criteria: 502 masks `*****@`/never token + row untouched ✅; LLM base-URL masked, credential-free strings byte-identical ✅; `_CREDS_RE` only in `app/core/errors.py` (working-tree grep) ✅; full gate green ✅; `git diff --stat` limited to the 4 app files + 2 modified test files + 3 phase task files (untracked: new module, new unit test, complete/ dir, reports, audit plan) ✅ - Commit/phase move left to the harness per instructions (task files already in `complete/`) - No deviations; nothing to fix - Next pending phase: **85_mobile_menu_gate_overlap**
This commit is contained in:
+20
-8
@@ -26,8 +26,10 @@ triggers — commit + ``git push`` the draft's file to the
|
||||
:func:`app.core.docs_push.push_document`; success records
|
||||
``status`` / ``branch`` / ``commit_sha`` on the row and returns
|
||||
``DocDraftPushed``; 409 while unconfigured, 422 on a path that no
|
||||
longer passes the guard-rails, 502 on git failure with git's stderr
|
||||
in the detail — the row untouched).
|
||||
longer passes the guard-rails, 502 on git failure with the
|
||||
SANITIZED git stderr in the detail (credential ``user:pass@``
|
||||
userinfo masked by :func:`app.core.errors.sanitize_error` — phase
|
||||
84, SEC-08) — the row untouched).
|
||||
|
||||
Every ``path`` (create, update **and** push) passes the shared
|
||||
:func:`validate_draft_path` guard, so no draft can ever be created,
|
||||
@@ -45,6 +47,7 @@ from sqlalchemy.orm import Session
|
||||
from app.config import Settings, get_settings
|
||||
from app.core.auth import require_admin
|
||||
from app.core.docs_push import DocsPushError, push_document
|
||||
from app.core.errors import sanitize_error
|
||||
from app.db import get_db
|
||||
from app.models import DocDraft
|
||||
from app.schemas import DocDraft as DocDraftOut
|
||||
@@ -251,11 +254,16 @@ def push_doc_draft(
|
||||
3. the stored ``path`` re-runs :func:`validate_draft_path` → 422
|
||||
on the first violated rule (a row must not be pushable into a
|
||||
bad path, whatever wrote it);
|
||||
4. :class:`DocsPushError` → 502 with ``detail=str(exc)`` — git's
|
||||
stderr, the ``GitSyncError`` → ``detail`` mapping from
|
||||
:mod:`app.api.git_sources`. Only a SUCCESS mutates the row:
|
||||
the failed push leaves ``status`` / ``branch`` / ``commit_sha``
|
||||
exactly as found (no partial commit).
|
||||
4. :class:`DocsPushError` → 502 with the SANITIZED git stderr in
|
||||
the detail (phase 84, SEC-08): ``str(exc)`` runs through
|
||||
:func:`app.core.errors.sanitize_error`, which masks any
|
||||
``user:pass@`` userinfo git's stderr may echo (a
|
||||
``BOR_DOCS_REPO`` URL with embedded credentials) — the failing
|
||||
repo and git's reason stay readable, and the
|
||||
``GitSyncError`` → ``detail`` mapping from
|
||||
:mod:`app.api.git_sources` is otherwise kept. Only a SUCCESS
|
||||
mutates the row: the failed push leaves ``status`` / ``branch``
|
||||
/ ``commit_sha`` exactly as found (no partial commit).
|
||||
|
||||
On success the row becomes ``status = "pushed"`` with the landed
|
||||
``branch`` and ``commit_sha`` (must equal ``git rev-parse
|
||||
@@ -283,7 +291,11 @@ def push_doc_draft(
|
||||
commit_message=f"docs: {row.title}",
|
||||
)
|
||||
except DocsPushError as exc:
|
||||
raise HTTPException(status_code=502, detail=str(exc)) from None
|
||||
# Phase 84 (SEC-08): git's stderr may echo the remote URL with
|
||||
# embedded credentials — the RESPONSE is where the secret would
|
||||
# leak, so it is sanitized here (the DocsPushError itself keeps
|
||||
# carrying the full stderr for logs/inspection).
|
||||
raise HTTPException(status_code=502, detail=sanitize_error(str(exc))) from None
|
||||
row.status = "pushed"
|
||||
row.branch = branch
|
||||
row.commit_sha = sha
|
||||
|
||||
Reference in New Issue
Block a user