सैंडबॉक्स किए गए iframe में eval() का इस्तेमाल करें

Chrome का एक्सटेंशन सिस्टम, डिफ़ॉल्ट रूप से कॉन्टेंट की सुरक्षा के बारे में नीति (सीएसपी) को सख्ती से लागू करता है. नीति से जुड़ी पाबंदियां आसान हैं: स्क्रिप्ट को आउट-ऑफ़-लाइन, अलग-अलग JavaScript फ़ाइलों में ले जाना चाहिए. साथ ही, इनलाइन इवेंट हैंडलर को addEventListener का इस्तेमाल करने के लिए बदला जाना चाहिए. साथ ही, eval() बंद होना चाहिए.

हालांकि, हम मानते हैं कि कई तरह की लाइब्रेरी, eval() और eval जैसे कंस्ट्रक्ट का इस्तेमाल करती हैं, जैसे कि new Function() का इस्तेमाल परफ़ॉर्मेंस को ऑप्टिमाइज़ करने और एक्सप्रेशन को आसान बनाने के लिए किया जाता है. खास तौर पर, लाइब्रेरी को इस तरह से लागू करने की ज़रूरत होती है. हालांकि, कुछ (जैसे Angular.js) सीएसपी की सुविधा देते हैं, लेकिन कई लोकप्रिय फ़्रेमवर्क अब तक एक्सटेंशन की eval-की दुनिया के साथ काम करने वाले तरीके में अपडेट नहीं हुए हैं. इसलिए, उस सुविधा को हटाने का अनुरोध, डेवलपर के लिए उम्मीद से ज़्यादा मुश्किल साबित हुआ है.

यह दस्तावेज़, सुरक्षा से समझौता किए बिना, इन लाइब्रेरी को अपने प्रोजेक्ट में शामिल करने के लिए, सैंडबॉक्स की सुविधा को एक सुरक्षित तरीके के रूप में पेश करता है.

सैंडबॉक्स का इस्तेमाल क्यों करें?

eval किसी एक्सटेंशन के अंदर खतरनाक है, क्योंकि इसके ज़रिए एक्ज़ीक्यूट किए जाने वाले कोड के पास एक्सटेंशन की ज़्यादा अनुमति वाली सेटिंग की सभी चीज़ों का ऐक्सेस होता है. कई बेहतरीन chrome.* एपीआई उपलब्ध हैं, जो उपयोगकर्ता की सुरक्षा और निजता पर बुरा असर डाल सकते हैं. डेटा बाहर निकाला जाना हमारी सबसे चिंता का हिस्सा नहीं है. ऑफ़र पर मिलने वाला समाधान एक सैंडबॉक्स है. इसमें eval, एक्सटेंशन के डेटा या एक्सटेंशन के ज़्यादा वैल्यू वाले एपीआई को ऐक्सेस किए बिना कोड को एक्ज़ीक्यूट कर सकता है. कोई डेटा नहीं, कोई एपीआई नहीं, कोई समस्या नहीं.

ऐसा करने के लिए, हम एक्सटेंशन पैकेज में मौजूद खास एचटीएमएल फ़ाइलों को सैंडबॉक्स के तौर पर लिस्ट करते हैं. जब भी कोई सैंडबॉक्स किया गया पेज लोड होगा, तो उसे यूनीक ऑरिजिन पर ले जाया जाएगा. साथ ही, उसे chrome.* API का ऐक्सेस नहीं दिया जाएगा. अगर हम इस सैंडबॉक्स किए गए पेज को iframe की मदद से अपने एक्सटेंशन में लोड करते हैं, तो हम इसे मैसेज भेज सकते हैं. साथ ही, उन मैसेज पर किसी तरह से कार्रवाई कर सकते हैं और तब तक इंतज़ार कर सकते हैं, जब तक हमें कोई नतीजा नहीं मिलता. मैसेज करने के इस आसान तरीके से, हमें वह सब कुछ मिलता है जिसकी मदद से हम अपने एक्सटेंशन के वर्कफ़्लो में, eval वाला कोड सुरक्षित तरीके से शामिल कर सकते हैं.

सैंडबॉक्स बनाना और उसका इस्तेमाल करना

अगर आपको सीधे कोड का इस्तेमाल करना है, तो सैंडबॉक्सिंग सैंपल एक्सटेंशन आज़माएं और टेकऑफ़ करें. यह हैंडलबार की लाइब्रेरी के ऊपर बने एक छोटे से मैसेजिंग एपीआई का काम करने वाला उदाहरण है. इससे आपको आगे बढ़ने में मदद मिलेगी. आप में से जो लोग इसके बारे में और जानना चाहते हैं, उन्हें हम इस सैंपल के बारे में बताते हैं.

मेनिफ़ेस्ट में फ़ाइलों की सूची बनाएं

