স্যান্ডবক্স করা iframe-এ eval() ব্যবহার করা

Chrome-এর এক্সটেনশন সিস্টেম বেশ কঠোর ডিফল্ট কন্টেন্ট সংক্রান্ত নিরাপত্তা নীতি (CSP) প্রয়োগ করে। নীতি সংক্রান্ত বিধিনিষেধগুলি সহজ: স্ক্রিপ্টটি অবশ্যই আলাদা জাভাস্ক্রিপ্ট ফাইলে আউট-অফ-লাইন সরাতে হবে, ইনলাইন ইভেন্ট হ্যান্ডলারগুলিকে অবশ্যই addEventListener ব্যবহার করার জন্য কনভার্ট করতে হবে এবং eval() বন্ধ করা আছে।

তবে, আমরা জানি যে বিভিন্ন লাইব্রেরি eval() এবং eval-এর মতো কনস্ট্রাক্ট ব্যবহার করে, যেমন new Function(), যাতে পারফর্ম্যান্স অপ্টিমাইজ করা যায় এবং সহজে প্রকাশ করা যায়। টেমপ্লেটিং লাইব্রেরিগুলি বিশেষ করে এই ধরনের বাস্তবায়নের প্রবণতা রয়েছে। যদিও কিছু (যেমন Angular.js) CSP আউট অফ দ্য বক্স সাপোর্ট করে, অনেক জনপ্রিয় ফ্রেমওয়ার্ক এখনও এমন মেকানিজমে আপডেট করা হয়নি যা এক্সটেনশনের eval-লেস ওয়ার্ল্ডের সাথে মানানসই। তাই, সেই কার্যকারিতার জন্য সহায়তা সরিয়ে দেওয়া ডেভেলপারদের জন্য প্রত্যাশিত সমস্যার চেয়ে বেশি বলে প্রমাণিত হয়েছে।

এই ডকুমেন্টটি আপনার প্রোজেক্টে নিরাপত্তা বিঘ্নিত না করে এইসব লাইব্রেরি অন্তর্ভুক্ত করার জন্য একটি নিরাপদ পদ্ধতি হিসেবে স্যান্ডবক্সিংয়ের সাথে পরিচয় করিয়ে দেয়।

স্যান্ডবক্স কেন?

এক্সটেনশনের মধ্যে eval বিপজ্জনক কারণ এটি যে কোড এক্সিকিউট করে তার কাছে এক্সটেনশনের উচ্চ-অনুমতি এনভায়রনমেন্টের সবকিছুর অ্যাক্সেস থাকে। অনেক শক্তিশালী browser.* API উপলভ্য আছে যা ব্যবহারকারীর নিরাপত্তা ও গোপনীয়তার উপর মারাত্মক প্রভাব ফেলতে পারে; ডেটা এক্সফিলট্রেশন হল আমাদের উদ্বেগের সবচেয়ে ছোট কারণ। অফার করা সমাধানটি হল একটি স্যান্ডবক্স যেখানে eval এক্সটেনশনের ডেটা বা এক্সটেনশনের উচ্চ-মূল্যের API অ্যাক্সেস না করেই কোড এক্সিকিউট করতে পারে। কোনও ডেটা নেই, কোনও API নেই, কোনও সমস্যা নেই।

আমরা এক্সটেনশন প্যাকেজের মধ্যে নির্দিষ্ট HTML ফাইল স্যান্ডবক্স করা হিসেবে তালিকাভুক্ত করে এটি করি। যখনই কোনও স্যান্ডবক্স করা পৃষ্ঠা লোড করা হয়, এটি একটি অনন্য অরিজিনে সরানো হবে এবং browser.* API-তে অ্যাক্সেস প্রত্যাখ্যান করা হবে। আমরা যদি iframe-এর মাধ্যমে আমাদের এক্সটেনশনে এই স্যান্ডবক্স করা পৃষ্ঠা লোড করি, তাহলে আমরা এটিকে মেসেজ পাঠাতে পারি, এটিকে কোনওভাবে সেই মেসেজ অনুযায়ী কাজ করতে দিতে পারি এবং এটি আমাদের কাছে ফলাফল ফেরত পাঠানোর জন্য অপেক্ষা করতে পারি। এই সহজ মেসেজিং মেকানিজম আমাদের এক্সটেনশনের ওয়ার্কফ্লোতে নিরাপদে eval-চালিত কোড অন্তর্ভুক্ত করার জন্য প্রয়োজনীয় সবকিছু প্রদান করে।

স্যান্ডবক্স তৈরি ও ব্যবহার করা

আপনি যদি সরাসরি কোড দেখতে চান, তাহলে স্যান্ডবক্সিং স্যাম্পেল এক্সটেনশন নিয়ে শুরু করুন। এটি Handlebars টেমপ্লেটিং লাইব্রেরির উপরে তৈরি একটি ছোট মেসেজিং API-এর কার্যকরী উদাহরণ এবং এটি আপনাকে শুরু করার জন্য প্রয়োজনীয় সবকিছু প্রদান করবে। আপনাদের মধ্যে যারা আরও একটু ব্যাখ্যা চান, আসুন আমরা এখানে একসাথে সেই স্যাম্পেলটি দেখে নিই।

ম্যানিফেস্টে ফাইলের তালিকা তৈরি করুন

