phase: 123_chat_image_questions
All gates green. Verification complete. **Phase 123 — final verification pass (all 4 tasks already in `complete/`)** - Verified the full implementation is in the working tree: `app/api/chat_images.py` (upload/serve pair), `ChatRequest.image`/`ChatMessage.image` (path-validated, omitted-when-None), toggle-off + stale-file hinted error frames, `build_user_content` multimodal build at both sites (chat.py deflected branch + `run_agent`), config-gated composer attach/preview/upload-then-send, restore + shared rendering, CSP `img-src 'self' data:` carve-out, mock-LLM capture buffer. - `uv run pytest` → **2796 passed**, exit 0 (unit + integration). - `uv run pytest --cov=app --cov-report=term-missing` → **TOTAL 99%** (29/4615 missed; phase-123 modules 99–100%). - `uv run pytest tests/e2e/test_chat_image_questions.py -v --no-cov` → **5 passed** in isolation. - `uv run ruff check . && uv run pyright` → clean (0 errors). **Completion criteria:** (1) attach→send→multimodal text+image to the model, bubble/reload/shared all render it, saved chat stores the PATH with `"base64" not in json.dumps(stored)` — **verified** (E2E tests 1–4 + integration round-trip); (2) `BOR_IMAGES=false` — control hidden, exact hinted error frame, zero model calls / no query_log row — **verified** (E2E test 5 + integration); (3) text-only byte-identical (`content` stays a plain `str`) — **verified** (unit + integration); (4) all gates green — **verified**; (5) commit + phase move — left to the harness per pipeline rules (no `git add`/`commit` run). No defects found; no live-infrastructure changes (repo + local dev DB only). **Next pending phase: none** — 123 is the last phase in `todo/`.
This commit is contained in:
@@ -29,7 +29,10 @@ from app.core.security_headers import CSP, SecurityHeadersMiddleware
|
||||
#: The exact expected header set (decision A1 for the CSP, A4 for the
|
||||
#: other two).
|
||||
EXPECTED_HEADERS = {
|
||||
"content-security-policy": "default-src 'self'; base-uri 'none'; frame-ancestors 'none'",
|
||||
"content-security-policy": (
|
||||
"default-src 'self'; base-uri 'none'; frame-ancestors 'none'; "
|
||||
"img-src 'self' data:"
|
||||
),
|
||||
"x-frame-options": "DENY",
|
||||
"x-content-type-options": "nosniff",
|
||||
}
|
||||
@@ -111,10 +114,19 @@ def _assert_security_headers(start: Message, expected_extra: dict[str, str] | No
|
||||
|
||||
|
||||
def test_csp_constant_is_the_exact_a1_policy() -> None:
|
||||
"""The owner-approved A1 string, verbatim: same-origin default, no
|
||||
base-tag hijack, no framing — no 'unsafe-inline', no report sink."""
|
||||
assert CSP == "default-src 'self'; base-uri 'none'; frame-ancestors 'none'"
|
||||
"""The owner-approved A1 string (phase 82), verbatim, with the
|
||||
phase-123 ``img-src`` carve-out (the question-image composer's
|
||||
data-URL preview + live bubble — see ``security_headers.CSP``):
|
||||
same-origin default, no base-tag hijack, no framing — no
|
||||
'unsafe-inline', no report sink, and the ``data:`` allowance is
|
||||
SCOPED to img-src (never script/style/fetch)."""
|
||||
assert CSP == (
|
||||
"default-src 'self'; base-uri 'none'; frame-ancestors 'none'; "
|
||||
"img-src 'self' data:"
|
||||
)
|
||||
assert "unsafe-inline" not in CSP
|
||||
# the carve-out is img-src ONLY — no other directive gains data:
|
||||
assert CSP.count("data:") == 1
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -158,6 +170,7 @@ def test_pre_existing_csp_from_an_inner_layer_is_preserved() -> None:
|
||||
while the other two headers are still added."""
|
||||
themed = (
|
||||
"default-src 'self'; base-uri 'none'; frame-ancestors 'none'; "
|
||||
"img-src 'self' data:; "
|
||||
"style-src 'self' 'sha256-2rm3wPcQfXmE8q1s9vBzK7hN4tY5uJ6gW3oR0cAeDfH='"
|
||||
)
|
||||
wrapped = SecurityHeadersMiddleware(
|
||||
|
||||
Reference in New Issue
Block a user