सैंडबॉक्स में चलने वाली हर फ़ाइल के लिए, sandbox प्रॉपर्टी जोड़कर एक्सटेंशन मेनिफ़ेस्ट की सूची में शामिल होना ज़रूरी है. यह एक ज़रूरी चरण है और इसे भूलना आसान है. इसलिए, अच्छी तरह जांच लें कि आपकी सैंडबॉक्स की गई फ़ाइल मेनिफ़ेस्ट में दी गई है या नहीं. इस नमूने में, हम बड़े पैमाने पर "sandbox.html" नाम वाली फ़ाइल को सैंडबॉक्स कर रहे हैं. मेनिफ़ेस्ट में ऐसी एंट्री दिखती है:

{
  ...,
  "sandbox": {
     "pages": ["sandbox.html"]
  },
  ...
}

सैंडबॉक्स की गई फ़ाइल लोड करें

सैंडबॉक्स की गई फ़ाइल के साथ कुछ दिलचस्प करने के लिए, हमें इसे उस संदर्भ में लोड करना होगा जहां एक्सटेंशन के कोड से इसका पता लगाया जा सके. यहां, sandbox.html को iframe के ज़रिए एक्सटेंशन पेज में लोड किया गया है. पेज की JavaScript फ़ाइल में ऐसा कोड होता है जो पेज पर iframe को ढूंढकर, और इसके contentWindow पर postMessage() को कॉल करके, ब्राउज़र की कार्रवाई को क्लिक करने पर सैंडबॉक्स में एक मैसेज भेजता है. मैसेज एक ऑब्जेक्ट है, जिसमें तीन प्रॉपर्टी होती हैं: context, templateName, और command. हम कुछ ही देर में context और command के बारे में जानकारी देंगे.

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, '*');
  });

कुछ खतरनाक करना

sandbox.html लोड होने पर, यह हैंडलबार लाइब्रेरी को लोड करता है. साथ ही, हैंडलबार के सुझाव के मुताबिक इनलाइन टेंप्लेट बनाता और कंपाइल करता है:

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>

ऐसा नहीं होता है! भले ही Handlebars.compile, new Function का इस्तेमाल करना बंद कर दे, लेकिन चीज़ें उम्मीद के मुताबिक काम करती हैं. आखिर में, हमें templates['hello'] में कंपाइल किया गया टेंप्लेट मिलता है.

नतीजे को फिर से पास करें

हम मैसेज लिसनर सेट करके, इस टेंप्लेट को इस्तेमाल के लिए उपलब्ध कराएंगे, जो एक्सटेंशन पेज से निर्देश स्वीकार करेगा. हम दिए गए command का इस्तेमाल यह तय करने के लिए करेंगे कि क्या किया जाना चाहिए (आप सिर्फ़ रेंडर करने के अलावा कई और काम करने की कल्पना कर सकते हैं; शायद टेंप्लेट बनाना चाहते हैं? शायद उन्हें किसी तरह से मैनेज करना हो?), और context को रेंडरिंग के लिए सीधे टेंप्लेट में पास कर दिया जाएगा. रेंडर किए गए एचटीएमएल को एक्सटेंशन पेज पर वापस पास कर दिया जाएगा, ताकि बाद में एक्सटेंशन इसके साथ काम कर सके:

 <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>

एक्सटेंशन पेज पर वापस आने पर, हमें यह मैसेज मिलेगा और html डेटा के साथ कुछ दिलचस्प काम होंगे. इस मामले में, हम सिर्फ़ एक सूचना के ज़रिए उसे आवाज़ में सुनाई देंगे, लेकिन एक्सटेंशन के यूज़र इंटरफ़ेस (यूआई) के हिस्से के रूप में इस एचटीएमएल का सुरक्षित तरीके से इस्तेमाल करना पूरी तरह से मुमकिन है. इसे innerHTML की मदद से शामिल करने से, सुरक्षा को लेकर ज़्यादा खतरा नहीं होता. ऐसा इसलिए होता है, क्योंकि सैंडबॉक्स में रेंडर किए गए कॉन्टेंट पर हमें भरोसा होता है.

इस तरीके की मदद से, टेंप्लेट बनाने की प्रोसेस आसान हो जाती है. हालांकि, यह सिर्फ़ टेंप्लेट के इस्तेमाल तक ही सीमित नहीं है. ऐसा कोई भी कोड जो कॉन्टेंट की सुरक्षा के बारे में सख्त नीति के तहत काम नहीं करता, उसे सैंडबॉक्स किया जा सकता है. असल में, यह अक्सर आपके एक्सटेंशन के कॉम्पोनेंट को सैंडबॉक्स करने के लिए काम का होता है, जो सही तरीके से होते हैं. ऐसा इसलिए किया जाता है, ताकि आपके प्रोग्राम के हर हिस्से को सही तरीके से काम करने के लिए ज़रूरी खास सुविधाओं वाले छोटे सेट तक सीमित किया जा सके. Google I/O 2012 की राइटिंग सिक्योर वेब ऐप्लिकेशन और Chrome एक्सटेंशन प्रज़ेंटेशन में, इन तकनीकों का इस्तेमाल करने के कुछ अच्छे उदाहरण दिए गए हैं. इन्हें अपनाने पर, आपको 56 मिनट का समय मिलेगा.