Sistem ekstensi Chrome menerapkan Kebijakan Keamanan Konten (CSP) default yang cukup ketat.
Batasan kebijakannya sederhana: skrip harus dipindahkan ke luar 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 template
terutama rentan terhadap gaya implementasi ini. Meskipun beberapa framework (seperti Angular.js) mendukung CSP secara langsung, banyak framework populer yang belum diupdate ke mekanisme yang kompatibel dengan dunia tanpa eval
ekstensi. Oleh karena itu, menghapus dukungan untuk fungsi tersebut terbukti lebih
bermasalah dari yang diharapkan bagi developer.
Dokumen ini memperkenalkan sandbox sebagai mekanisme aman untuk menyertakan library ini dalam project Anda tanpa mengorbankan keamanan.
Mengapa sandbox?
eval
berbahaya di dalam ekstensi karena kode yang dieksekusi memiliki akses ke semua hal di lingkungan izin tinggi ekstensi. Ada banyak API chrome.*
canggih yang tersedia dan dapat
berdampak serius pada keamanan dan privasi pengguna; ekstraksi data sederhana adalah hal yang paling tidak perlu dikhawatirkan.
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 ada masalah.
Kita melakukannya dengan mencantumkan file HTML tertentu di dalam paket ekstensi sebagai sandbox.
Setiap kali dimuat, halaman dengan sandbox akan dipindahkan ke asal unik, dan akan ditolak aksesnya ke API chrome.*
. Jika memuat halaman dengan sandbox ini ke dalam ekstensi melalui iframe
, kita dapat
meneruskan pesan, membiarkannya bertindak berdasarkan pesan tersebut dengan cara tertentu, dan menunggunya meneruskan kembali
hasil. Mekanisme pesan sederhana ini memberi kita semua yang diperlukan untuk menyertakan kode yang didorong eval
dengan aman dalam alur kerja ekstensi.
Membuat dan menggunakan sandbox
Jika Anda ingin langsung mempelajari kode, dapatkan ekstensi contoh sandboxing dan mulailah. Ini adalah contoh kerja API pesan kecil yang dibuat di atas library template Handlebars, dan akan memberi Anda semua yang diperlukan untuk memulai. Bagi Anda yang ingin mendapatkan penjelasan lebih lanjut, mari kita pelajari contoh tersebut bersama-sama di sini.
Membuat daftar 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 terlupakan, jadi periksa kembali
file dengan sandbox Anda tercantum dalam manifes. Dalam contoh ini, kita membuat sandbox file yang diberi nama "sandbox.html". Entri manifes terlihat seperti ini:
{
...,
"sandbox": {
"pages": ["sandbox.html"]
},
...
}
Memuat file dengan sandbox
Untuk melakukan sesuatu yang menarik dengan file dengan sandbox, kita perlu memuat file tersebut dalam konteks yang dapat ditangani oleh kode ekstensi. Di sini, sandbox.html telah dimuat ke dalam
halaman ekstensi melalui 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
sebentar lagi.
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 berfungsi
seperti yang diharapkan, dan kita akan mendapatkan template yang dikompilasi di templates['hello']
.
Meneruskan hasil kembali
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 ke template secara langsung untuk rendering. HTML yang dirender
akan diteruskan 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 ke 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 menayangkannya melalui notifikasi, tetapi
Anda dapat menggunakan HTML ini dengan aman sebagai bagian dari UI ekstensi. Menyisipkannya 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. Setiap kode yang tidak berfungsi secara otomatis berdasarkan Kebijakan Keamanan Konten yang ketat dapat di-sandbox. Faktanya, sering kali berguna untuk membuat sandbox komponen ekstensi yang akan berjalan dengan benar guna membatasi setiap bagian program ke kumpulan hak istimewa terkecil yang diperlukan agar dapat dijalankan dengan benar. Presentasi Menulis Aplikasi Web dan Ekstensi Chrome yang Aman dari Google I/O 2012 memberikan beberapa contoh bagus tentang penerapan teknik ini, dan Anda akan menghabiskan waktu 56 menit untuk menontonnya.