Signed-in app downloads are cached privately, not publicly
CI & Build / Python lint (push) Successful in 3s
CI & Build / Build now, or wait for Android? (push) Successful in 3s
Android / Build, or is the channel already serving this? (push) Successful in 3s
Android / Core and FFI clippy and tests (push) Skipped
Android / Kotlin + Rust (APK) (push) Skipped
Android / Build the server image (push) Skipped
Desktop (Tauri) / Build, or is the channel already serving this? (push) Successful in 3s
Desktop (Tauri) / Web tests, clippy, Rust tests and rustfmt (push) Skipped
Desktop (Tauri) / Tauri desktop (Linux) (push) Skipped
Desktop (Tauri) / Windows installer (cross-compiled) (push) Skipped
Desktop (Tauri) / Update manifest (push) Skipped
CI & Build / Web typecheck and unit tests (push) Successful in 10s
CI & Build / Python tests (push) Successful in 15s
CI & Build / integration (push) Successful in 1m6s
CI & Build / Build & push image (push) Successful in 49s
CI & Build / Python lint (push) Successful in 3s
CI & Build / Build now, or wait for Android? (push) Successful in 3s
Android / Build, or is the channel already serving this? (push) Successful in 3s
Android / Core and FFI clippy and tests (push) Skipped
Android / Kotlin + Rust (APK) (push) Skipped
Android / Build the server image (push) Skipped
Desktop (Tauri) / Build, or is the channel already serving this? (push) Successful in 3s
Desktop (Tauri) / Web tests, clippy, Rust tests and rustfmt (push) Skipped
Desktop (Tauri) / Tauri desktop (Linux) (push) Skipped
Desktop (Tauri) / Windows installer (cross-compiled) (push) Skipped
Desktop (Tauri) / Update manifest (push) Skipped
CI & Build / Web typecheck and unit tests (push) Successful in 10s
CI & Build / Python tests (push) Successful in 15s
CI & Build / integration (push) Successful in 1m6s
CI & Build / Build & push image (push) Successful in 49s
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 <noreply@anthropic.com>
This commit is contained in:
@@ -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
|
root, platform.artifact, mimetype=platform.mimetype, add_etags=False, conditional=False
|
||||||
)
|
)
|
||||||
response.set_etag(found["sha256"])
|
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
|
# Without this some browsers try to render it, and Android's download handler
|
||||||
# wants a filename to hand to the package installer.
|
# wants a filename to hand to the package installer.
|
||||||
response.headers["Content-Disposition"] = f'attachment; filename="{platform.artifact}"'
|
response.headers["Content-Disposition"] = f'attachment; filename="{platform.artifact}"'
|
||||||
|
|||||||
@@ -448,6 +448,15 @@ async def test_the_etag_is_the_sha256_the_sidecar_records(app):
|
|||||||
assert body == PAYLOAD
|
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):
|
async def test_a_range_request_gets_just_that_range(app):
|
||||||
resp, body = await _send(app, {"Range": "bytes=4-9"})
|
resp, body = await _send(app, {"Range": "bytes=4-9"})
|
||||||
assert resp.status_code == 206
|
assert resp.status_code == 206
|
||||||
|
|||||||
Reference in New Issue
Block a user