···200200}
201201```
202202203203+`com.atproto.sync.getBlob` responses should stay uncompressed end-to-end. `perlsky` now bypasses Mojolicious dynamic gzip for blob bytes because some downstream image proxy routes will auto-decompress the body and accidentally forward a stale `Content-Encoding` header, which shows up in clients as broken image loads (`ERR_CONTENT_DECODING_FAILED`).
204204+203205This still requires wildcard DNS or per-handle DNS records so public ACME validation can reach the server.
204206205207A minimal nginx site looks like:
+4-1
lib/ATProto/PDS/API/Sync.pm
···128128 $c->res->headers->header('Content-Disposition' => 'attachment; filename="' . ($c->param('cid') // q()) . '"');
129129 $c->res->headers->header('Content-Security-Policy' => q{default-src 'none'; sandbox});
130130 $c->observe_blob_egress($blob->{mime_type}, length($bytes));
131131- $c->render(data => $bytes);
131131+ # Blob bytes need to bypass Mojolicious' dynamic gzip so image proxy routes
132132+ # do not see a decompressed body paired with a stale Content-Encoding header.
133133+ $c->res->body($bytes);
134134+ $c->rendered(200);
132135 return;
133136 });
134137
+10
t/blob-sync-surfaces.t
···7878 ->status_is(200);
7979is($t->tx->res->body, 'blob-bytes', 'blob bytes are served back');
8080like($t->tx->res->headers->content_type // '', qr{image/png}, 'blob content type preserved');
8181+ok(!defined($t->tx->res->headers->content_encoding), 'blob download is not dynamically compressed by default');
8282+8383+my $gzip_blob_tx = $t->ua->build_tx(
8484+ GET => '/xrpc/com.atproto.sync.getBlob?did=' . $did . '&cid=' . $blob_cid => {
8585+ 'Accept-Encoding' => 'gzip',
8686+ },
8787+);
8888+$t->request_ok($gzip_blob_tx)->status_is(200);
8989+is($t->tx->res->body, 'blob-bytes', 'blob bytes stay readable when gzip is accepted');
9090+ok(!defined($t->tx->res->headers->content_encoding), 'blob download ignores Accept-Encoding gzip');
81918292$t->get_ok('/xrpc/com.atproto.sync.getLatestCommit?did=' . $did)
8393 ->status_is(200)