স্যান্ডবক্সের মধ্যে রান করতে হবে এমন প্রতিটি ফাইলকে এক্সটেনশন ম্যানিফেস্টে sandbox প্রপার্টি যোগ করে তালিকাভুক্ত করতে হবে। এটি একটি গুরুত্বপূর্ণ ধাপ এবং এটি ভুলে যাওয়া সহজ, তাই আপনার স্যান্ডবক্স করা ফাইলটি ম্যানিফেস্টে তালিকাভুক্ত আছে কিনা তা ডবল চেক করুন। এই স্যাম্পেলে, আমরা "sandbox.html" নামের ফাইলটি স্যান্ডবক্স করছি। ম্যানিফেস্ট এন্ট্রি দেখতে এইরকম হয়:

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

স্যান্ডবক্স করা ফাইল লোড করুন

স্যান্ডবক্স করা ফাইল নিয়ে আকর্ষণীয় কিছু করতে হলে, আমাদের এটি এমন একটি প্রসঙ্গে লোড করতে হবে যেখানে এক্সটেনশনের কোড এটিকে অ্যাড্রেস করতে পারবে। এখানে, sandbox.html একটি এক্সটেনশন পৃষ্ঠায় iframe ব্যবহার করে লোড করা হয়েছে। পৃষ্ঠার জাভাস্ক্রিপ্ট ফাইলে এমন কোড থাকে যা ব্রাউজার অ্যাকশনে ক্লিক করা হলে স্যান্ডবক্সে মেসেজ পাঠায়। এটি পৃষ্ঠায় iframe খুঁজে নিয়ে এবং এর contentWindow-এ postMessage() কল করে এই কাজ করে। মেসেজটি হল একটি অবজেক্ট যার মধ্যে তিনটি প্রপার্টি আছে: context, templateName এবং command। আমরা কিছুক্ষণের মধ্যেই context এবং command-এর ব্যাপারে আরও গভীরে যাব।

service-worker.js:

browser.action.onClicked.addListener(() => {
  browser.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 লোড হলে, এটি Handlebars লাইব্রেরি লোড করে এবং 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>

এটি ব্যর্থ হয় না! যদিও Handlebars.compile শেষ পর্যন্ত new Function ব্যবহার করে, জিনিসগুলি ঠিক প্রত্যাশা অনুযায়ী কাজ করে এবং আমরা templates['hello']-এ একটি কম্পাইল করা টেমপ্লেট পাই।

ফলাফলটি ফিরিয়ে দিন

আমরা এই টেমপ্লেটটি ব্যবহারের জন্য উপলভ্য করব, এর জন্য একটি মেসেজ লিসেনার সেট-আপ করব যা এক্সটেনশন পৃষ্ঠা থেকে কমান্ড গ্রহণ করে। কী করা উচিত তা নির্ধারণ করতে আমরা পাস করা command ব্যবহার করব (আপনি সহজ রেন্ডারিংয়ের চেয়ে আরও বেশি কিছু করার কথা ভাবতে পারেন; সম্ভবত টেমপ্লেট তৈরি করা? Perhaps managing them in some way?), and the context will be passed into the template directly for rendering. রেন্ডার করা HTML এক্সটেনশন পৃষ্ঠায় ফেরত পাঠানো হবে যাতে এক্সটেনশন পরে এটি দিয়ে কোনও প্রয়োজনীয় কাজ করতে পারে:

 <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 ডেটা নিয়ে আকর্ষণীয় কিছু করব। এই ক্ষেত্রে, আমরা শুধু বিজ্ঞপ্তির মাধ্যমে এটি দেখাব, তবে এক্সটেনশনের UI-এর অংশ হিসেবে এই HTML নিরাপদে ব্যবহার করা সম্পূর্ণভাবে সম্ভব। innerHTML-এর মাধ্যমে এটি যোগ করলে নিরাপত্তার ক্ষেত্রে উল্লেখযোগ্য ঝুঁকি তৈরি হয় না কারণ স্যান্ডবক্সের মধ্যে রেন্ডার করা কন্টেন্ট আমরা বিশ্বাস করি।

এই মেকানিজম টেমপ্লেটিংকে সহজ করে তোলে, তবে এটি অবশ্যই টেমপ্লেটিংয়ের মধ্যে সীমাবদ্ধ নয়। কন্টেন্ট নিরাপত্তা সংক্রান্ত কঠোর নীতির অধীনে যে কোড বক্সের বাইরে কাজ করে না, সেটি স্যান্ডবক্স করা যেতে পারে; আসলে, আপনার এক্সটেনশনের যেসব কম্পোনেন্ট সঠিকভাবে কাজ করবে, সেগুলি স্যান্ডবক্স করা প্রায়শই উপযোগী হয়, যাতে আপনার প্রোগ্রামের প্রতিটি অংশকে সঠিকভাবে কার্যকর করার জন্য প্রয়োজনীয় ন্যূনতম সেট অফ প্রিভিলেজে সীমাবদ্ধ করা যায়। Google I/O 2012-এর নিরাপদ ওয়েব অ্যাপ ও Chrome এক্সটেনশন লেখা প্রেজেন্টেশন থেকে এই টেকনিকের কিছু ভালো উদাহরণ পাওয়া যায় এবং এটি দেখার জন্য ৫৬ মিনিট সময় ব্যয় করা সার্থক হবে।