স্যান্ডবক্সড আইফ্রেমে eval() ব্যবহার করুন

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

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

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

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

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

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

একটি স্যান্ডবক্স তৈরি করুন এবং ব্যবহার করুন

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

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

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

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

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

স্যান্ডবক্স করা ফাইলটি নিয়ে আকর্ষণীয় কিছু করার জন্য, আমাদের এটিকে এমন একটি প্রেক্ষাপটে লোড করতে হবে যেখান থেকে এক্সটেনশনের কোড এটিকে অ্যাক্সেস করতে পারে। এখানে, sandbox.html ফাইলটিকে একটি iframe ব্যবহার করে একটি এক্সটেনশন পেজে লোড করা হয়েছে। পেজটির জাভাস্ক্রিপ্ট ফাইলে এমন কোড রয়েছে যা ব্রাউজার অ্যাকশনে ক্লিক করা হলেই স্যান্ডবক্সে একটি মেসেজ পাঠায়। এটি পেজে থাকা 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!');
});

এক্সটেনশন-পেজ.জেএস:

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-এর পরামর্শ অনুযায়ী একটি ইনলাইন টেমপ্লেট তৈরি ও কম্পাইল করে:

এক্সটেনশন-পৃষ্ঠা.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>

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

এই পদ্ধতিটি টেমপ্লেটিংকে সহজ করে তোলে, কিন্তু এটি অবশ্যই শুধু টেমপ্লেটিংয়ের মধ্যেই সীমাবদ্ধ নয়। যেকোনো কোড যা একটি কঠোর কন্টেন্ট সিকিউরিটি পলিসির অধীনে সরাসরি কাজ করে না, তাকে স্যান্ডবক্স করা যেতে পারে; প্রকৃতপক্ষে, আপনার এক্সটেনশনের সেইসব উপাদানকে স্যান্ডবক্স করা প্রায়শই উপকারী হয় যেগুলো সঠিকভাবে চলতে পারত , যাতে আপনার প্রোগ্রামের প্রতিটি অংশকে যথাযথভাবে কার্যকর হওয়ার জন্য প্রয়োজনীয় সর্বনিম্ন সুযোগ-সুবিধার মধ্যে সীমাবদ্ধ রাখা যায়। গুগল আই/ও ২০১২-এর ‘রাইটিং সিকিওর ওয়েব অ্যাপস অ্যান্ড ক্রোম এক্সটেনশনস’ প্রেজেন্টেশনটিতে এই কৌশলগুলোর কিছু ভালো বাস্তব উদাহরণ দেওয়া আছে এবং এটি আপনার ৫৬ মিনিট সময় দেওয়ার যোগ্য।