@@ -69,20 +69,23 @@ export default defineEventHandler(async event => {
6969 const miniDoc : PublicUserSession = await response . json ( )
7070
7171 // Fetch the user's profile record to get their avatar blob reference
72- // We use com.atproto.repo.getRecord to fetch directly from the user's PDS
73- // This works with any PDS, not just Bluesky
7472 let avatar : string | undefined
73+ const did = agent . did
7574 try {
76- const profileResponse = await fetch (
77- `${ miniDoc . pds } /xrpc/com.atproto.repo.getRecord?repo=${ encodeURIComponent ( agent . did ! ) } &collection=app.bsky.actor.profile&rkey=self` ,
78- { headers : { 'User-Agent' : 'npmx' } } ,
79- )
80- if ( profileResponse . ok ) {
81- const record = ( await profileResponse . json ( ) ) as { value : ProfileRecord }
82- const avatarBlob = record . value . avatar
83- if ( avatarBlob ?. ref ?. $link ) {
84- // Construct the blob URL from the user's PDS
85- avatar = `${ miniDoc . pds } /xrpc/com.atproto.sync.getBlob?did=${ encodeURIComponent ( agent . did ! ) } &cid=${ encodeURIComponent ( avatarBlob . ref . $link ) } `
75+ const pdsUrl = new URL ( miniDoc . pds )
76+ // Only fetch from HTTPS PDS endpoints to prevent SSRF
77+ if ( did && pdsUrl . protocol === 'https:' ) {
78+ const profileResponse = await fetch (
79+ `${ pdsUrl . origin } /xrpc/com.atproto.repo.getRecord?repo=${ encodeURIComponent ( did ) } &collection=app.bsky.actor.profile&rkey=self` ,
80+ { headers : { 'User-Agent' : 'npmx' } } ,
81+ )
82+ if ( profileResponse . ok ) {
83+ const record = ( await profileResponse . json ( ) ) as { value : ProfileRecord }
84+ const avatarBlob = record . value . avatar
85+ if ( avatarBlob ?. ref ?. $link ) {
86+ // Use Bluesky CDN for faster image loading
87+ avatar = `https://cdn.bsky.app/img/feed_thumbnail/plain/${ did } /${ avatarBlob . ref . $link } @jpeg`
88+ }
8689 }
8790 }
8891 } catch {
0 commit comments