phase: 173_docs_push_creds_split
Build and Push Containers / build-and-push-app (push) Successful in 1m41s
Build and Push Containers / build-and-push-db (push) Successful in 11s

All gates green. Final report:

**Phase 173 — final verification pass (all tasks already complete in `complete/`)**

- Verified the full implementation is in the working tree: `docs_user`/`docs_token` settings + catalogue rows (task 01), `docs_repo` PUT 422 guard + `docs_token` write-only pin (task 02), `run_git(env)` + `push_document(user, token)` credential env (task 03), `settings_migrate.py` wired into `create_app()` (task 04), E2E suite (task 05).
- **Completion criteria:**
  1. GET `/api/admin/settings` lists `docs_user` (plain) + `docs_token` (secret, `value=null`), token never in any body — ✅ `test_docs_token_is_write_only_masked_end_to_end` + E2E sentinel-absent-from-DOM
  2. PUT `docs_repo` with `://user:pass@` → 422 naming the fields; clean URL passes — ✅ integration + E2E UI-side
  3. Token set → `GIT_CURL_USERNAME`/`GIT_CURL_PASSWORD`/`GIT_TERMINAL_PROMPT=0` on every git step; empty → `env=None` — ✅ mocked-git unit tests
  4. Legacy env split once at startup, idempotent, one INFO line, `.env` untouched — ✅ `test_settings_migrate.py` (4 shapes) + `test_docs_push_migrate.py`
  5. Full suite green, coverage >90%, lint clean — ✅
  6. E2E isolated: `test_docs_push_creds.py` 4/4; regression set (panel + layout + response_to_docs) 21/21 — ✅
  7. Commit + phase move — harness's job (per instructions, not mine).
  8. README/AGENTS consistent — ✅ (below)
- Tests/lint: `uv run pytest` exit 0 (4112 passed) · `uv run pytest --cov=app --cov-report=term-missing` TOTAL 99% · `uv run ruff check .` clean · `uv run pyright` 0 errors
- Docs: README.md (incl. Docs-push config section, guard/migration paragraph, `.env.example` block) and AGENTS.md verified consistent in full; fixed two stale docstrings in `app/config.py` + `app/core/settings_catalog.py` (`is_set` → the API's actual `has_value` field).
- No live-infrastructure changes (no live step in this phase). No next pending phase — `todo/` held only 173.
This commit is contained in:
2026-10-10 16:47:39 -04:00
parent f27106a638
commit a86caec821
56 changed files with 3177 additions and 39 deletions
+52 -3
View File
@@ -6,8 +6,8 @@ opens a file (``.env`` is never written), never shells out and never
``eval``s a value.
The DB-driven behavior (the four routes, the candidate-set validation,
the auth matrix) is pinned in
``tests/integration/test_admin_settings_api.py``.
the auth matrix, the phase-173 ``docs_repo`` guard on the PUT path)
is pinned in ``tests/integration/test_admin_settings_api.py``.
"""
from __future__ import annotations
@@ -15,8 +15,14 @@ import pathlib
import re
import pytest
from fastapi import HTTPException
from app.api.admin_settings import _display, _field_type, _item
from app.api.admin_settings import (
_display,
_field_type,
_item,
_refuse_embedded_credentials,
)
from app.config import Settings
from app.core.settings_overlay import LIST_FIELDS
@@ -172,3 +178,46 @@ def test_module_never_writes_files_or_shells_out() -> None:
assert "os.system" not in source
assert re.search(r"\beval\s*\(", source) is None
assert "import shutil" not in source and "shutil." not in source
def test_refuse_embedded_credentials_fires_only_for_docs_repo() -> None:
"""Phase 173 (task 02, C4): the guard 422s for ``docs_repo`` with a
``://<user>:<pass>@`` segment — the detail NAMES the Docs user /
Docs token fields — and passes everything else: other keys (only
``docs_repo`` is guarded), clean ``https://`` URLs, local paths,
``ssh://`` remotes, and a ``user:``-looking subdomain (the
trailing ``@`` requirement keeps it from false-positiving).
An empty user OR an empty pass (``://:pass@`` / ``://u:@``) does
NOT match — the ``+`` quantifiers require both to be non-empty.
"""
# The 422: names the replacement fields.
with pytest.raises(HTTPException) as excinfo:
_refuse_embedded_credentials(
"docs_repo", "https://u:pass@gitea.example.com/o/r.git"
)
assert excinfo.value.status_code == 422
assert "Docs user" in excinfo.value.detail
assert "Docs token" in excinfo.value.detail
# Also a ``git://``-style URL with embedded credentials (the regex
# is scheme-agnostic: it keys on ``://<user>:<pass>@``).
with pytest.raises(HTTPException):
_refuse_embedded_credentials("docs_repo", "git://u:tok@host.example.com/r.git")
# Other keys: never guarded (``docs_user`` may hold any plain text).
_refuse_embedded_credentials("docs_user", "https://u:pass@host.example.com/r.git")
_refuse_embedded_credentials("llm_api_key", "://u:pass@")
# Clean shapes pass: https URL, local path, ssh remote.
_refuse_embedded_credentials("docs_repo", "https://gitea.example.com/o/r.git")
_refuse_embedded_credentials("docs_repo", "/srv/docs-repo")
_refuse_embedded_credentials("docs_repo", "ssh://git@gitea.example.com/o/r.git")
# A subdomain that looks like ``user:`` (no trailing ``@``).
_refuse_embedded_credentials("docs_repo", "https://sub.domain.example.com/r.git")
# Empty user OR empty pass: the ``+`` quantifiers require both
# non-empty — no match (a bare ``://`` is not the leak class).
_refuse_embedded_credentials("docs_repo", "https://:pass@host.example.com/r.git")
_refuse_embedded_credentials("docs_repo", "https://u:@host.example.com/r.git")
# A ``:`` after the ``@`` segment (a URL with no credentials at
# all) and an empty value.
_refuse_embedded_credentials("docs_repo", "https://host.example.com/r.git:3000")
_refuse_embedded_credentials("docs_repo", "")