Share
Embed
ユーザーが作成したコードをブラウザ上で安全に実行できる Plugin システムへのアプローチ
BuriKaigi2026
- https://fortee.jp/burikaigi-2026/proposal/c8e1a1a4-6eb5-4bc9-bc25-a846183e4efa
X
- https://twitter.com/sajikix
Saji
January 08, 2026
Transcript
63 slides
-
͜ͷτʔΫͰ͢͜ͱ ʮ1MVHJOγεςϜʯͷೝࣝ߹Θͤ 1MVHJOγεςϜͷ͍͠ͱ͜Ζ ʮִʯͷٕज़બఆ ʮݖݶཧʯΛݫີʹ
࣮ྫΛݟͯΈΑ͏ ༗༻ͳϥΠϒϥϦͱظ͢Δ༷ -
1MVHJOγεςϜͷΠϝʔδ ΞϓϦέʔγϣϯιϑτΣΞͷػೳΛ֦ு͢ΔͨΊʹՃ͢Δ͜ͱͷͰ͖ ΔϓϩάϥϜϞδϡʔϧͷҰछɻ 8JLJQFEJBʮϓϥάΠϯʯͷϖʔδΑΓ TU1BSUZͱSE1BSUZ w TU1BSUZΞϓϦέʔγϣϯ։ൃଆ͕ग़͍ͯ͠Δެࣜͷͷ w SE1BSUZϢʔβʔΞϓϦέʔγϣϯ։ൃଆͰͳ͍اۀͳͲ͕࡞
SE1BSUZͷͷؚΊͯɺͷ֦ுΛड͚ೖΕΔΈ͕ʮ1MVHJOγες ϜʯͱݺΕ͕ͪͳؾ͕͢Δ ͋͘·Ͱൃදऀͷײ֮Ͱ -
αΠϘζͰ࣮ࡍͷ༻్ྫ ࣮ࡍʹΘΕ͍ͯΔྫ w ݟͨͷΧελϚΠζ w 'PSN෦ͷ6*ͷมߋ w 'PSNೖྗޙͷಠࣗόϦσʔγϣϯ w ΧϯόϯͳͲͷಠࣗ6*ͷૠೖ
w 3&45"1*ͰόοΫάϥϯυॲཧ 👇͜͜ʂ -
͜ͷτʔΫͰͷʮ1MVHJOγεςϜʯ ༷ʑͳʮ1MVHJOγεςϜʯ͕ଘࡏ͢Δ͕ɺ͜ͷτʔΫͰҎԼΛରͱ͢Δ w ϒϥβ্Ͱಈ͘ͷ 㱠αʔόʔαΠυ w ඞવతʹ΄΅+4Ͱॻ͔ΕΔ 8BTNͳΒʁΈ͍ͨͳޙड़
w SE1BSUZ Ϣʔβʔɾύʔτφʔاۀ༷ ʹΑΔͷ͕΄ͱΜͲ w ۀɾχʔζʹ߹Θ֦ͤͨுɾޮԽखஈͱͯ͠ར༻͞ΕΔ ͳͷͰ͜ͷൃදͷλΠτϧʮϢʔβʔ͕࡞ͨ͠ίʔυΛϒϥβ্Ͱ҆શ ʹ࣮ߦͰ͖Δ1MVHJOγεςϜͷΞϓϩʔνʯ -
Γ҆શੑ ·ͣ࠷ॳʹࢥ͍ͭ͘Ͱ͋Ζ͏͕҆શੑ Ұาؒҧ͑Δͱ w Ϣʔβʔ͕͖ʹίʔυΛ࣮ߦͰ͖Δˠ͖ʹෆਖ਼ɾഁյͰ͖Δ w ԚછʹΑͬͯΞϓϦέʔγϣϯίʔυͷਖ਼ৗͳ࣮ߦΛ͛ͨΓ w ͨ͘͠ͳ͍ใ·ͰऔΕͯ͠·ͬͨΓ w
FUD ˠ҆શʹ1MVHJOͷίʔυΛ࣮ߦͰ͖Δ͜ͱ͕େલఏ -
1MVHJOγεςϜΛ͘αϙʔτ͢Δͱ͍͏͜ͱ ͘1MVHJOγεςϜΛαϙʔτ͢ΔͱҎԼͷΑ͏ͳ͕ൃੜͯ͘͠Δ w 1MVHJOػೳͷ͍ͤͰຊମϩδοΫ͕ෳࡶʹͳΓɺ։ൃίετ͕ංେ w 1MVHJOػೳࣗମͷ࣮ίετ "1*Ͱطଘಈ࡞հೖ͢Δ෦ͷෳࡶ͞ w ྫ͋Δ'JFMEͷදࣔɾඇදࣔӾཡݖݶͱ"1*ʹΑΔૢ࡞྆ํΛධՁ w
ʮ1MVHJOͰͰ͖Δ͜ͱɾڍಈʯͯ͢ίϛϡχςΟʹਁಁ͠ɺґଘͱͳΔ w ͍ΘΏΔϋΠϥϜͷ๏ଇͱݺΕΔͷ w ৽͍͠ػೳΛ࡞Δͨͼʹʮ͜Εطଘͷ1MVHJOյΕͳ͍ΑͶʁʯͱͳΔ -
ݪଇ ͜ΕΒͷΛ౿·͑Δͱ1MVHJOγεςϜʹେࣄͳͷҎԼͷ̏ͭ ͪΌΜͱ1MVHJOͷ࣮ߦڥΛִ͢Δ͜ͱ w ҆શੑͷ໘Ͱͷమଇ ִڥͱຊମڥͷΓऔΓΛ୯Ұͷͷʹ͢Δ͜ͱ w ͜ΕΛ͓͔ͯ͠ͳ͍ͱݖݶͷ੍ޚͳͲ͕͍͠
8FCඪ४ͷΈʹͳΔ͘Δ͜ͱ w ಠࣗϩδοΫ͕૿͑Δ΄Ͳಠࣗڍಈ૿͑ɺຊମͷ։ൃΛಷԽͤ͞Δ -
JGSBNFͰִ͢Δ ΞϓϦέʔγϣϯຊମʹຒΊࠐΜͩJGSBNFͰϓϥάΠϯίʔυΛ࣮ߦ͢Δํ๏ ϝϦοτ👍 w ϒϥβ্ͷػೳ͕΄ͱΜͲͦͷ··͑Δ w ϖʔδΛຒΊࠐΜͰΔͷͰ6* %0. ͍࣋ͬͯΔ σϝϦοτ👎
w େྔʹੜ͢ΔͱύϑΥʔϚϯεͷݒ೦͕͋Δ w ΞϓϦέʔγϣϯຊମͱͷΓऔΓجຊඇಉظʹͳΔ ಉظ"1*͍͠ -
+4&OHJOFPO8BTNͰִ͢Δલఏ 8BTN 8FC"TTFNCMZ ʹ͍ͭͯ w 8FCϒϥβ্Ͱߴʹಈ࡞͢Δɺ$$ ɺ3VTUɺ(PͳͲ༷ʑͳݴޠ͔Β ίϯύΠϧՄೳͳόΠφϦܗࣜͷ໋ྩηοτɾϑΥʔϚοτ 8BTN্Ͱ+4ಈ͔ͳ͍ ˠ8BTN্Ͱ+4&OHJOFΛಈ͔ͯͦ͠ͷ্Ͱ+4Λ࣮ߦ͢Ε͍͍ͷͰʁ
ˠʮ+4&OHJOFPO8BTNʯ w ࣮ࡍͷݕূ2VJDLKTΛ8BTN্Ͱಈ͔ͯͦ͠ͷ্Ͱ+4Λ࣮ߦͨ͠ -
+4&OHJOFPO8BTNͰִ͢Δ ϝϦοτ👍 w ϝϞϦϨϕϧͰִ͞Εͨ+4ͷ࣮ߦڥ͕ಘΒΕΔ 8BTNͷڧΈ w ͏·͘ڮ͠Λ͢Εಉظతͳ"1*Λ࡞Δ͜ͱՄೳ σϝϦοτ👎 w
ΞϓϦέʔγϣϯຊମͱͷڮ͠ΛࣗͨͪͰ࣮͢Δඞཁ͕͋Δ w ར༻Ͱ͖Δ+4ͷػೳ+4&OHJOF࣍ୈ+4ͷݴޠػೳ͔͑͠ͳ͍ w 6*ͳ͍ͷͰ%0.ૢ࡞Ͱ͖ͳ͍ॳظϩʔυͷ໘Ͱෆར -
3FBMNT4IJNͰִ͢Δલఏ ͦͦ3FBMNT &$."4DSJQU3FBMNT ͱ w &$."4DSJQU +4ͷ༷ Ͱఆٛ͞Ε͍ͯΔ֓೦ͷͭ w +4͕ධՁ͞ΕΔάϩʔόϧมϏϧυΠϯΫϥεɺ࣮ߦ͞ΕΔίʔυঢ়
ଶɾϦιʔεͳͲΛͯ͢·ͱΊͷ w ΑΓৄ͘͠.%/ͷʮ+BWB4DSJQUFYFDVUJPONPEFMʯͱ͍͏هࣄ͕ྑ͍ 3FBMN4IJNT3FBMNTͱݺΕΔΈ ֓೦ Λ࣮ݱ͢ΔTIJN w 㲈Ծతʹ3FBMNΛ৽͘͠࡞ΕͯΔΑ͏ʹ͢Δٕज़ -
બΕͨͷʮJGSBNFʯͰͨ͠ େ͖͔ͬͨϝϦοτ w 6*Λ͍࣋ͬͯͯ%0.ૢ࡞ΛؚΊͨ΄΅ᘳͳ+4࣮ߦڥͰ͋Δ͜ͱ w ͪΌΜͱઃఆ ޙड़ ͢Ε࣮ߦΛ҆શʹִ͢Δ͜ͱ͕Մೳ w 8FCͷඪ४ػೳʹΕΔͷͰϓϥάΠϯػߏ͕ෳࡶԽ͠ʹ͍͘
͋Δఔڐ༰Ͱ͖Δͱஅͨ͠σϝϦοτ w ύϑΥʔϚϯεˠ4&0ϢʔβମݧΛؾʹ͢ΔͷΑΓएׯ༏ઌ͕͍ w "1*͕ඇಉظͳΔˠࠓޙՃ͍ͯ͘͠"1*Ͱॱ࣍ඇಉظͷͷ͚ͩʹ -
1MVHJOͷݖݶͷཧΛ͍ͨ͠ 1MVHJOଆ͕ΞΫηεͰ͖ΔใɾͰ͖Δૢ࡞Λઃఆ௨Γʹ੍ݶ͍ͨ͠ 1MVHJOͷִڥ͔ΒϦιʔεʹΞΫηε͢Δϧʔτͭ w ִڥ JGSBNF ↔︎ ΞϓϦέʔγϣϯຊମͷ௨৴ w
ִڥ JGSBNF ˠ֎෦Ϧιʔε ֎෦ͷ"1*ɾαʔϏεͳͲ ˠ͜ͷ͕ͭઃఆ௨Γʹ੍ݶͰ͖ΔΈ͕͋Εྑ͍ʂ👍 -
ִڥ ↔︎ ΞϓϦέʔγϣϯຊମͷ௨৴Λ੍ݶ ͜ͷ߹୯ʹΞϓϦέʔγϣϯଆ͕ϦΫΤετΛڋ൱͢Εྑ͍ w ͋͘·ͰओಋݖΞϓϦέʔγϣϯଆ͕࣋ͬͯΔ ΞϓϦέʔγϣϯຊମ 1MVHJO" ڐՄ͞Εͨ"1* ྫHFU"QQ*E
ฦ͢Δ \JE^ ڐՄ͞Εͯͳ͍"1* ྫVQEBUF ❌ ෆڐՄ 1FSNJTTJPO&SS -
࣮ࡍʹߟ͑ͨ৽͍͠1MVHJOγεςϜͷߏ ͜Ε·Ͱͷٕज़બఆΛ౿·্͑ͨͰࠓճ࣮ͨ͠1MVHJOγεςϜ ΞϓϦέʔγϣϯຊମ BQQDPN JGSBNF 1MVHJO" TSDBQMVHJODPN JGSBNF
1MVHJO# TSDCQMVHJODPN $ 4 1 $ 4 1 $IBOOFM .FTTBHF -
ࠓճͷߏͰϙΠϯτͱͳΔͱ͜Ζ ࠓ·Ͱͷٕज़બఆΛ౿·্͑ͨͰϙΠϯτͱͳΔ෦ w ࣮ߦڥ$SPTT0SJHJOJGSBNFʹ͢Δ w OVMMPSJHJOͱTFDVSF$POUFYUʹ͍ͭͯ w ҟͳΔϓϥάΠϯಉ࢜$SPTT0SJHJOʹ w JGSBNFͷTBOECPYଐੑ
w JGSBNF ↔︎ ΞϓϦέʔγϣϯຊମͷ௨৴ $IBOOFM.FTTBHJOH"1* w NBOJGFTU fi MFͱ$41ʹΑΔ֎෦ϦιʔεΞΫηεͷ੍ݶ -
࣮ߦڥ$SPTT0SJHJOJGSBNF ࣮ߦڥͱͳΔJGSBNFΞϓϦέʔγϣϯຊମͱ$SPTT0SJHJOʹ͢Δ ˠ͜͏͢ΔͱʮJGSBNFͷ ΞϓϦέʔγϣϯຊମ ࢀর͕Ͱ͖ͳ͘ͳΔʯͱ ͍͏ϝϦοτ͕͋Δ ۩ମతʹҎԼͷΑ͏ͳܗ w ຊମͱ$SPTT0SJHJOͳυϝΠϯͰ1MVHJO͕࣮ߦ͞ΕΔϖʔδΛ)PTUJOH w
IUUQTGPPDPN ຊମ ↔︎ IUUQTGPPQMVHJOBDPN ϓϥάΠϯ w ˢΛJGSBNFͱͯ͠ຒΊࠐΉ -
࣮ߦڥ$SPTT0SJHJOJGSBNF 4BNF0SJHJOͷ߹ ΞϓϦέʔγϣϯ 1BSFOU 1MVHJO *GSBNF ΞΫηε Ͱ͖ͪΌ͏ $SPTT0SJHJOͷ߹ ΞϓϦέʔγϣϯ
1BSFOU 1MVHJO *GSBNF ΞΫηε Ͱ͖ͳ͍ ❌ -
JGSBNF ↔︎ ΞϓϦέʔγϣϯຊମͷ௨৴ ΞϓϦέʔγϣϯຊମͱͷ௨৴$IBOOFM.FTTBHJOH"1*Λར༻͢Δ $IBOOFM.FTTBHJOH"1*ͱ w ҟͳΔίϯςΩετؒͰ௨৴Λߦ͏ͨΊͷඪ४ػೳ w *GSBNF ↔︎
ຒΊࠐΈݩϖʔδͭͷJ'SBNFಉ࢜จॻͱ4IBSFE8PSLFS ΞϓϦέʔγϣϯຊମͱ$PSTT0SJHJOJGSBNFͱͰ௨৴͢Δ།Ұͷํ๏ w ٯʹݴ͑$IBOOFM.FTTBHJOH"1*ʹݶఆͰ͖Δ -
$IBOOFM.FTTBHJOH"1*Ͱͷૄ௨ ຊମˠQMVHJOJGSBNFʹ66*%Λૹ৴͢Δ const uuid = generateUUID(); const channel = new
MessageChannel(); function onLoad() { // send UUID iframe.contentWindow.postMessage(uuid, "*", [channel.port2]); } -
$IBOOFM.FTTBHJOH"1*Ͱͷૄ௨ JGSBNFड͚औͬͨΒૹΓओͷPSJHJOΛνΣοΫͦ͠ͷ··ฦ৴ const handler = (e) => { if (!checkMessageOrigin(e.origin))
{ reject(new Error("Invalid origin")); } messagePort.postMessage(e.data); window.removeEventListener("message", handler); // ... }; window.addEventListener("message", handler); -
$IBOOFM.FTTBHJOH"1*Ͱͷૄ௨ ຊମ1MVHJOJGSBNF͔Βͷฦ৴Λ֬ೝ 66*%͕ಉ͡PSJHJO͕ਖ਼͍͔͠ const handler = (e) => { if
(e.data === uuid && checkMessageOrigin(e.origin)) { resolve(channel.port1); } else { reject(new Error("Invalid message origin or uuid")); } channel.port1.removeEventListener("message", handler); }; channel.port1.addEventListener("message", handler); -
/FYU ʮ1MVHJOγεςϜʯͷೝࣝ߹Θͤ 1MVHJOγεςϜͷ͍͠ͱ͜Ζ ʮִʯͷٕज़બఆ ʮݖݶཧʯΛݫີʹ
ࠓճͷઃܭΛݟͯΈΑ͏ ༗༻ͳϥΠϒϥϦͱظ͢Δ༷👈 -
4IBEPX3FBMNT1SPQPTBM ෮श &$."4DSJQU +4ͷ༷ ʹ3FBMNTͱ͍͏֓೦͕͋ͬͨ w ݱঢ়3FBMNT͚ͩΛϢʔβʔ͕ࣗ༝ʹ࡞͢Δํ๏ͳ͍ ͱ͍͏͜ͱͰʮࣗ༝ʹ3FBMNTΛ࡞ΕΔΑ͏ʹ͍ͨ͠ʯͱ͍͏༷ఏҊ͕͋Δ ˠ4IBEPX3FBMNT1SPQPTBM ݱࡏ4UBHFͷ༷ͳͷͰ·͔͔ͩ࣌ؒΓͦ͏
w +4ͷ༷ఏҊʹ4UBHF͕͋ͬͯɺˠˠˠˠͱ্͕͍ͬͯ͘ w 4UBHFͰΕͯਖ਼༷ࣜʹͳΔ -
4IBEPX3FBMNT1SPQPTBM 3FBMNTΛ໌ࣔతʹ࡞Ͱ͖Δ4IBEPX3FBMNΫϥεΛՃ͢Δ w ࡞͞Εͨ3FBMNͰ+BWB4DSJQUΛධՁͰ͖Δ const realm = new ShadowRealm(); const
result = realm.evaluate(` function add(s1, s2) {return s1 + s2;} add("Hello, ", "world!"); `); console.log(result); // Hello, World! -
·ͱΊ w ϒϥβ্ʹ͓͚Δ1MVHJOγεςϜʹ҆શੑҎ֎ʹ͕͋Δ w ִͷํ๏ͱͯ͠ݱঢ়͍͔ͭ͘ߟ͑ΒΕΔ͕ɺࠓճJGSBNFΛબͨ͠ w JGSBNFͰͷִʹՃ͑ͯ$41Λઃఆ͢ΔͱΑΓݖݶΛݫີʹཧͰ͖Δ w ٕज़બఆΛͱʹࠓճઃܭͨ͠1MVHJOγεςϜͷ֎؍ͱҙΛઆ໌ͨ͠ w
ߏ$IBOOFM.FTTBHJOHͷҙ0SJHJOͷѻ͍$41FUD w JGSBNFͱͷ௨৴ʹศརͳ$PNMJOLͱ4IBEPX3FBMNT1SPQPTBMͷظ