From 65f004029b293d0081a6adac3d4eed8cae356d6f Mon Sep 17 00:00:00 2001 From: Bryan Van Deusen Date: Thu, 8 Oct 2026 09:33:48 -0400 Subject: [PATCH] Signed-in app downloads are cached privately, not publicly Quart's send_file marks every file it sends Cache-Control: public. The app download is behind a login, so a shared cache or proxy could have kept one account's copy and handed it to anyone. send_artifact now marks it private, as the attachment route already does. Family idea #5105, practice 11 (Scribe #5113). Co-Authored-By: Claude Opus 5.5 --- src/inkwell/client_dist.py | 4 ++++ tests/test_client_dist.py | 9 +++++++++ 2 files changed, 13 insertions(+) diff --git a/src/inkwell/client_dist.py b/src/inkwell/client_dist.py index 465f9cc..185d13b 100644 --- a/src/inkwell/client_dist.py +++ b/src/inkwell/client_dist.py @@ -424,6 +424,10 @@ async def send_artifact(root: Path, platform: Platform, found: dict) -> Response root, platform.artifact, mimetype=platform.mimetype, add_etags=False, conditional=False ) response.set_etag(found["sha256"]) + # Only behind a login, so no shared cache may keep a copy (family idea #5105, + # practice 11). Quart marks every file it sends `public`. + response.cache_control.public = False + response.cache_control.private = True # Without this some browsers try to render it, and Android's download handler # wants a filename to hand to the package installer. response.headers["Content-Disposition"] = f'attachment; filename="{platform.artifact}"' diff --git a/tests/test_client_dist.py b/tests/test_client_dist.py index 8dc3095..f9c836e 100644 --- a/tests/test_client_dist.py +++ b/tests/test_client_dist.py @@ -448,6 +448,15 @@ async def test_the_etag_is_the_sha256_the_sidecar_records(app): assert body == PAYLOAD +async def test_signed_in_bytes_are_never_cached_by_a_shared_cache(app): + """Quart sends files `public`; behind a login that would let a proxy hand one + account's download to anyone.""" + resp, _ = await _send(app, {}) + directives = {d.strip().split("=")[0] for d in resp.headers["Cache-Control"].split(",")} + assert "private" in directives + assert "public" not in directives + + async def test_a_range_request_gets_just_that_range(app): resp, body = await _send(app, {"Range": "bytes=4-9"}) assert resp.status_code == 206