perlsky is a Perl 5 implementation of an AT Protocol Personal Data Server.
13
fork

Configure Feed

Select the types of activity you want to include in your feed.

Align sync blob header semantics

alice 6f1e646f 6f181ab0

+55 -6
+4 -2
docs/TEST_AUDIT.md
··· 1 1 # Test Audit Status 2 2 3 - As of 2026-03-12, the focused test-correctness and reference-audit pass is complete on rewritten history through `457a027`. 3 + As of 2026-03-12, the focused test-correctness and reference-audit pass is complete on rewritten history through `6f181ab`. 4 4 5 5 That does not mean every test has been manually revalidated against every other PDS implementation line by line. It means: 6 6 ··· 13 13 The current baseline for saying "the audited suite is green" is: 14 14 15 15 - `prove -lr t` 16 - - latest full green result in the realigned Meridian worktree: `Files=48, Tests=2798` 16 + - latest full green result in the realigned Meridian worktree: `Files=48, Tests=2880` 17 17 - `prove -lv t/server-auth.t` 18 18 - `perl -c script/differential-validate` 19 19 - `PERLSKY_RUN_REFERENCE_DIFF=1 prove -lv t/reference-differential.t` ··· 61 61 - `com.atproto.server.createAccount` with an explicit `did` must behave like an authenticated migration flow: require auth from that same DID, keep the existing DID document, and start the new account deactivated until activation catches the DID document up to the new PDS. 62 62 - `com.atproto.server.checkAccountStatus` must validate the stored DID document against the PDS service endpoint and signing key, and `com.atproto.repo.describeRepo` must derive `didDoc` / `handleIsCorrect` from that document instead of hardcoding success. 63 63 - `com.atproto.sync.getBlob` should ship the same download-hardening headers as the reference PDS (`X-Content-Type-Options`, `Content-Disposition`, `Content-Security-Policy`). 64 + - `com.atproto.sync.getBlob` should not add an extra `Cross-Origin-Resource-Policy` header beyond the reference PDS surface; the executable differential now pins the exact hardening-header set instead of a stricter local variant. 65 + - `com.atproto.sync.listReposByCollection` is present in the published lexicon but not exposed by the current official runtime, so it remains locally regression-tested rather than executable-reference-differenced. 64 66 65 67 ## Known Intentional Divergences 66 68
-1
lib/ATProto/PDS/API/Sync.pm
··· 125 125 my $bytes = <$fh>; 126 126 close($fh); 127 127 $c->res->headers->content_type($blob->{mime_type} || 'application/octet-stream'); 128 - $c->res->headers->header('Cross-Origin-Resource-Policy' => 'cross-origin'); 129 128 $c->res->headers->header('X-Content-Type-Options' => 'nosniff'); 130 129 $c->res->headers->header('Content-Disposition' => 'attachment; filename="' . ($c->param('cid') // q()) . '"'); 131 130 $c->res->headers->header('Content-Security-Policy' => q{default-src 'none'; sandbox});
+51 -1
script/differential-validate
··· 1604 1604 my $upload = post_bytes( 1605 1605 $server{$name}{origin}, 1606 1606 'com.atproto.repo.uploadBlob', 1607 - "blob bytes for $name", 1607 + 'sync blob bytes', 1608 1608 'text/plain', 1609 1609 auth_header(($session->json || {})->{accessJwt}), 1610 1610 ); ··· 1635 1635 check($res->is_success, "$name listBlobs with since succeeds"); 1636 1636 my $json = $res->json || {}; 1637 1637 my $blob_cid = $blob->{ref}{'$link'}; 1638 + $server{$name}{sync_blob} = { 1639 + cid => $blob_cid, 1640 + }; 1638 1641 $server{$name}{list_blobs_since} = { 1639 1642 ok => $res->is_success ? 1 : 0, 1640 1643 returns_blob => (scalar grep { $_ eq $blob_cid } @{ $json->{cids} || [] }) ? 1 : 0, ··· 1649 1652 } else { 1650 1653 pass('listBlobs since semantics match the official reference PDS'); 1651 1654 } 1655 + 1656 + note('Comparing getBlob'); 1657 + for my $name (sort keys %server) { 1658 + my $blob_cid = $server{$name}{sync_blob}{cid}; 1659 + my $res = get_form($server{$name}{origin}, 'com.atproto.sync.getBlob', { 1660 + did => $server{$name}{did}, 1661 + cid => $blob_cid, 1662 + }); 1663 + check($res->is_success, "$name getBlob succeeds"); 1664 + next unless $res->is_success; 1665 + my $content_type = $res->headers->content_type // q(); 1666 + my $disposition = $res->headers->header('Content-Disposition') // q(); 1667 + $server{$name}{get_blob} = { 1668 + body_matches => (($res->body // q()) eq 'sync blob bytes') ? 1 : 0, 1669 + content_type_plain => ($content_type =~ /\Atext\/plain(?:\z|;)/) ? 1 : 0, 1670 + nosniff => (($res->headers->header('X-Content-Type-Options') // q()) eq 'nosniff') ? 1 : 0, 1671 + csp_sandbox => (($res->headers->header('Content-Security-Policy') // q()) eq q{default-src 'none'; sandbox}) ? 1 : 0, 1672 + attachment_name => ($disposition =~ /\Aattachment; filename="/) ? 1 : 0, 1673 + }; 1674 + } 1675 + 1676 + check( 1677 + same_hash($server{reference}{get_blob}, $server{perlsky}{get_blob}), 1678 + 'getBlob payload and hardening headers match the official reference PDS', 1679 + ); 1680 + 1681 + note('Comparing listRepos'); 1682 + for my $name (sort keys %server) { 1683 + my $list_repos = get_form($server{$name}{origin}, 'com.atproto.sync.listRepos', { 1684 + limit => 10, 1685 + }); 1686 + check($list_repos->is_success, "$name listRepos succeeds"); 1687 + my $repos = $list_repos->is_success ? (($list_repos->json || {})->{repos} || []) : []; 1688 + my ($repo_row) = grep { (($_->{did} // q()) eq $server{$name}{did}) } @$repos; 1689 + 1690 + $server{$name}{list_repos_surface} = { 1691 + repo_present => $repo_row ? 1 : 0, 1692 + repo_active => (($repo_row || {})->{active} ? 1 : 0), 1693 + repo_has_rev => defined(($repo_row || {})->{rev}) && length(($repo_row || {})->{rev}) ? 1 : 0, 1694 + repo_did_matches => (($repo_row || {})->{did} // q()) eq $server{$name}{did} ? 1 : 0, 1695 + }; 1696 + } 1697 + 1698 + check( 1699 + same_hash($server{reference}{list_repos_surface}, $server{perlsky}{list_repos_surface}), 1700 + 'listRepos matches the official reference PDS semantics', 1701 + ); 1652 1702 1653 1703 note('Comparing listMissingBlobs empty-state semantics'); 1654 1704 for my $name (sort keys %server) {
-2
t/external-surface.t
··· 125 125 did => $did, 126 126 cid => $blob_cid, 127 127 ))->status_is(200) 128 - ->header_is('Cross-Origin-Resource-Policy' => 'cross-origin') 129 128 ->header_is('X-Content-Type-Options' => 'nosniff') 130 129 ->header_like('Content-Disposition' => qr/\Aattachment; filename="/) 131 130 ->header_is('Content-Security-Policy' => "default-src 'none'; sandbox") ··· 204 203 did => $second_did, 205 204 cid => $blob_cid, 206 205 ))->status_is(200) 207 - ->header_is('Cross-Origin-Resource-Policy' => 'cross-origin') 208 206 ->header_is('X-Content-Type-Options' => 'nosniff') 209 207 ->header_like('Content-Disposition' => qr/\Aattachment; filename="/) 210 208 ->header_is('Content-Security-Policy' => "default-src 'none'; sandbox")