Sistem ekstensi Chrome menerapkan Kebijakan Keamanan Konten (CSP) default yang cukup ketat.
Pembatasan kebijakan sangat mudah: skrip harus dipindahkan dari baris ke file JavaScript terpisah, pengendali peristiwa inline harus dikonversi untuk menggunakan addEventListener, dan eval() dinonaktifkan.
Namun, kami menyadari bahwa berbagai library menggunakan konstruksi seperti eval() dan eval, seperti
new Function() untuk pengoptimalan performa dan kemudahan ekspresi. Library pembuatan template sangat rentan terhadap gaya implementasi ini. Meskipun beberapa (seperti Angular.js) mendukung CSP secara langsung, banyak framework populer belum diupdate ke mekanisme yang kompatibel dengan dunia tanpa eval ekstensi. Oleh karena itu, penghapusan dukungan untuk fungsi tersebut terbukti lebih
bermasalah dari yang diharapkan bagi developer.
Dokumen ini memperkenalkan sandbox sebagai mekanisme yang aman untuk menyertakan library ini dalam project Anda tanpa mengorbankan keamanan.
Mengapa sandbox?
eval berbahaya di dalam ekstensi karena kode yang dieksekusinya memiliki akses ke semua hal di lingkungan izin tinggi ekstensi. Sejumlah API chrome.* yang canggih tersedia dan dapat berdampak parah pada keamanan dan privasi pengguna; eksfiltrasi data sederhana adalah hal yang paling tidak kami khawatirkan.
Solusi yang ditawarkan adalah sandbox tempat eval dapat mengeksekusi kode tanpa akses ke data ekstensi atau API bernilai tinggi ekstensi. Tidak ada data, tidak ada API, tidak masalah.
Kami melakukannya dengan mencantumkan file HTML tertentu di dalam paket ekstensi sebagai file yang di-sandbox.
Setiap kali halaman dengan sandbox dimuat, halaman tersebut akan dipindahkan ke origin unik, dan akan ditolak
akses ke API chrome.*. Jika kita memuat halaman sandbox ini ke ekstensi melalui iframe, kita dapat meneruskan pesan ke halaman tersebut, membiarkannya bertindak berdasarkan pesan tersebut dengan cara tertentu, dan menunggunya untuk meneruskan kembali hasil kepada kita. Mekanisme pengiriman pesan sederhana ini memberi kita semua yang kita butuhkan untuk menyertakan kode yang didorong eval dengan aman dalam alur kerja ekstensi kita.
Membuat dan menggunakan sandbox
Jika Anda ingin langsung mempelajari kode, dapatkan ekstensi contoh sandbox dan mulai. Ekstensi ini adalah contoh fungsi API pesan kecil yang dibangun di atas library template Handlebars, dan akan memberi Anda semua yang diperlukan untuk memulai. Bagi Anda yang ingin penjelasan lebih lanjut, mari kita pelajari contoh tersebut bersama-sama di sini.
Mencantumkan file dalam manifes
Setiap file yang harus dijalankan di dalam sandbox harus dicantumkan dalam manifes ekstensi dengan menambahkan properti
sandbox. Ini adalah langkah penting, dan mudah dilupakan, jadi periksa kembali apakah file sandbox Anda tercantum dalam manifes. Dalam contoh ini, kita akan melakukan sandboxing pada file yang diberi nama "sandbox.html" dengan cerdas. Entri manifes terlihat seperti ini:
{
...,
"sandbox": {
"pages": ["sandbox.html"]
},
...
}
Memuat file yang di-sandbox
Untuk melakukan sesuatu yang menarik dengan file yang di-sandbox, kita perlu memuatnya dalam konteks yang
dapat diakses oleh kode ekstensi. Di sini, sandbox.html telah dimuat ke
halaman ekstensi menggunakan iframe. File JavaScript halaman berisi kode yang mengirim pesan
ke sandbox setiap kali tindakan browser diklik dengan menemukan iframe
di halaman, dan memanggil postMessage() di contentWindow-nya. Pesan adalah objek
yang berisi tiga properti: context, templateName, dan command. Kita akan membahas context dan command nanti.
service-worker.js:
chrome.action.onClicked.addListener(() => {
chrome.tabs.create({
url: 'mainpage.html'
});
console.log('Opened a tab with a sandboxed page!');
});
extension-page.js:
let counter = 0;
document.addEventListener('DOMContentLoaded', () => {
document.getElementById('reset').addEventListener('click', function () {
counter = 0;
document.querySelector('#result').innerHTML = '';
});
document.getElementById('sendMessage').addEventListener('click', function () {
counter++;
let message = {
command: 'render',
templateName: 'sample-template-' + counter,
context: { counter: counter }
};
document.getElementById('theFrame').contentWindow.postMessage(message, '*');
});
Melakukan sesuatu yang berbahaya
Saat dimuat, sandbox.html akan memuat library Handlebars, serta membuat dan mengompilasi template inline
dengan cara yang disarankan Handlebars:
extension-page.html:
<!DOCTYPE html>
<html>
<head>
<script src="mainpage.js"></script>
<link href="styles/main.css" rel="stylesheet" />
</head>
<body>
<div id="buttons">
<button id="sendMessage">Click me</button>
<button id="reset">Reset counter</button>
</div>
<div id="result"></div>
<iframe id="theFrame" src="sandbox.html" style="display: none"></iframe>
</body>
</html>
sandbox.html:
<script id="sample-template-1" type="text/x-handlebars-template">
<div class='entry'>
<h1>Hello</h1>
<p>This is a Handlebar template compiled inside a hidden sandboxed
iframe.</p>
<p>The counter parameter from postMessage() (outer frame) is:
</p>
</div>
</script>
<script id="sample-template-2" type="text/x-handlebars-template">
<div class='entry'>
<h1>Welcome back</h1>
<p>This is another Handlebar template compiled inside a hidden sandboxed
iframe.</p>
<p>The counter parameter from postMessage() (outer frame) is:
</p>
</div>
</script>
Ini tidak gagal! Meskipun Handlebars.compile akhirnya menggunakan new Function, semuanya akan berfungsi
persis seperti yang diharapkan, dan kita akan mendapatkan template yang dikompilasi di templates['hello'].
Mengirimkan kembali hasil
Kita akan menyediakan template ini untuk digunakan dengan menyiapkan pemroses pesan yang menerima perintah
dari halaman ekstensi. Kita akan menggunakan command yang diteruskan untuk menentukan apa yang harus dilakukan (Anda dapat membayangkan melakukan lebih dari sekadar merender; mungkin membuat template? Mungkin mengelolanya dengan cara tertentu?), dan context akan diteruskan langsung ke template untuk rendering. HTML yang dirender
akan dikirim kembali ke halaman ekstensi sehingga ekstensi dapat melakukan sesuatu yang berguna dengannya nanti:
<script>
const templatesElements = document.querySelectorAll(
"script[type='text/x-handlebars-template']"
);
let templates = {},
source,
name;
// precompile all templates in this page
for (let i = 0; i < templatesElements.length; i++) {
source = templatesElements[i].innerHTML;
name = templatesElements[i].id;
templates[name] = Handlebars.compile(source);
}
// Set up message event handler:
window.addEventListener('message', function (event) {
const command = event.data.command;
const template = templates[event.data.templateName];
let result = 'invalid request';
// if we don't know the templateName requested, return an error message
if (template) {
switch (command) {
case 'render':
result = template(event.data.context);
break;
// you could even do dynamic compilation, by accepting a command
// to compile a new template instead of using static ones, for example:
// case 'new':
// template = Handlebars.compile(event.data.templateSource);
// result = template(event.data.context);
// break;
}
} else {
result = 'Unknown template: ' + event.data.templateName;
}
event.source.postMessage({ result: result }, event.origin);
});
</script>
Kembali di halaman ekstensi, kita akan menerima pesan ini, dan melakukan sesuatu yang menarik dengan data html
yang telah diteruskan kepada kita. Dalam hal ini, kita hanya akan mengulanginya melalui notifikasi, tetapi
HTML ini dapat digunakan dengan aman sebagai bagian dari UI ekstensi. Memasukkannya melalui
innerHTML tidak menimbulkan risiko keamanan yang signifikan karena kami memercayai konten yang telah dirender
dalam sandbox.
Mekanisme ini membuat pembuatan template menjadi mudah, tetapi tentu saja tidak terbatas pada pembuatan template. Kode apa pun yang tidak berfungsi langsung di bawah Kebijakan Keamanan Konten yang ketat dapat di-sandbox; bahkan, sering kali berguna untuk melakukan sandbox pada komponen ekstensi yang akan berjalan dengan benar untuk membatasi setiap bagian program Anda ke kumpulan hak istimewa terkecil yang diperlukan agar dapat dieksekusi dengan benar. Presentasi Writing Secure Web Apps and Chrome Extensions dari Google I/O 2012 memberikan beberapa contoh bagus tentang penerapan teknik ini, dan layak untuk ditonton selama 56 menit.