เว็บไซต์ที่โฮสต์อยู่บน Bluesky
(danielmangum.com)- Daniel Mangum แสดงให้เห็นว่า ด้วยการใช้การอัปโหลด blob และโครงสร้างการอ้างอิง record ของ AT Protocol สามารถนำไฟล์
text/htmlที่อัปโหลดไปยัง Bluesky PDS มาให้บริการเหมือนเว็บเพจที่เปิดในเบราว์เซอร์ได้ - กลไกสำคัญคือ blob ที่อัปโหลดจะไม่ถูกเผยแพร่ทันที แต่ PDS จะเปลี่ยนให้เข้าถึงแบบสาธารณะได้ก็ต่อเมื่อมี record อ้างอิงถึง blob นั้น
- embed รูปภาพทั่วไปของ Bluesky อนุญาตเฉพาะ MIME type แบบ
image/*แต่ record ของ lexicon ที่ไม่รู้จัก หรือ custom embed ที่อิง open union สามารถเก็บการอ้างอิง blob แบบtext/htmlได้ getBlobเข้าถึงได้โดยไม่ต้องยืนยันตัวตน และคำขอไปยังbsky.socialจะถูก redirect ไปยัง PDS จริงคือporcini.us-east.host.bsky.networkแล้วส่ง HTML กลับมา- Bluesky ใช้ CSP header และ
nosniffกับgetBlobและจำกัดการอัปโหลด blob พื้นฐานไว้ที่ 5MB แต่บริการที่ดำเนินการ PDS ยังคงต้องแบกรับภาระการโฮสต์ไฟล์ใด ๆ รวมถึงต้นทุนการจัดเก็บและรับส่งข้อมูล
วิธีนำเว็บไซต์ขึ้นด้วย blob ของ AT Protocol
- กรณีนี้อิงกับวิธีทำงานของ AT Protocol และ PDS API มากกว่าฟีเจอร์ทั่วไปของแอป Bluesky
- Bluesky มี PDS entryway สำหรับเข้าถึง PDS ของผู้ใช้ และเผยแพร่ PDS หลายอินสแตนซ์ผ่านโดเมน
bsky.social - สามารถเข้าถึงอินสแตนซ์ PDS รายตัวได้โดยตรงเช่นกัน และเว็บไซต์ตัวอย่างเปิดจาก endpoint
com.atproto.sync.getBlobของporcini.us-east.host.bsky.network - ก่อนเผยแพร่บทความได้สื่อสารกับทีม Bluesky แล้ว พฤติกรรมนี้ไม่ใช่วิธีใช้งานแอปพลิเคชันที่ตั้งใจไว้ แต่เป็นพฤติกรรมที่ทราบอยู่แล้ว และไม่ใช่กระบวนการเปิดเผยช่องโหว่
เหตุผลที่ records และ blobs ถูกแยกออกจากกัน
- ใน AT Protocol ข้อมูลของแอปพลิเคชันแบ่งกว้าง ๆ เป็น records และ blobs
- records คือเอนทิตีหลักที่ผู้ใช้สร้างขึ้น สามารถมีโครงสร้างและเมทาดาทาได้
- blobs คือข้อมูลไม่มีโครงสร้างขนาดใหญ่กว่า เช่น รูปภาพ และโดยปกติต้องมี record อ้างอิงจึงจะถูกเปิดเผย
- เมื่อสร้างโพสต์ที่มีรูปภาพใน Bluesky ผู้ใช้จะเห็นเหมือนเป็นการทำงานครั้งเดียว แต่ในระดับ API การอัปโหลด blob และการสร้าง record แยกจากกัน
- ตาม blob specification ต้องอัปโหลด blob ไปยัง PDS ก่อนที่ record จะอ้างอิงได้
- ตอนอัปโหลด เซิร์ฟเวอร์ไม่สามารถรู้ได้ว่า blob จะถูกใช้ใน Lexicon ใด ดังนั้นตอนอัปโหลดเริ่มต้นจึงใช้เพียงข้อจำกัด blob ทั่วไป และค่อยใช้ข้อจำกัดตาม Lexicon ตอนสร้าง record
- blob ที่อัปโหลดสำเร็จจะถูกเก็บในพื้นที่จัดเก็บชั่วคราว โดยในสถานะนี้จะดาวน์โหลดหรือเผยแพร่ไม่ได้ และไม่รวมอยู่ในผลลัพธ์ของ
listBlobs - เมื่อสร้าง record สำเร็จและ record นั้นอ้างอิง blob เซิร์ฟเวอร์จะเปลี่ยน blob ให้เป็นสถานะที่เข้าถึงแบบสาธารณะได้
การอัปโหลด blob HTML และการเปลี่ยนเป็นสาธารณะ
- token ยืนยันตัวตนได้จากการแลกเปลี่ยนข้อมูลรับรองผู้ใช้ผ่านเมธอด XRPC
com.atproto.server.createSession - ในตัวอย่างใช้
com.atproto.sync.listBlobsเพื่อตรวจสอบจำนวน blob เดิม และก่อนอัปโหลดมีจำนวน391รายการ - เนื้อหาเว็บไซต์เป็นไฟล์
index.htmlแบบเรียบง่าย และถูกอัปโหลดด้วยcom.atproto.repo.uploadBlobContent-Typeคือtext/html- เมทาดาทา blob ที่ส่งกลับมามี CID
bafkreic5fmelmhqoqxfjz2siw5ey43ixwlzg5gvv2pkkz7o25ikepv4zeq,mimeType: text/html,size: 268
- ทันทีหลังอัปโหลด เมื่อลองดึงด้วย
com.atproto.sync.getBlobจะยังเข้าถึงไม่ได้ และจำนวนในlistBlobsก็ยังคงเป็น391 - หากอ้างอิง blob HTML ผ่าน image embed ของโพสต์ Bluesky จะล้มเหลว เพราะ schema
app.bsky.embed.imageต้องการ MIME type แบบimage/*- หากปล่อย
text/htmlไว้ตามเดิมจะเกิดข้อผิดพลาดWrong type of file. It is text/html but it must match image/*. - หากเปลี่ยน
mimeTypeเป็นimage/jpegจะเกิดข้อผิดพลาดว่า MIME type ของ blob ที่เก็บไว้กับ MIME type ที่อ้างอิงไม่ตรงกัน
- หากปล่อย
ทำให้ blob เป็นสาธารณะด้วย record ของ lexicon ที่ไม่รู้จัก
- ประเภท blob ไม่ได้เป็นของ Bluesky โดยเฉพาะ แต่เป็นส่วนหนึ่งของ โมเดลข้อมูลของ AT Protocol
- implementation ของ Bluesky PDS เป็นโอเพนซอร์ส และ
findBlobRefsจะค้นหาLexValueแบบ recursive เพื่อหา reference ที่มี$type: blob - เนื่องจาก PDS ต้องรองรับ lexicon ใหม่ ๆ เมื่อเวลาผ่านไป จึงต้องสามารถจัดการ lexicon ที่ตนไม่รู้จักได้ด้วย
- เมื่อสร้าง record ประเภท
com.danielmangum.hack.websiteและอ้างอิง blob HTML แล้ว PDS ก็จัดเก็บ record นั้น- ใน response มี
validationStatus: unknown - PDS ไม่รู้จัก lexicon
com.danielmangum.hack.*จึงตรวจสอบความถูกต้องไม่ได้ แต่ยังอนุญาตให้จัดเก็บ record
- ใน response มี
- หลังจากนั้นจำนวนใน
listBlobsเพิ่มเป็น392ยืนยันได้ว่า blob ถูกจัดเก็บแบบถาวร getBlobเรียกได้โดยไม่ต้องใช้ token ยืนยันตัวตน เพราะ entity ที่ไม่ผ่านการยืนยันตัวตนซึ่งต้องประมวลผล blobs พร้อมกับ records ก็จำเป็นต้องดึง blob ได้- การเรียก
getBlobของbsky.socialส่ง URL ของ PDS จริงกลับมาผ่าน 302 redirect และเมื่อใช้-Lเพื่อตาม redirect ก็จะได้เนื้อหา HTML กลับมาตามเดิม
security headers, CDN และภาระของการเข้าถึงโดยตรง
- Security Considerations ในสเปก blob ของ AT Protocol กล่าวถึงความเสี่ยงด้านความปลอดภัยของเนื้อหาเมื่อให้บริการไฟล์ที่ผู้ใช้อัปโหลดได้อย่างอิสระจากเว็บเซิร์ฟเวอร์
- ตัวอย่างสำคัญคือปัญหา XSS จากการรันสคริปต์หรือเนื้อหา SVG บน origin เดียวกัน
- endpoint
getBlobแทบจำเป็นต้องใช้ Content Security Policy - การให้เบราว์เซอร์และ web agent เข้าถึงที่เก็บ blob โดยตรงโดยแท้จริงแล้วไม่รองรับ และแอปพลิเคชันควรผ่าน CDN, proxy, web service ฯลฯ ที่แยกออกมา
- handler
getBlobของ Bluesky PDS ใช้ security headers ตามคำแนะนำx-content-type-options: nosniffcontent-security-policy: default-src 'none'; sandbox
- ข้อจำกัดการอัปโหลด blob พื้นฐานคือ 5MB
- blob รูปภาพทั่วไปของแอป Bluesky ไม่ได้ถูกให้บริการโดยตรงจาก PDS แต่ให้บริการผ่าน URL ของ CDN ที่
cdn.bsky.app- มี URL สำหรับ feed thumbnail และ URL full size แยกกัน
- record ของโพสต์มี CID ของรูปภาพ และแอปพลิเคชันต้องรู้วิธีให้บริการผ่าน CDN
- การเข้าถึง
getBlobโดยตรงยังคงอยู่ตาม GitHub issue เนื่องจากการติดป้ายกำกับรูปภาพ การ export เนื้อหาผู้ใช้ และกรณีใช้งานในอนาคต- ในการถกเถียงเดียวกันยังรวมถึงความเป็นไปได้ที่ผู้ใช้จะ hotlink เนื้อหาหรือใช้ Bluesky เหมือนโฮสติ้งฟรี
- implementation ช่วงแรกไม่มี security headers และถูกเพิ่มในภายหลัง
- แพลตฟอร์มโซเชียลแบบดั้งเดิมมีชนิดเนื้อหาที่ถูกต้องจำกัด จึงจำกัด blob ได้เข้มงวดกว่าตั้งแต่ตอนอัปโหลด แต่ความสามารถในการขยายของ Bluesky และ AT Protocol ต้องการการประมวลผลที่ซับซ้อนกว่า
ใส่ custom embed ในโพสต์ด้วย open union
- ประเภท
app.bsky.feed.postกำหนดรายการ embed ที่ถูกต้องเป็น union - ใน lexicon ของ AT Protocol หาก union ไม่ได้ระบุชัดเจนว่า
closedโดยปริยายจะเป็น open- เนื่องจากอาจมีการเพิ่มประเภทใน schema revision ในอนาคต implementation จึงควรผ่อนปรนตอน validation
- หากมี flag
closedชุดของประเภทจะถูกตรึงไว้
- embed union ของโพสต์ Bluesky ไม่ได้ถูกทำเครื่องหมายเป็น
closed - ดังนั้นจึงสร้างโพสต์ด้วย embed
$typeที่ไม่ได้ถูก enumerate ไว้ได้ - ในตัวอย่าง ใส่ embed
com.danielmangum.hack.sitesไว้ภายใน recordapp.bsky.feed.postและอ้างอิง blob HTML ภายในนั้นvalidationStatusใน response คือvalid- ในแอปพลิเคชัน Bluesky embed ดังกล่าวถูกละเลยไปอย่างเงียบ ๆ
- เนื้อหาและ reference ยังคงถูกจัดเก็บใน record ดังนั้นแอปพลิเคชันอื่นสามารถ render embed นี้ได้
- คุณลักษณะของ lexicon แบบนี้สามารถใช้เป็นวิธีต่อยอดเล็ก ๆ บนกรณีใช้งานเดิมได้ และตัวอย่างความเป็นไปได้คือการรันโค้ดชิ้นเล็ก ๆ ในโพสต์ภายใน WebAssembly sandbox
1 ความคิดเห็น
ความคิดเห็นบน Hacker News
รู้สึกขอบคุณที่ Daniel ติดต่อสอบถามทีมโดยตรง และ การโฮสต์บล็อบ ก็เป็นพื้นที่ที่คงต้องปรับแต่งกันต่อไปอย่างหลีกเลี่ยงไม่ได้ ขณะที่ทำความเข้าใจเส้นทางการนำไปใช้ในทางที่ผิดให้มากขึ้น
ตอนนี้การได้เห็นการใช้งานแบบนี้ทำงานได้จริงเป็นเรื่องน่าสนุก และโครงสร้างของ PDS ก็ตั้งใจให้เป็น โฮสต์ฐานข้อมูล ในลักษณะเดียวกับที่เว็บเซิร์ฟเวอร์โฮสต์เว็บไซต์
เมื่อก่อนเป็นของที่เอาไว้ลองเล่นได้สนุก และไม่รู้เลยว่าย้ายไป Bluesky แล้ว ค่อนข้างน่าสนใจทีเดียว
Bsky รองรับวิธีผูกโดเมนผู้ใช้เป็น ALIAS ไปที่ redirect.bsky.com อยู่แล้ว: https://bsky.app/profile/jacob.gold/post/3kh6rnpdzmp2v
สงสัยว่าสิ่งนี้รันอยู่ใน บริบทด้านความปลอดภัย แบบไหน เลยลองตรวจ URL บล็อบของ PDS ด้วย
curlและในเฮดเดอร์ของการตอบกลับมีaccess-control-allow-origin: *,content-security-policy: default-src 'none'; sandbox,x-content-type-options: nosniffเป็นต้นaccess-control-allow-origin: *น่าสนใจเพราะหมายความว่า JavaScript บนเว็บเพจจากโดเมนใดก็ได้สามารถเข้าถึงเนื้อหานี้ด้วยfetch()ได้ ส่วนdefault-src 'none'; sandboxเป็นการตั้งค่าที่จำกัดมากซึ่งป้องกันไม่ให้โหลดสคริปต์หรือรูปภาพเพิ่มเติม และยังบล็อกการรัน JavaScript ด้วย ซึ่งถือว่าดี: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Co...ratelimit-limit: 3000น่าจะเป็นการจำกัดตาม IPข้อเสนอท้ายบทความที่ให้ใช้ WebAssembly ถือว่าเป็นทิศทางที่ถูกต้อง และปัญหาเรื่องการคลิกลิงก์สามารถจำกัดได้ด้วยการใส่โค้ดที่ไม่น่าเชื่อถือไว้ใน iframe แล้วให้ parent ใช้
child-srcอนุญาตเฉพาะโดเมนที่กำหนดหรือdata:เท่านั้นhttps://github.com/w3c/webappsec/issues/656#issuecomment-246...
https://www.w3.org/TR/CSP3/#exfiltration
sandboxแล้วยังจำเป็นต้องมีdefault-srcอยู่ไหม หรือเป็น การตั้งค่าซ้ำซ้อน กันค่อนข้างคาดหวังกับความเป็นไปได้ในการใช้ Bluesky เป็น ที่เก็บข้อมูลบล็อบ
เคยคิดกับเพื่อนว่าจะเก็บ DOOM WAD ไว้บน Bluesky แล้วแชร์ “แมปแพ็ก” เหมือนโพสต์ จากนั้นถ้าฟอลโลว์บัญชี รายการ หรือสตาร์เตอร์แพ็ก ก็อาจปรับแต่งไคลเอนต์อย่าง GZDoom ให้ค้นหาและดู WAD ที่บัญชีเหล่านั้นอัปโหลดได้
คล้าย Steam Workshop แต่ทำผ่าน Bluesky
ไม่รู้ว่า Bluesky เคยพูดถึง แนวทางการใช้เป็นที่เก็บบล็อบ ไว้หรือเปล่า และไฟล์ DOOM มีขนาดเล็กจึงเป็นตัวอย่างที่ดี แต่ก็สงสัยว่าจะถูกนำไปใช้ในทางที่ผิดแบบผลักภาระพื้นที่เซิร์ฟเวอร์ขนาดใหญ่ไปไว้ข้างนอกได้ไหม
แม้จะไม่ได้ผ่านยุคนั้นโดยตรง แต่การที่ผู้ใช้ BlueSky กระโดดลงไปเล่นกับเรื่องเทคนิคเชิงลึกทำให้นึกถึงยุคที่คนเริ่มเรียน HTML ครั้งแรกบน MySpace
ตลาดโซเชียลมีเดียอิ่มตัวกว่าแต่ก่อนมากแล้ว แต่ก็สงสัยว่าจะมี โปรแกรมเมอร์รุ่นใหม่ เกิดขึ้นจาก BlueSky ได้หรือไม่
Bluesky ให้แค่ขั้นแรก และหลังจากนั้นดูเหมือนเป็นการกระโดดที่ค่อนข้างใหญ่
ถ้าเป็น HTTP/HTML ก็สามารถแสดงวิธีสร้างเซิร์ฟเวอร์ตั้งแต่ต้นให้คนที่รู้แค่ Python พื้นฐานหรือภาษาโปรแกรมอื่น ๆ ได้ภายใน 14 นาที
เห็นว่าถ้าจะไม่ให้ยักษ์เทคโนโลยีแสวงหาประโยชน์จากชุมชน จำเป็นต้องมี โปรโตคอลสื่อสารแบบระบุตำแหน่งด้วยแฮช แต่ยังไม่มั่นใจว่า AT Protocol เล็งเป้าหมายได้ดีพอที่จะจุดประกายการปฏิวัติแบบ HTTP หรือไม่
ประเด็นสำคัญอย่างหนึ่งคือ PDS ที่พูดถึงในที่นี้ไม่ได้เป็นตัวผลิตภัณฑ์ Bluesky เอง แต่เป็นส่วนหนึ่งของ Personal Data Server ดังนั้นผลลัพธ์อาจดูเหมือนที่เก็บข้อมูลฟรีไม่จำกัด
ดูเหมือนบริการสมัครสมาชิกที่ทีม Bluesky พูดถึงอยู่บ่อย ๆ อาจเชื่อมโยงกับส่วนนี้ และการโฮสต์ที่ให้พื้นที่ แบนด์วิดท์ และวิดีโอความละเอียดสูงมากขึ้นบน PDS ก็ดูเหมาะที่จะขายเป็นเทียร์พรีเมียม
ก็น่าจะโฮสต์ที่อื่นได้ด้วย ดังนั้นคำพูดที่ว่าสามารถโต้ตอบได้กว้างกว่าแอป Bluesky จึงดูถูกต้อง
เมื่อทำให้ผู้คนสามารถโพสต์อะไรบางอย่างขึ้นอินเทอร์เน็ตได้ สุดท้ายก็จะมีใครสักคนพยายามใช้มันเป็น โฮสติ้งไฟล์ตามอำเภอใจ ปรากฏการณ์นี้น่าจะต้องมีชื่อเรียก
ในยุคที่ยังไม่จำกัดความจุ มีโปรเจกต์โอเพนซอร์สหลายตัวออกมาพยายามสำรองข้อมูลไฟล์ซิสเต็มไว้ใน Gmail เป็นต้น และหลังจาก Drive เกิดขึ้น เรื่องก็ยิ่งหนักขึ้น
บริการฟรีควรคาดการณ์ปัญหาแบบนี้ไว้และตั้งข้อจำกัดพื้นฐานตั้งแต่แรก
https://en.wikipedia.org/wiki/Inner-platform_effect
ถ้าสนใจหัวข้อแบบนี้ atfile ก็น่าดูเช่นกัน: https://github.com/electricduck/atfile
ค่อนข้างเจ๋ง และสามารถดู GitHub issue ที่น่าสนใจซึ่งลิงก์ไว้ท้ายบทความได้ทันที
pfrazee บุคคลดังใน Bluesky ก็ปรากฏตัวด้วย: https://github.com/bluesky-social/atproto/issues/523
ผมคาดหวังกับ AT มาก และแม้ใน Fediverse จะมีคนเก่ง ๆ จำนวนมากที่ทำผลงานยอดเยี่ยม แต่พาราไดม์นี้ดูยั่งยืนและสมจริงกว่า
แนวทางคือให้ค่าเริ่มต้นเป็นแบบรวมศูนย์ แต่เมื่อจำเป็นหรือสำหรับผู้ใช้ขั้นสูง ก็ให้ การรองรับแบบกระจายศูนย์ จริง ๆ
จำเป็นต้องมีวิธีสร้างผลตอบแทนจากการลงทุนในการมิเรอร์ firehose เช่น อาจเป็นทางเลือกแทน Discord ที่ผู้ใช้บางส่วนจ่ายค่าโฮสต์วิดีโอที่ยาวขึ้น, โครงสร้างแบบ Patreon ที่ relay จัดการคีย์เข้าถึงและคีย์ถอดรหัสให้แล้วเก็บค่าธรรมเนียม, หรือโซเชียลมาร์เก็ตเพลสแบบ Bandcamp ก็ดูเป็นไปได้
ไม่ว่าอย่างไร ในเมื่อเป็นแพลตฟอร์มเปิด ก็ไม่มีเหตุผลที่จะทำไม่ได้แม้ตอนนี้
https://www.blockchaincapital.com/blog/bluesky-13m-users-and...
https://bsky.social/about/blog/09-11-2024-video
รู้สึกว่าวิธีนี้น่าจะถูกนำไปใช้ในทางที่ผิดเพื่อ ฟิชชิงหรือแจกจ่ายมัลแวร์ ได้ไม่ใช่หรือ
ดูเหมือนแทบจะเป็นกฎของคอมพิวติ้งเลยว่า ถ้าสร้างเว็บโฮสติ้งขึ้นมา จะมีใครสักคนพยายามใช้มันเพื่อจุดประสงค์ไม่ดี
ไฟล์แนบของ Matrix ก็มีปัญหาเดียวกัน
*.bsky.networkก็น่าจะเริ่มถูก Google Safe Browsing, Palo Alto, Bluecoat เป็นต้นบล็อกนี่เป็นลิงก์ตรงไปยังเซิร์ฟเวอร์ Bluesky ของผู้เขียน หรือก็คือ PDS ดังนั้นย่อมเป็นสิ่งที่ผู้เขียนควบคุมอยู่แล้ว
เมื่อเห็นการเปลี่ยนแปลง API ล่าสุดของ Strava ทำให้นึกได้ว่าการเข้าถึงข้อมูลที่เก็บอยู่บนแพลตฟอร์มของพวกเขาถูกจำกัดมากเพียงใด
ในฐานะบริการที่ครองตลาดด้านฟิตเนส พวกเขาอาจค่อย ๆ ย้ายฟีเจอร์ต่าง ๆ ไปไว้หลังกำแพงสมัครสมาชิกได้
แม้จะมีข้อกังวลเรื่องความเป็นส่วนตัวหรือความปลอดภัย แต่ก็สงสัยว่า AT Protocol จะเหมาะเป็นแพลตฟอร์มสำหรับเก็บไฟล์ GPX หรือ FIT ได้หรือไม่
แต่เท่าที่ทราบ AT Protocol ยังไม่รองรับ โพสต์แบบส่วนตัวหรือจำกัดการมองเห็น และผมมองว่าฟีเจอร์นี้ค่อนข้างเป็นหัวใจสำหรับการใช้งานแบบ Strava