{"title":"JShelter","link":[{"@attributes":{"href":"https:\/\/jshelter.org\/","rel":"alternate"}},{"@attributes":{"href":"https:\/\/jshelter.org\/feeds\/all.atom.xml","rel":"self"}}],"id":"https:\/\/jshelter.org\/","updated":"2026-01-12T05:03:03+01:00","entry":[{"title":"First step towards MV3 date: 2024-04-19 17:00","link":{"@attributes":{"href":"https:\/\/jshelter.org\/pt\/first-mv3-step\/","rel":"alternate"}},"published":"2025-07-10T07:01:03+02:00","updated":"2025-07-10T07:01:03+02:00","author":{"name":"The JShelter team"},"id":"tag:jshelter.org,2025-07-10:\/pt\/first-mv3-step\/","summary":"<p>We have been working on migration to Manifest v3 (MV3) for some time and today we\nare shipping a JShelter version 0.18 that implements stateless replacement for\nbackground pages which is a first step towards MV3.<\/p>\n<p>MV2 extensions were allowed to create background pages. These pages allow running\nJavaScript \u2026<\/p>","content":"<p>We have been working on migration to Manifest v3 (MV3) for some time and today we\nare shipping a JShelter version 0.18 that implements stateless replacement for\nbackground pages which is a first step towards MV3.<\/p>\n<p>MV2 extensions were allowed to create background pages. These pages allow running\nJavaScript code and keep state like variables for the whole browser session.\nEssentially, all background pages started with the browser and lasted until the\nuser closed the browser. Hence, we could utilize background pages to safe all\ninformation needed to be kept in memory. For instance, JShelter needs to store:<\/p>\n<ul>\n<li>hashes used as a seed for JavaScript shield anti-fingerprinting protection,<\/li>\n<li>number of API calls needed for Fingerprint detector, which the user can see in\nthe pop up and fingerprinting report,<\/li>\n<li>information needed to keep pop up icon dynamic,<\/li>\n<li>etc.<\/li>\n<\/ul>\n<p>We needed to solve issues related to the migration of all these information from\nregular JavaScript variables to Web Storage. As we expect that other extensions\nneed to solve the same problem, we created a <a href=\"https:\/\/github.com\/hackademix\/nscl\/tree\/stateless\">stateless NSCL\nbranch<\/a>. Among others, we\nneeded to solve the issue of writing to the storage too frequently. As the core\nof JShelter is heavily stateful, we needed to rewrite important parts that were\nin the code base for years and were proven to work.<\/p>\n<p>JShelter has repeatable tests and we run additional testing, especially under\ncircumstances like this change. Everything should run the same as it used to\nwork in 0.17. However, please be cautious and report back any odd behavior that\nyou encounter with 0.18 and later versions.<\/p>\n<p>Migration to stateless or non-persistent background pages is needed for MV3 but\nit is still not a final step. Expect other major changes in JShelter core code\nincluding removal of Network Boundary Shield for Chromium-based browsers soon.\nHence, keep cautious also in the following months and report back any issues\nthat you encounter. Have a look at our <a href=\"\/versions\/\">Release page<\/a> for more\ninformation about the changes in JShelter.<\/p>","category":{"@attributes":{"term":"pt"}}},{"title":"\"Fixing\" Manifest V3 date: 2024-02-07 12:00","link":{"@attributes":{"href":"https:\/\/jshelter.org\/pt\/fixing-mv3\/","rel":"alternate"}},"published":"2025-07-10T07:01:03+02:00","updated":"2025-07-10T07:01:03+02:00","author":{"name":"The JShelter team"},"id":"tag:jshelter.org,2025-07-10:\/pt\/fixing-mv3\/","summary":"<p>In the previous post of this series, <a href=\"\/mv3\/\">What is Manifest v3 and how\nit affects\nJShelter<\/a>, we've stressed the limitations of the new WebExtensions APIs,\nalong with the challenges and the threats it poses to privacy and security\nextensions such as JShelter.<\/p>\n<p>As part of our strategy to mitigate these \u2026<\/p>","content":"<p>In the previous post of this series, <a href=\"\/mv3\/\">What is Manifest v3 and how\nit affects\nJShelter<\/a>, we've stressed the limitations of the new WebExtensions APIs,\nalong with the challenges and the threats it poses to privacy and security\nextensions such as JShelter.<\/p>\n<p>As part of our strategy to mitigate these problems, we mentioned \"actively\nparticipating in the ongoing \/browser extensions API design work\/ of the <a href=\"https:\/\/www.w3.org\/groups\/cg\/webextensions\">Web\nExtensions Community Group<\/a> (WECG),\nto steer the MV3 specification in the most favorable direction for security and\nprivacy use cases\".<\/p>\n<p>Here, we want to provide some updates about these design participation\nactivities, and pointers to follow their progress.<\/p>\n<p>Specifically, we prompted the W3C's WECG to resume the discussion of the two main\nissues \"blocking\" JShelter, which we had originally opened more than 2 years ago:<\/p>\n<ol>\n<li><a href=\"https:\/\/github.com\/w3c\/webextensions\/issues\/103\">Scripting API minimum\nrequirements<\/a> to enable the\nreliable and pervasive script injection needed to enforce JShelter's JavaScript\nShield wrappers<\/li>\n<li><a href=\"https:\/\/github.com\/w3c\/webextensions\/issues\/402\">LAN-aware Declarative Net Request\nfilters<\/a>, required for the\nNetwork Boundary Shield to operate in Chromium-derived browsers and Safari on MV3<\/li>\n<\/ol>\n<p>See <a href=\"https:\/\/docs.google.com\/document\/d\/1QkwhEMtMS67JBUkl_WVPZ4lRSKoWcQNlLJSf_GwSXg8\/\">public notes of the meetings of the\nWECG<\/a>\nfor more details on the working of the group.<\/p>\n<p>This time, the response has been unanimously positive on #1, and generally\npositive on #2, with Google expressing a neutral position motivated by Chromium\ndevelopers unsure if the Network Boundary Shield use case would be better served\nby a built-in browser UI around their (not ready for prime time yet and planned\nfor quite a long time now) Private Network Access.<\/p>\n<p>The discussion continues on the specific follow-up proposals we've created\nafterwards, covering all our JavaScript Shield requirements:<\/p>\n<ol>\n<li>Proposal: <a href=\"https:\/\/github.com\/w3c\/webextensions\/issues\/536\">RegisteredContentScript.func and RegisteredContentScript.args\n(similar to ScriptInjection)<\/a><\/li>\n<li>Proposal: <a href=\"https:\/\/github.com\/w3c\/webextensions\/issues\/538\">RegisteredContentScript.workers property to inject\nWorkerScope(s)<\/a><\/li>\n<li>Proposal: <a href=\"https:\/\/github.com\/w3c\/webextensions\/issues\/539\">Proposal: RegisteredContentScript.tabIds and\nRegisteredContentScript.excludeTabIds properties to filter\ninjection<\/a><\/li>\n<\/ol>\n<p>Furthermore <a href=\"https:\/\/bugzilla.mozilla.org\/show_bug.cgi?id=1736575\">MAIN world support in\nFirefox<\/a> would greatly\nsimplify our cross-browser story, allowing us to drop a complex and fragile\ncompatibility layer working around Firefox's XRay vision approach to \"safe\"\nDOM \/ JavaScript environment manipulation that we currently employ in Jshelter\nfor Firefox.<\/p>\n<p>Browser vendors now signalling adequate understanding of our requirements and\ntheir will to implement our API proposals or equivalent alternatives before MV2\nsunset can finally induce some cautious optimism about a reasonably better MV3\nfor privacy and security extensions.<\/p>","category":{"@attributes":{"term":"pt"}}},{"title":"JShelter Debuts as a Manifest V3 Extension","link":{"@attributes":{"href":"https:\/\/jshelter.org\/mv3-jshelter-debut\/","rel":"alternate"}},"published":"2024-07-27T19:00:00+02:00","updated":"2024-07-27T16:24:02+02:00","author":{"name":"The JShelter team"},"id":"tag:jshelter.org,2024-07-27:\/mv3-jshelter-debut\/","summary":"<p>Today, we're happy to announce the release of JShelter 0.19: in spite of the odd decimal version number,\nthis is the most important milestone in our <a href=\"\/mv3\/\">migration journey to Manifest v3 (MV3)<\/a>.<\/p>\n<p>In fact, the JShelter 0.19 package we're shipping to the Chrome Store is the first one \u2026<\/p>","content":"<p>Today, we're happy to announce the release of JShelter 0.19: in spite of the odd decimal version number,\nthis is the most important milestone in our <a href=\"\/mv3\/\">migration journey to Manifest v3 (MV3)<\/a>.<\/p>\n<p>In fact, the JShelter 0.19 package we're shipping to the Chrome Store is the first one officially marked\nas MV3-compatible, and it is built from a dedicated\n<a href=\"https:\/\/pagure.io\/JShelter\/webextension\/commits\/mv3\">mv3 source code branch<\/a> implementing several changes\nto work around the severe limitations imposed by Google's Manifest V3 and affecting all the extensions\ninstalled in recent Chromium-based browsers.<\/p>\n<p>Although we're very proud of this release, which prevents JShelter from getting disabled by Chrome,\nwe consider it more a start than a finish line. As anticipated in <a href=\"\/mv3\/\">previous installments of this blog series<\/a>,\nwe had to drop some features (more exactly one and a half) from the Chromium version due to currently insurmountable\ntechnical constraints. We still hope to overcome these constraints in the future, if and when <a href=\"https:\/\/jshelter.org\/fixing-mv3\/\">our advocacy efforts<\/a>\non behalf of privacy and security extensions developers eventually find a more receptive audience, especially\nfrom Google.<\/p>\n<p>Firefox, on the other hand, should be fine for now, thanks to Mozilla's sensible choice of keeping around, even in\nthis brave new MV3 world, their powerful\n<a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Mozilla\/Add-ons\/WebExtensions\/API\/webRequest\">asynchronous blocking webRequest API<\/a>, which is crucial to implement\nany non-trivial content blocking browser extension, and to their more friendly attitude towards privacy and security extensions use cases.<\/p>\n<h3 id=\"what-does-work\"><a class=\"toclink\" href=\"#what-does-work\">What does work<\/a><\/h3>\n<p><strong>On Firefox<\/strong>, JShelter 0.19 should be virtually indistinguishable from 0.18.1. In fact, it should work even better,\nbecause we fixed a bug due to the new stateless architecture which sometimes prevented the UI popup from being\nproperly rendered after long inactivity.<\/p>\n<p><strong>On Chrome<\/strong>, the most notable and annoying user-facing change, which JShelter will notify you about as soon as you try to use it,\nis that <a href=\"https:\/\/developer.chrome.com\/docs\/extensions\/reference\/api\/userScripts#developer_mode_for_extension_users\">enabling the Extensions Developer Mode<\/a>\nis required for extensions (e.g., GreaseMonkey) to use the <code>userScripts<\/code> API, which JShelter must leverage to build and inject JShelter's anti-fingerprinting wrappers.\nOnce the Developer Mode is enabled, almost everything should work as expected, except for one feature and a half, i.e., the\n<a href=\"\/nbs\/\">Network Boundary Shield<\/a> and\nthe blocking mode of the <a href=\"\/fpd\/\">Fingerprinting Detector<\/a> (more details below).<\/p>\n<p>However, since the code changes both in JShelter itself and in the NoScript Commons Library have been many and pretty\ndramatic, we certainly expect bugs. <a href=\"https:\/\/pagure.io\/JShelter\/webextension\/issues\">Please report your issues here<\/a> as usual. Thanks!<\/p>\n<h3 id=\"what-does-not-work-yet\"><a class=\"toclink\" href=\"#what-does-not-work-yet\">What does not work yet<\/a><\/h3>\n<p>As mentioned above, we had to remove one feature and a half <strong>from the Chrome version only<\/strong>:<\/p>\n<ul>\n<li>The <a href=\"\/nbs\/\">Network Boundary Shield<\/a><\/li>\n<li>The <a href=\"\/fpd\/\">Fingerprinting Detector<\/a>'s blocking mode<\/li>\n<\/ul>\n<p>Both of these features rely on the\n<a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Mozilla\/Add-ons\/WebExtensions\/API\/webRequest\">blocking webRequest API<\/a>,\nwhich in MV3 is provided only by Firefox.<\/p>\n<p>The Network Boundary Shield also requires either a reliable DNS API or at least <a href=\"https:\/\/github.com\/w3c\/webextensions\/issues\/402\">some form of LAN awareness<\/a>.<\/p>\n<h3 id=\"what-were-still-working-on\"><a class=\"toclink\" href=\"#what-were-still-working-on\">What we're still working on<\/a><\/h3>\n<p>As we said, JShelter on Firefox is in pretty good shape, but we will keep fixing bugs and evolving anti-fingerprinting techniques\naligned with academic research. We also plan to gradually reduce the code path divergences introduced by the MV3 Chromium version,\nin order to enhance the maintainability and predictability of our code.<\/p>\n<p>We also still hope to overcome in the future the main disadvantages that Chromium MV3 JShelter\nsuffers over its Firefox counterpart,\nbut for this plan to work we really need more cooperation from the\n<a href=\"https:\/\/www.w3.org\/groups\/cg\/webextensions\">Web Extensions Community Group<\/a> (WECG)\n(and especially goodwill from Google as the dominant browser vendor represented there)\nat <a href=\"\/fixing-mv3\/\">fixing MV3<\/a>:<\/p>\n<ol>\n<li><strong>Removing the <a href=\"https:\/\/developer.chrome.com\/docs\/extensions\/reference\/api\/userScripts#developer_mode_for_extension_users\">developer mode requirement<\/a><\/strong>, e.g., by introducing more <a href=\"https:\/\/github.com\/w3c\/webextensions\/issues\/103\">reliable and flexible\n  script injection APIs like the ones we proposed here<\/a>.<\/li>\n<li><strong><a href=\"\/nbs\/\">Implementing an MV3 version of the removed Network Boundary Shield<\/a><\/strong>, which requires\n  <a href=\"https:\/\/github.com\/w3c\/webextensions\/issues\/402\">some kind of LAN awareness<\/a> from the available content\n  blocking APIs.<\/li>\n<li><strong><a href=\"https:\/\/github.com\/w3c\/webextensions\/issues\/110\">Reintroducing Fingerprinting Detector Blocking Mode<\/a><\/strong>, which had to go away because\n  complex algorithmic use cases like ours are <a href=\"https:\/\/github.com\/w3c\/webextensions\/issues\/110\">not covered by the declarativeNetRequest API<\/a> (MV3's\n  inadequate replacement for <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Mozilla\/Add-ons\/WebExtensions\/API\/webRequest\">blocking webRequest API<\/a>).<\/li>\n<\/ol>\n<p>The road ahead is long, but we've also accomplished a lot so far, and we want\nto express our gratitude to you, JShelter users, for your help. Please keep reporting any issues that you encounter\nand take a look at our <a href=\"\/versions\/\">Release page<\/a> for more information about\nongoing changes.<\/p>","category":{"@attributes":{"term":"posts"}}},{"title":"Translating the JShelter website","link":{"@attributes":{"href":"https:\/\/jshelter.org\/i18n_website\/","rel":"alternate"}},"published":"2024-07-08T15:00:00+02:00","updated":"2025-07-10T07:01:03+02:00","author":{"name":"The JShelter team"},"id":"tag:jshelter.org,2024-07-08:\/i18n_website\/","summary":"<p>The JShelter website has a dedicated translation pipeline, described in this post.<\/p>\n<h2 id=\"translating-the-website-content\"><a class=\"toclink\" href=\"#translating-the-website-content\">Translating the website content<\/a><\/h2>\n<p>The simplest way to contribute to the site translation is on Weblate. Just head\nover to the <a href=\"https:\/\/hosted.weblate.org\/projects\/jshelter\/website\/\">JShelter website\nsection<\/a>, select the\nlanguage you want to work on, and start translating. Project maintainers will \u2026<\/p>","content":"<p>The JShelter website has a dedicated translation pipeline, described in this post.<\/p>\n<h2 id=\"translating-the-website-content\"><a class=\"toclink\" href=\"#translating-the-website-content\">Translating the website content<\/a><\/h2>\n<p>The simplest way to contribute to the site translation is on Weblate. Just head\nover to the <a href=\"https:\/\/hosted.weblate.org\/projects\/jshelter\/website\/\">JShelter website\nsection<\/a>, select the\nlanguage you want to work on, and start translating. Project maintainers will\nreview and integrate new translations periodically.<\/p>\n<h3 id=\"translating-locally-without-weblate\"><a class=\"toclink\" href=\"#translating-locally-without-weblate\">Translating locally without Weblate<\/a><\/h3>\n<p>If you're comfortable with editing PO files, that's also possible. For this,\n   just do the following steps:<\/p>\n<ol>\n<li>Work on the <strong>weblate<\/strong> branch with <code>git checkout weblate<\/code><\/li>\n<li>Edit the relevant files inside the <code>website\/i18n\/<\/code> directory, which has\n   subdirectories for each language<\/li>\n<li>Commit and push your changes back to the repository once you're done, or\n   file a pull request<\/li>\n<\/ol>\n<p>In case you run into any problem, feel free to create an issue in our issue\ntracker and we'll do our best to guide you through the process.<\/p>\n<h2 id=\"updating-the-translation-source-files\"><a class=\"toclink\" href=\"#updating-the-translation-source-files\">Updating the translation source files<\/a><\/h2>\n<h3 id=\"1-updating-the-weblate-string-list\"><a class=\"toclink\" href=\"#1-updating-the-weblate-string-list\">1. Updating the Weblate string list<\/a><\/h3>\n<p>When there are updates to the site content, Weblate needs to be manually\nupdated to provide the new strings for translation.<\/p>\n<p>First, run the command <code>make translate-extract<\/code>, which will generate PO files\ninside the <code>i18n\/en\/<\/code> directory. This process uses the <code>md2po<\/code> command from the\n<a href=\"https:\/\/mondeja.github.io\/mdpo\/latest\/\">mdpo<\/a> Markdown parsing package to read\nall the website strings and generate source PO translation files.<\/p>\n<p>The resulting files can be uploaded to Weblate to update the string list. Right\nnow this upload needs to be done manually on a per-component basis, but the\nfollowing links will take you directly to each component's page so you can\nupload the appropriate PO file:\n<a href=\"https:\/\/hosted.weblate.org\/projects\/jshelter\/website\/en\/#upload\">Website<\/a>\n(pages.po), <a href=\"https:\/\/hosted.weblate.org\/projects\/jshelter\/website-posts\/en\/#upload\">Blog\nPosts<\/a>\n(posts.po) or\n<a href=\"https:\/\/hosted.weblate.org\/projects\/jshelter\/website-wrappers\/en\/#upload\">Wrappers<\/a>\n(wrappers.po).<\/p>\n<h3 id=\"2-download-the-latest-translations-and-apply-them\"><a class=\"toclink\" href=\"#2-download-the-latest-translations-and-apply-them\">2. Download the latest translations and apply them<\/a><\/h3>\n<p>The command <code>make translate<\/code> will download the latest version of each available\ntranslation. The resulting <code>.po<\/code> files are placed inside the\n<code>website\/i18n\/&lt;lang&gt;\/<\/code> directory, where <code>&lt;lang&gt;<\/code> is the language 2-letter code.\nIt will also generate translated Markdown files from the <code>.po<\/code> translation\nsources obtained in the previous step. <\/p>\n<p>The resulting files can be found inside the <code>website\/content\/<\/code> directory: it\ncreates a directory with the language code inside each section dir\n(<code>website\/content\/pages<\/code>, <code>website\/content\/posts<\/code> and\n <code>website\/content\/wrappers<\/code>).<\/p>\n<p>If everything looks okay, you can commit the new translated files so that\nthey're integrated into the website.<\/p>\n<h2 id=\"adding-a-new-language\"><a class=\"toclink\" href=\"#adding-a-new-language\">Adding a new language<\/a><\/h2>\n<p>There are many languages available on Weblate, but only some of them are\nintegrated in the website. This is for quality control reasons, to ensure\nthat a translation is stable enough before pushing it into the live site.<\/p>\n<p>At the moment, only Portuguese (<code>pt_PT<\/code>) is included.<\/p>\n<p>To add a new language, first edit <code>website\/pelicanconf.py<\/code> and find the lines:<\/p>\n<div class=\"highlight\"><pre><span><\/span><code><span class=\"n\">I18N_SUBSITES<\/span> <span class=\"o\">=<\/span> <span class=\"p\">{<\/span>\n    <span class=\"s2\">&quot;pt&quot;<\/span><span class=\"p\">:<\/span> <span class=\"p\">{<\/span>\n        <span class=\"s2\">&quot;DESCRIPTION&quot;<\/span><span class=\"p\">:<\/span> <span class=\"s2\">&quot;A extens\u00e3o para navegar em seguran\u00e7a&quot;<\/span><span class=\"p\">,<\/span>\n        <span class=\"s2\">&quot;LONGDESCRIPTION&quot;<\/span><span class=\"p\">:<\/span> <span class=\"s2\">&quot;Uma extens\u00e3o anti-malware para o teu navegador web que vai p\u00f4r sob controlo amea\u00e7as de JavaScript, incluindo a recolha de impress\u00f5es digitais, rastreamento e recolha de dados&quot;<\/span><span class=\"p\">,<\/span>\n    <span class=\"p\">}<\/span>\n<span class=\"p\">}<\/span>\n<\/code><\/pre><\/div>\n\n<p>Add your new language here, here using Czech as an example:<\/p>\n<div class=\"highlight\"><pre><span><\/span><code><span class=\"n\">I18N_SUBSITES<\/span> <span class=\"o\">=<\/span> <span class=\"p\">{<\/span>\n    <span class=\"s2\">&quot;pt&quot;<\/span><span class=\"p\">:<\/span> <span class=\"p\">{<\/span>\n        <span class=\"s2\">&quot;DESCRIPTION&quot;<\/span><span class=\"p\">:<\/span> <span class=\"s2\">&quot;A extens\u00e3o para navegar em seguran\u00e7a&quot;<\/span><span class=\"p\">,<\/span>\n        <span class=\"s2\">&quot;LONGDESCRIPTION&quot;<\/span><span class=\"p\">:<\/span> <span class=\"s2\">&quot;Uma extens\u00e3o anti-malware para o teu navegador web que vai p\u00f4r sob controlo amea\u00e7as de JavaScript, incluindo a recolha de impress\u00f5es digitais, rastreamento e recolha de dados&quot;<\/span><span class=\"p\">,<\/span>\n    <span class=\"p\">},<\/span>\n    <span class=\"s2\">&quot;cz&quot;<\/span><span class=\"p\">:<\/span> <span class=\"p\">{<\/span>\n        <span class=\"s2\">&quot;DESCRIPTION&quot;<\/span><span class=\"p\">:<\/span> <span class=\"s2\">&quot;A extens\u00e3o para navegar em seguran\u00e7a&quot;<\/span><span class=\"p\">,<\/span>\n        <span class=\"s2\">&quot;LONGDESCRIPTION&quot;<\/span><span class=\"p\">:<\/span> <span class=\"s2\">&quot;Uma extens\u00e3o anti-malware para o teu navegador web que vai p\u00f4r sob controlo amea\u00e7as de JavaScript, incluindo a recolha de impress\u00f5es digitais, rastreamento e recolha de dados&quot;<\/span><span class=\"p\">,<\/span>\n    <span class=\"p\">}<\/span>\n<span class=\"p\">}<\/span>\n<\/code><\/pre><\/div>\n\n<p>Now, when you build the website with <code>make html<\/code>, the new language should be present\nin the generated website.<\/p>","category":{"@attributes":{"term":"posts"}}},{"title":"Translating the JShelter website","link":{"@attributes":{"href":"https:\/\/jshelter.org\/pt\/i18n_website\/","rel":"alternate"}},"published":"2024-07-08T15:00:00+02:00","updated":"2025-07-10T07:01:03+02:00","author":{"name":"The JShelter team"},"id":"tag:jshelter.org,2024-07-08:\/pt\/i18n_website\/","summary":"<p>The JShelter website has a dedicated translation pipeline, described in this post.<\/p>\n<h2 id=\"translating-the-website-content\"><a class=\"toclink\" href=\"#translating-the-website-content\">Translating the website content<\/a><\/h2>\n<p>The simplest way to contribute to the site translation is on Weblate. Just head\nover to the <a href=\"https:\/\/hosted.weblate.org\/projects\/jshelter\/website\/\">JShelter website\nsection<\/a>, select the\nlanguage you want to work on, and start translating. Project maintainers will \u2026<\/p>","content":"<p>The JShelter website has a dedicated translation pipeline, described in this post.<\/p>\n<h2 id=\"translating-the-website-content\"><a class=\"toclink\" href=\"#translating-the-website-content\">Translating the website content<\/a><\/h2>\n<p>The simplest way to contribute to the site translation is on Weblate. Just head\nover to the <a href=\"https:\/\/hosted.weblate.org\/projects\/jshelter\/website\/\">JShelter website\nsection<\/a>, select the\nlanguage you want to work on, and start translating. Project maintainers will\nreview and integrate new translations periodically.<\/p>\n<h3 id=\"translating-locally-without-weblate\"><a class=\"toclink\" href=\"#translating-locally-without-weblate\">Translating locally without Weblate<\/a><\/h3>\n<p>If you're comfortable with editing PO files, that's also possible. For this, just\ndo the following steps:<\/p>\n<ol>\n<li>Work on the <strong>weblate<\/strong> branch with <code>git checkout weblate<\/code><\/li>\n<li>Edit the relevant files inside the <code>website\/i18n\/<\/code> directory, which has\nsubdirectories for each language<\/li>\n<li>Commit and push your changes back to the repository once you're done, or file\na pull request<\/li>\n<\/ol>\n<p>In case you run into any problem, feel free to create an issue in our issue\ntracker and we'll do our best to guide you through the process.<\/p>\n<h2 id=\"updating-the-translation-source-files\"><a class=\"toclink\" href=\"#updating-the-translation-source-files\">Updating the translation source files<\/a><\/h2>\n<h3 id=\"1-updating-the-weblate-string-list\"><a class=\"toclink\" href=\"#1-updating-the-weblate-string-list\">1. Updating the Weblate string list<\/a><\/h3>\n<p>When there are updates to the site content, Weblate needs to be manually updated\nto provide the new strings for translation.<\/p>\n<p>First, run the command <code>make translate-extract<\/code>, which will generate PO files\ninside the <code>i18n\/en\/<\/code> directory. This process uses the <code>md2po<\/code> command from the\n<a href=\"https:\/\/mondeja.github.io\/mdpo\/latest\/\">mdpo<\/a> Markdown parsing package to read\nall the website strings and generate source PO translation files.<\/p>\n<p>The resulting files can be uploaded to Weblate to update the string list. Right\nnow this upload needs to be done manually on a per-component basis, but the\nfollowing links will take you directly to each component's page so you can\nupload the appropriate PO file:\n<a href=\"https:\/\/hosted.weblate.org\/projects\/jshelter\/website\/en\/#upload\">Website<\/a>\n(pages.po), <a href=\"https:\/\/hosted.weblate.org\/projects\/jshelter\/website-posts\/en\/#upload\">Blog\nPosts<\/a>\n(posts.po) or\n<a href=\"https:\/\/hosted.weblate.org\/projects\/jshelter\/website-wrappers\/en\/#upload\">Wrappers<\/a>\n(wrappers.po).<\/p>\n<h3 id=\"2-download-the-latest-translations-and-apply-them\"><a class=\"toclink\" href=\"#2-download-the-latest-translations-and-apply-them\">2. Download the latest translations and apply them<\/a><\/h3>\n<p>The command <code>make translate<\/code> will download the latest version of each available\ntranslation. The resulting <code>.po<\/code> files are placed inside the\n<code>website\/i18n\/&lt;lang&gt;\/<\/code> directory, where <code>&lt;lang&gt;<\/code> is the language 2-letter code.\nIt will also generate translated Markdown files from the <code>.po<\/code> translation\nsources obtained in the previous step.<\/p>\n<p>The resulting files can be found inside the <code>website\/content\/<\/code> directory: it\ncreates a directory with the language code inside each section dir\n(<code>website\/content\/pages<\/code>, <code>website\/content\/posts<\/code> and\n<code>website\/content\/wrappers<\/code>).<\/p>\n<p>If everything looks okay, you can commit the new translated files so that they're\nintegrated into the website.<\/p>\n<h2 id=\"adding-a-new-language\"><a class=\"toclink\" href=\"#adding-a-new-language\">Adding a new language<\/a><\/h2>\n<p>There are many languages available on Weblate, but only some of them are\nintegrated in the website. This is for quality control reasons, to ensure that a\ntranslation is stable enough before pushing it into the live site.<\/p>\n<p>At the moment, only Portuguese (<code>pt_PT<\/code>) is included.<\/p>\n<p>To add a new language, first edit <code>website\/pelicanconf.py<\/code> and find the lines:<\/p>\n<div class=\"highlight\"><pre><span><\/span><code><span class=\"n\">I18N_SUBSITES<\/span> <span class=\"o\">=<\/span> <span class=\"p\">{<\/span>\n    <span class=\"s2\">&quot;pt&quot;<\/span><span class=\"p\">:<\/span> <span class=\"p\">{<\/span>\n        <span class=\"s2\">&quot;DESCRIPTION&quot;<\/span><span class=\"p\">:<\/span> <span class=\"s2\">&quot;A extens\u00e3o para navegar em seguran\u00e7a&quot;<\/span><span class=\"p\">,<\/span>\n        <span class=\"s2\">&quot;LONGDESCRIPTION&quot;<\/span><span class=\"p\">:<\/span> <span class=\"s2\">&quot;Uma extens\u00e3o anti-malware para o teu navegador web que vai p\u00f4r sob controlo amea\u00e7as de JavaScript, incluindo a recolha de impress\u00f5es digitais, rastreamento e recolha de dados&quot;<\/span><span class=\"p\">,<\/span>\n    <span class=\"p\">}<\/span>\n<span class=\"p\">}<\/span>\n<\/code><\/pre><\/div>\n\n<p>Add your new language here, here using Czech as an example:<\/p>\n<div class=\"highlight\"><pre><span><\/span><code><span class=\"n\">I18N_SUBSITES<\/span> <span class=\"o\">=<\/span> <span class=\"p\">{<\/span>\n    <span class=\"s2\">&quot;pt&quot;<\/span><span class=\"p\">:<\/span> <span class=\"p\">{<\/span>\n        <span class=\"s2\">&quot;DESCRIPTION&quot;<\/span><span class=\"p\">:<\/span> <span class=\"s2\">&quot;A extens\u00e3o para navegar em seguran\u00e7a&quot;<\/span><span class=\"p\">,<\/span>\n        <span class=\"s2\">&quot;LONGDESCRIPTION&quot;<\/span><span class=\"p\">:<\/span> <span class=\"s2\">&quot;Uma extens\u00e3o anti-malware para o teu navegador web que vai p\u00f4r sob controlo amea\u00e7as de JavaScript, incluindo a recolha de impress\u00f5es digitais, rastreamento e recolha de dados&quot;<\/span><span class=\"p\">,<\/span>\n    <span class=\"p\">},<\/span>\n    <span class=\"s2\">&quot;cz&quot;<\/span><span class=\"p\">:<\/span> <span class=\"p\">{<\/span>\n        <span class=\"s2\">&quot;DESCRIPTION&quot;<\/span><span class=\"p\">:<\/span> <span class=\"s2\">&quot;A extens\u00e3o para navegar em seguran\u00e7a&quot;<\/span><span class=\"p\">,<\/span>\n        <span class=\"s2\">&quot;LONGDESCRIPTION&quot;<\/span><span class=\"p\">:<\/span> <span class=\"s2\">&quot;Uma extens\u00e3o anti-malware para o teu navegador web que vai p\u00f4r sob controlo amea\u00e7as de JavaScript, incluindo a recolha de impress\u00f5es digitais, rastreamento e recolha de dados&quot;<\/span><span class=\"p\">,<\/span>\n    <span class=\"p\">}<\/span>\n<span class=\"p\">}<\/span>\n<\/code><\/pre><\/div>\n\n<p>Now, when you build the website with <code>make html<\/code>, the new language should be\npresent in the generated website.<\/p>","category":{"@attributes":{"term":"pt"}}},{"title":"First step towards MV3","link":{"@attributes":{"href":"https:\/\/jshelter.org\/first-mv3-step\/","rel":"alternate"}},"published":"2024-04-19T17:00:00+02:00","updated":"2025-07-10T07:01:03+02:00","author":{"name":"The JShelter team"},"id":"tag:jshelter.org,2024-04-19:\/first-mv3-step\/","summary":"<p>We have been working on migration to Manifest v3 (MV3) for some time and today we are shipping a\nJShelter version 0.18 that implements stateless replacement for background pages which is a first step towards MV3.<\/p>\n<p>MV2 extensions were allowed to create background pages. These pages allow running JavaScript \u2026<\/p>","content":"<p>We have been working on migration to Manifest v3 (MV3) for some time and today we are shipping a\nJShelter version 0.18 that implements stateless replacement for background pages which is a first step towards MV3.<\/p>\n<p>MV2 extensions were allowed to create background pages. These pages allow running JavaScript code\nand keep state like variables for the whole browser session. Essentially, all background pages\nstarted with the browser and lasted until the user closed the browser. Hence, we could utilize\nbackground pages to safe all information needed to be kept in memory. For instance, JShelter needs\nto store:<\/p>\n<ul>\n<li>hashes used as a seed for JavaScript shield anti-fingerprinting protection,<\/li>\n<li>number of API calls needed for Fingerprint detector, which the user can see in the pop up and\n  fingerprinting report,<\/li>\n<li>information needed to keep pop up icon dynamic,<\/li>\n<li>etc.<\/li>\n<\/ul>\n<p>We needed to solve issues related to the migration of all these information from regular JavaScript\nvariables to Web Storage. As we expect that other extensions need to solve the same problem, we created\na <a href=\"https:\/\/github.com\/hackademix\/nscl\/tree\/stateless\">stateless NSCL branch<\/a>. Among others, we\nneeded to solve the issue of writing to the storage too frequently. As the core of JShelter is\nheavily stateful, we needed to rewrite important parts that were in the code base for years and were\nproven to work.<\/p>\n<p>JShelter has repeatable tests and we run additional testing, especially under circumstances like\nthis change. Everything should run the same as it used to work in 0.17. However, please be cautious\nand report back any odd behavior that you encounter with 0.18 and later versions.<\/p>\n<p>Migration to stateless or non-persistent background pages is needed for MV3 but it is still not a\nfinal step. Expect other major changes in JShelter core code including removal of Network Boundary\nShield for Chromium-based browsers soon. Hence, keep cautious also in the following months and\nreport back any issues that you encounter. Have a look at our <a href=\"\/versions\/\">Release page<\/a> for more\ninformation about the changes in JShelter.<\/p>","category":{"@attributes":{"term":"posts"}}},{"title":"\"Fixing\" Manifest V3","link":{"@attributes":{"href":"https:\/\/jshelter.org\/fixing-mv3\/","rel":"alternate"}},"published":"2024-02-07T12:00:00+01:00","updated":"2025-07-10T07:01:03+02:00","author":{"name":"The JShelter team"},"id":"tag:jshelter.org,2024-02-07:\/fixing-mv3\/","summary":"<p>In the previous post of this series, <a href=\"\/mv3\/\">What is Manifest v3 and how\nit affects JShelter<\/a>, we've stressed the\nlimitations of the new WebExtensions APIs, along with the challenges and\nthe threats it poses to privacy and security extensions such as JShelter.<\/p>\n<p>As part of our strategy to mitigate these \u2026<\/p>","content":"<p>In the previous post of this series, <a href=\"\/mv3\/\">What is Manifest v3 and how\nit affects JShelter<\/a>, we've stressed the\nlimitations of the new WebExtensions APIs, along with the challenges and\nthe threats it poses to privacy and security extensions such as JShelter.<\/p>\n<p>As part of our strategy to mitigate these problems, we mentioned\n\"actively participating in the ongoing \/browser extensions API design\nwork\/ of the <a href=\"https:\/\/www.w3.org\/groups\/cg\/webextensions\">Web Extensions Community Group<\/a> (WECG), to steer the MV3\nspecification in the most favorable direction for security and privacy\nuse cases\".<\/p>\n<p>Here, we want to provide some updates about these design participation\nactivities, and pointers to follow their progress.<\/p>\n<p>Specifically, we prompted the W3C's WECG to resume the discussion of the\ntwo main issues \"blocking\" JShelter, which we had originally opened more\nthan 2 years ago:<\/p>\n<ol>\n<li><a href=\"https:\/\/github.com\/w3c\/webextensions\/issues\/103\">Scripting API minimum requirements<\/a> to enable the\n    reliable and pervasive script injection needed to enforce JShelter's\n    JavaScript Shield wrappers<\/li>\n<li><a href=\"https:\/\/github.com\/w3c\/webextensions\/issues\/402\">LAN-aware Declarative Net Request filters<\/a>, required for the\n    Network Boundary Shield to operate in Chromium-derived browsers and\n    Safari on MV3<\/li>\n<\/ol>\n<p>See <a href=\"https:\/\/docs.google.com\/document\/d\/1QkwhEMtMS67JBUkl_WVPZ4lRSKoWcQNlLJSf_GwSXg8\/\">public notes of the meetings of the WECG<\/a> for more details on the working of the group.<\/p>\n<p>This time, the response has been unanimously positive on #1, and\ngenerally positive on #2, with Google expressing a neutral position\nmotivated by Chromium developers unsure if the Network Boundary Shield\nuse case would be better served by a built-in browser UI around their\n(not ready for prime time yet and planned for quite a long time now)\nPrivate Network Access.<\/p>\n<p>The discussion continues on the specific follow-up proposals we've\ncreated afterwards, covering all our JavaScript Shield requirements:<\/p>\n<ol>\n<li>Proposal: <a href=\"https:\/\/github.com\/w3c\/webextensions\/issues\/536\">RegisteredContentScript.func and\n    RegisteredContentScript.args (similar to ScriptInjection)<\/a><\/li>\n<li>Proposal: <a href=\"https:\/\/github.com\/w3c\/webextensions\/issues\/538\">RegisteredContentScript.workers property to inject\n    WorkerScope(s)<\/a><\/li>\n<li>Proposal: <a href=\"https:\/\/github.com\/w3c\/webextensions\/issues\/539\">Proposal: RegisteredContentScript.tabIds and RegisteredContentScript.excludeTabIds properties to filter injection<\/a><\/li>\n<\/ol>\n<p>Furthermore <a href=\"https:\/\/bugzilla.mozilla.org\/show_bug.cgi?id=1736575\">MAIN world support in Firefox<\/a>\nwould greatly simplify our cross-browser story,\nallowing us to drop a complex and fragile compatibility layer working\naround Firefox's XRay vision approach to \"safe\" DOM \/ JavaScript\nenvironment manipulation that we currently employ in Jshelter for Firefox.<\/p>\n<p>Browser vendors now signalling adequate understanding of our\nrequirements and their will to implement our API proposals or equivalent\nalternatives before MV2 sunset can finally induce some cautious\noptimism about a reasonably better MV3 for privacy and security extensions.<\/p>","category":{"@attributes":{"term":"posts"}}},{"title":"What is Manifest v3 and how it affects JShelter","link":{"@attributes":{"href":"https:\/\/jshelter.org\/mv3\/","rel":"alternate"}},"published":"2023-11-01T12:00:00+01:00","updated":"2024-03-26T12:54:03+01:00","author":{"name":"The JShelter team"},"id":"tag:jshelter.org,2023-11-01:\/mv3\/","summary":"<p>Over the years, privacy and security-oriented browser extensions in the same category of JShelter (e.g. uBlock or NoScript, whose code is\npartially inherited by JShelter through the NoScript Commons Library),\nhave been forced into multiple complex refactorings and\nmodernizations, including complete rewrites such as those <a href=\"https:\/\/yoric.github.io\/post\/why-did-mozilla-remove-xul-addons\/\">required by\nthe transition \u2026<\/a><\/p>","content":"<p>Over the years, privacy and security-oriented browser extensions in the same category of JShelter (e.g. uBlock or NoScript, whose code is\npartially inherited by JShelter through the NoScript Commons Library),\nhave been forced into multiple complex refactorings and\nmodernizations, including complete rewrites such as those <a href=\"https:\/\/yoric.github.io\/post\/why-did-mozilla-remove-xul-addons\/\">required by\nthe transition from Mozilla's original flexible XUL\/XPCOM technology<\/a> to\nthe more limiting <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Mozilla\/Add-ons\/WebExtensions\">WebExtensions API<\/a>, largely copied from <a href=\"https:\/\/developer.chrome.com\/docs\/extensions\/reference\/\">Google Chrome's Extensions API<\/a>.<\/p>\n<p>During the past 2 years, the <a href=\"https:\/\/developer.chrome.com\/docs\/extensions\/mv3\/intro\/\">Manifest V3 API<\/a> (\"MV3\" from\nnow on) has been aggressively pushed by Google as the successor\nof the current semi-unified browser extensions APIs (known as Manifest\nV2 or \"MV2\").<\/p>\n<p>MV3 comes with renewed migration challenges, made worse by\nits <em>incompleteness<\/em> and apparent <a href=\"https:\/\/www.eff.org\/deeplinks\/2021\/12\/chrome-users-beware-manifest-v3-deceitful-and-threatening&gt;\">hostility against privacy and security \nuse cases<\/a>.<\/p>\n<p>Let's have a look at some threats that the MV3 specification poses to JShelter and other security and privacy-oriented extensions:<\/p>\n<ul>\n<li>The new content script injection APIs are promising on paper, thanks also to <a href=\"https:\/\/bugs.chromium.org\/p\/chromium\/issues\/detail?id=1128112\">specific<\/a> <a href=\"https:\/\/bugs.chromium.org\/p\/chromium\/issues\/detail?id=1180659\">requests<\/a> for <a href=\"https:\/\/bugs.chromium.org\/p\/chromium\/issues\/detail?id=1180659#c5\">enhancements<\/a> <a href=\"https:\/\/bugs.chromium.org\/p\/chromium\/issues\/detail?id=1054624#c19\">coming<\/a> from <a href=\"https:\/\/github.com\/w3c\/webextensions\/issues\/402\">us<\/a>, but their\n    implementation is still incomplete and buggy.<\/li>\n<li>The removal of the blocking capabilities of the <code>webRequest<\/code> API\n    excludes any runtime <a href=\"https:\/\/lists.nongnu.org\/archive\/html\/js-shield\/2021-02\/msg00009.html\">algorithmic flexibility<\/a> to <a href=\"https:\/\/bugs.chromium.org\/p\/chromium\/issues\/detail?id=896897#c23\">analyze and\n    manipulate<\/a> the network traffic.<\/li>\n<li>The new <code>declarativeNetRequest<\/code> API should replace the\n    <code>webRequest<\/code> API. But it is triggered by a <a href=\"https:\/\/github.com\/w3c\/webextensions\/issues\/394\">limited number of basic\n    URL-matching rules<\/a>, which are easy to bypass for malicious actors<\/li>\n<li>The forced switch of extensions' main logic from a persistent and\n    stateful background process (MV2) to an ephemeral and stateless\n    service worker (which, by MV3's design, can be killed at any time)\n    hampers the ability of security extensions to promptly counter-react\n    synchronous events such as the start of a page script execution and\n    dramatically impacts any extension of medium complexity, now forced\n    to reconstruct its state from slow asynchronous storage every time\n    its service worker gets woken up by user interaction or network events.<\/li>\n<\/ul>\n<p>These and other technical problems are making the transition extremely\npainful to privacy and security-oriented browser extensions, and more in\ngeneral those aimed to change the browser's default behavior in\nrestrictive \/ protective directions or just give back some agency to the\nusers rather than prioritize the will of web authors, advertisers, and\ntrackers.<\/p>\n<p>Further factors make any migration route even harder:<\/p>\n<ol>\n<li><em>MV2 and MV3 API access is mutually exclusive<\/em>, meaning that we\n    cannot pick \"the best tool for the task\" during the transition.\n    Therefore, web developers are forced to maintain multiple versions, i.e.\n    MV2-based for the general public and MV3-based for early\n    adopters\/testers willing to bear with bugs and missing features\n    until MV3 is good enough.<\/li>\n<li><em>MV3 is far from having any finalized shape or roadmap<\/em>, despite the\n    relentless efforts to make it more viable from extensions\n    developers, including myself, convening with browser vendors in\n    <a href=\"https:\/\/www.w3.org\/groups\/cg\/webextensions\">W3C's Web Extensions Community Group<\/a> (WECG).<\/li>\n<li>There's still <em>no stable, feature-complete and reliable MV3 API\n    implementation<\/em> for moderately complex extensions to experiment with.<\/li>\n<li>Its actual implementations suffer from <em>fragmentation and countless\n    incompatibilities<\/em>, for the better or the worse, across browser\n    vendors adopting it, including Mozilla, Microsoft and Apple.<\/li>\n<\/ol>\n<p>Notwithstanding the aforementioned critical issues, one year ago, Google\nannounced a bellicose <a href=\"https:\/\/developer.chrome.com\/docs\/extensions\/mv3\/mv2-sunset\/\">timeline to extinguish \"legacy\" MV2 extensions<\/a>, starting\nwith a \"soft\" deprecation on the 1st of January 2023 but quickly ramping\nup to enterprise-only support in June and complete extermination by the\nend of the year, except for <a href=\"https:\/\/arstechnica.com\/gadgets\/2022\/12\/chrome-delays-plan-to-limit-ad-blockers-new-timeline-coming-in-march\/\">backpedalling at the last moment<\/a>, \nputting those dates \"under review until March\" with the admission that\nan API still in such a bad shape prevents too many extensions (even\noutside the controversial realm of content blockers) from migrating.<\/p>\n<p>In our project, we're seeking to navigate the uncertainty of Manifest V3\ntransition towards a successful outcome, trying to stay compatible with\nas many browsers as possible, preserving as many features as possible,\nthrough different interventions, some sequential, some parallel:<\/p>\n<ol>\n<li>actively participating in the ongoing <em>browser extensions API design\n    work<\/em> of the <a href=\"https:\/\/www.w3.org\/groups\/cg\/webextensions\">Web Extensions Community Group<\/a>, in order to steer the\n    MV3 specification in the most favorable direction for security and\n    privacy use cases;<\/li>\n<li>publishing a <em>MV3-compatible JShelter prototype<\/em> as much feature\n    complete and cross-browser compatible as possible, and\n    developed\/distributed\/tested separately from the MV2 version aimed\n    at the general public;<\/li>\n<li>simultaneously advocating for, keeping track of and taking advantage\n    of <em>useful API changes<\/em> (even if browser-specific) to improve the\n    MV3-based prototype;<\/li>\n<li><em>sharing the results with other extension developers<\/em> through the\n    <a href=\"https:\/\/noscript.net\/commons-library\">NoScript Commons Library<\/a>; the\n    compatibility layer eases the migration work for other developers.<\/li>\n<\/ol>","category":{"@attributes":{"term":"posts"}}},{"title":"What is Manifest v3 and how it affects JShelter","link":{"@attributes":{"href":"https:\/\/jshelter.org\/pt\/mv3\/","rel":"alternate"}},"published":"2023-11-01T12:00:00+01:00","updated":"2025-07-10T07:01:03+02:00","author":{"name":"The JShelter team"},"id":"tag:jshelter.org,2023-11-01:\/pt\/mv3\/","summary":"<p>Over the years, privacy and security-oriented browser extensions in the same\ncategory of JShelter (e.g. uBlock or NoScript, whose code is partially inherited\nby JShelter through the NoScript Commons Library), have been forced into\nmultiple complex refactorings and modernizations, including complete rewrites\nsuch as those <a href=\"https:\/\/yoric.github.io\/post\/why-did-mozilla-remove-xul-addons\/\">required by\nthe transition \u2026<\/a><\/p>","content":"<p>Over the years, privacy and security-oriented browser extensions in the same\ncategory of JShelter (e.g. uBlock or NoScript, whose code is partially inherited\nby JShelter through the NoScript Commons Library), have been forced into\nmultiple complex refactorings and modernizations, including complete rewrites\nsuch as those <a href=\"https:\/\/yoric.github.io\/post\/why-did-mozilla-remove-xul-addons\/\">required by\nthe transition from Mozilla's original flexible\nXUL\/XPCOM\ntechnology<\/a> to\nthe more limiting <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Mozilla\/Add-ons\/WebExtensions\">WebExtensions\nAPI<\/a>,\nlargely copied from <a href=\"https:\/\/developer.chrome.com\/docs\/extensions\/reference\/\">Google Chrome's Extensions\nAPI<\/a>.<\/p>\n<p>During the past 2 years, the <a href=\"https:\/\/developer.chrome.com\/docs\/extensions\/mv3\/intro\/\">Manifest V3\nAPI<\/a> (\"MV3\" from now\non) has been aggressively pushed by Google as the successor of the current\nsemi-unified browser extensions APIs (known as Manifest V2 or \"MV2\").<\/p>\n<p>MV3 comes with renewed migration challenges, made worse by its <em>incompleteness<\/em>\nand apparent <a href=\"https:\/\/www.eff.org\/deeplinks\/2021\/12\/chrome-users-beware-manifest-v3-deceitful-and-threatening&gt;\">hostility against privacy and security\nuse\ncases<\/a>.<\/p>\n<p>Let's have a look at some threats that the MV3 specification poses to JShelter\nand other security and privacy-oriented extensions:<\/p>\n<ul>\n<li>The new content script injection APIs are promising on paper, thanks also to\n<a href=\"https:\/\/bugs.chromium.org\/p\/chromium\/issues\/detail?id=1128112\">specific<\/a>\n<a href=\"https:\/\/bugs.chromium.org\/p\/chromium\/issues\/detail?id=1180659\">requests<\/a> for\n<a href=\"https:\/\/bugs.chromium.org\/p\/chromium\/issues\/detail?id=1180659#c5\">enhancements<\/a>\n<a href=\"https:\/\/bugs.chromium.org\/p\/chromium\/issues\/detail?id=1054624#c19\">coming<\/a> from\n<a href=\"https:\/\/github.com\/w3c\/webextensions\/issues\/402\">us<\/a>, but their implementation\nis still incomplete and buggy.<\/li>\n<li>The removal of the blocking capabilities of the <code>webRequest<\/code> API excludes any\nruntime <a href=\"https:\/\/lists.nongnu.org\/archive\/html\/js-shield\/2021-02\/msg00009.html\">algorithmic\nflexibility<\/a>\nto <a href=\"https:\/\/bugs.chromium.org\/p\/chromium\/issues\/detail?\nid=896897#c23\">analyze and manipulate<\/a> the network traffic.<\/li>\n<li>The new <code>declarativeNetRequest<\/code> API should replace the <code>webRequest<\/code> API. But it\nis triggered by a <a href=\"https:\/\/github.com\/w3c\/webextensions\/issues\/394\">limited number of basic URL-matching\nrules<\/a>, which are easy to\nbypass for malicious actors<\/li>\n<li>The forced switch of extensions' main logic from a persistent and stateful\nbackground process (MV2) to an ephemeral and stateless service worker (which,\nby MV3's design, can be killed at any time) hampers the ability of security\nextensions to promptly counter-react synchronous events such as the start of a\npage script execution and dramatically impacts any extension of medium\ncomplexity, now forced to reconstruct its state from slow asynchronous storage\nevery time its service worker gets woken up by user interaction or network\nevents.<\/li>\n<\/ul>\n<p>These and other technical problems are making the transition extremely painful to\nprivacy and security-oriented browser extensions, and more in general those\naimed to change the browser's default behavior in restrictive \/ protective\ndirections or just give back some agency to the users rather than prioritize the\nwill of web authors, advertisers, and trackers.<\/p>\n<p>Further factors make any migration route even harder:<\/p>\n<ol>\n<li><em>MV2 and MV3 API access is mutually exclusive<\/em>, meaning that we cannot pick\n\"the best tool for the task\" during the transition. Therefore, web developers\nare forced to maintain multiple versions, i.e. MV2-based for the general public\nand MV3-based for early adopters\/testers willing to bear with bugs and missing\nfeatures until MV3 is good enough.<\/li>\n<li><em>MV3 is far from having any finalized shape or roadmap<\/em>, despite the\nrelentless efforts to make it more viable from extensions developers,\nincluding myself, convening with browser vendors in <a href=\"https:\/\/www.w3.org\/groups\/cg\/webextensions\">W3C's Web Extensions\nCommunity Group<\/a> (WECG).<\/li>\n<li>There's still <em>no stable, feature-complete and reliable MV3 API\nimplementation<\/em> for moderately complex extensions to experiment with.<\/li>\n<li>Its actual implementations suffer from <em>fragmentation and countless\nincompatibilities<\/em>, for the better or the worse, across browser vendors\nadopting it, including Mozilla, Microsoft and Apple.<\/li>\n<\/ol>\n<p>Notwithstanding the aforementioned critical issues, one year ago, Google\nannounced a bellicose <a href=\"https:\/\/developer.chrome.com\/docs\/extensions\/mv3\/mv2-sunset\/\">timeline to extinguish \"legacy\" MV2\nextensions<\/a>,\nstarting with a \"soft\" deprecation on the 1st of January 2023 but quickly\nramping up to enterprise-only support in June and complete extermination by the\nend of the year, except for <a href=\"https:\/\/arstechnica.com\/gadgets\/2022\/12\/chrome-delays-plan-to-limit-ad-blockers-new-timeline-coming-in-march\/\">backpedalling at the last\nmoment<\/a>,\nputting those dates \"under review until March\" with the admission that an API\nstill in such a bad shape prevents too many extensions (even outside the\ncontroversial realm of content blockers) from migrating.<\/p>\n<p>In our project, we're seeking to navigate the uncertainty of Manifest V3\ntransition towards a successful outcome, trying to stay compatible with as many\nbrowsers as possible, preserving as many features as possible, through different\ninterventions, some sequential, some parallel:<\/p>\n<ol>\n<li>actively participating in the ongoing <em>browser extensions API design work<\/em> of\nthe <a href=\"https:\/\/www.w3.org\/groups\/cg\/webextensions\">Web Extensions Community\nGroup<\/a>, in order to steer the MV3\nspecification in the most favorable direction for security and privacy use cases;<\/li>\n<li>publishing a <em>MV3-compatible JShelter prototype<\/em> as much feature complete and\ncross-browser compatible as possible, and developed\/distributed\/tested\nseparately from the MV2 version aimed at the general public;<\/li>\n<li>simultaneously advocating for, keeping track of and taking advantage of\n<em>useful API changes<\/em> (even if browser-specific) to improve the MV3-based\nprototype;<\/li>\n<li><em>sharing the results with other extension developers<\/em> through the <a href=\"https:\/\/noscript.net\/commons-library\">NoScript\nCommons Library<\/a>; the compatibility\nlayer eases the migration work for other developers.<\/li>\n<\/ol>","category":{"@attributes":{"term":"pt"}}},{"title":"Recent improvements in JShelter performance","link":{"@attributes":{"href":"https:\/\/jshelter.org\/optimizations\/","rel":"alternate"}},"published":"2023-09-21T15:00:00+02:00","updated":"2024-03-26T12:54:03+01:00","author":{"name":"The JShelter team"},"id":"tag:jshelter.org,2023-09-21:\/optimizations\/","summary":"<p>You might have noticed that <a href=\"\/versions\/#015\/\">recent versions<\/a> improved JShelter performance. This\nblog post explains the improvements in more detail and contains graphs. The improvements are based on the <a href=\"https:\/\/www.vut.cz\/en\/students\/final-thesis\/detail\/147218\">bachelor thesis of Martin Zmitko<\/a>. If you are interested in this topic, you will find more information in the thesis. We thank \u2026<\/p>","content":"<p>You might have noticed that <a href=\"\/versions\/#015\/\">recent versions<\/a> improved JShelter performance. This\nblog post explains the improvements in more detail and contains graphs. The improvements are based on the <a href=\"https:\/\/www.vut.cz\/en\/students\/final-thesis\/detail\/147218\">bachelor thesis of Martin Zmitko<\/a>. If you are interested in this topic, you will find more information in the thesis. We thank Martin for his work and his proposals.<\/p>\n<div class=\"toc\">\n<ul>\n<li><a href=\"#1-the-code-inserted-into-each-page\">1. The code inserted into each page<\/a><\/li>\n<li><a href=\"#2-improvements-to-little-lies\">2. Improvements to little-lies<\/a><\/li>\n<li><a href=\"#3-improvements-to-fpd\">3. Improvements to FPD<\/a><\/li>\n<li><a href=\"#4-other-optimizations\">4. Other optimizations<\/a><\/li>\n<li><a href=\"#5-results\">5. Results<\/a><\/li>\n<\/ul>\n<\/div>\n<h2 id=\"1-the-code-inserted-into-each-page\"><a class=\"toclink\" href=\"#1-the-code-inserted-into-each-page\">1. The code inserted into each page<\/a><\/h2>\n<p>Archaic versions of JShelter (at that time JavaScript Restrictor) generated the wrapping code during\neach page load (in so-called content scripts). However, we still needed to solve the\nreliable code injection at that time. We wanted the lowest amount of work in content script. So, JShelter\nstarted to generate the code in the background and send the generated code to the content script.<\/p>\n<p>Starting from <a href=\"\/versions\/#015\/\">0.5<\/a>, NSCL solved the reliable code injection. The preferred and fast\nsolution is to inject the configuration in the <code>BeforeNavigate<\/code> event handler. However, there is a race\ncondition between the <code>BeforeNavigate<\/code> event and <code>document_start<\/code> phase of the page load. If the\nscript detects that the configuration is not available during the <code>document_start<\/code>, it initiates a\nsynchronous request to retrieve the configuration before page scripts start running.<\/p>\n<p>However, Martin realized that a synchronous request takes a long time. Moreover, he confirmed <a href=\"https:\/\/pagure.io\/JShelter\/webextension\/issue\/46#comment-793783\">our\nold observations<\/a> that the\nsynchronous request is needed very often. The time needed to process the configuration increases\nlinearly with the size of the configuration. JShelter used to inject 572kB of code in the default\nconfiguration. By shifting the code generation process back to content scripts, we decreased the configuration size to 21.2kB.<\/p>\n<p>During the work, we also optimized the code-generating process and eliminated duplicates in the code\nas well as Firefox-specific code in Chromium-based browsers.<\/p>\n<h2 id=\"2-improvements-to-little-lies\"><a class=\"toclink\" href=\"#2-improvements-to-little-lies\">2. Improvements to little-lies<\/a><\/h2>\n<p>As you probably know, the <a href=\"\/farbling\/\">anti-fingerprinting code<\/a> modifies the results of some APIs with\nlittle lies. However, that approach is performance-heavy for some APIs. The most critical are APIs\nthat read from canvas (<code>readPixels<\/code> and <code>toDataURL<\/code>) and <code>AudioBuffer.getChannelData<\/code>. For example,\nthe original <code>getChannelData<\/code> passes a reference to the underlying buffer, so the browser does not\nneed to do any computation. But JShelter needs to copy each item, determine how to apply the lies\n(ensuring consistent lies to the same data) and modify selected items.<\/p>\n<p>Martin discovered that the JShelter modifications to <code>readPixels<\/code>, <code>toDataURL<\/code>, and <code>getChannelData<\/code>\ncan benefit from a different iterator. More importantly, Martin proposed to translate the code to\nWebAssembly, which runs much faster.<\/p>\n<h2 id=\"3-improvements-to-fpd\"><a class=\"toclink\" href=\"#3-improvements-to-fpd\">3. Improvements to FPD<\/a><\/h2>\n<p><a href=\"\/fpd\/\">Fingerprint detector<\/a> collects information on each call of the APIs that are often misused\nfor fingerprinting. Martin discovered that some serializations performed during its operations are\nnot really needed.<\/p>\n<h2 id=\"4-other-optimizations\"><a class=\"toclink\" href=\"#4-other-optimizations\">4. Other optimizations<\/a><\/h2>\n<p>Martin also implemented several other performance improvements, some aiming at NSCL and not only\nJShelter. For example, NSCL included a JavaScript library to compute SHA-256, while native\n<code>SubtleCrypto<\/code> implementation is faster.<\/p>\n<h2 id=\"5-results\"><a class=\"toclink\" href=\"#5-results\">5. Results<\/a><\/h2>\n<p>First, let us have a look at Firefox and the improvements in 2D Canvas in <code>getImageData()<\/code> (note\nthat the y-axis is logarithmic):<\/p>\n<p><img alt=\"Performance of getImageData in Firefox\" src=\"https:\/\/jshelter.org\/optimizations\/firefox_canvas_recommended.png\"><\/p>\n<p>As expected, the optimized implementation is slower than the original because it needs much\nmore work. Even so, the performance hit is several magnitudes lower than the hit in 0.12.2.<\/p>\n<p>Now, let us have a look the improvements in 3D Canvas in <code>readPixels()<\/code> (this time, we show the\ngraph with the linear y-axis, the shape of the points depicting the performance is similar for\n2D and 3D canvas):<\/p>\n<p><img alt=\"Performance of readPixels in Firefox\" src=\"https:\/\/jshelter.org\/optimizations\/firefox_canvas3d_recommended.png\"><\/p>\n<p>See that the original implementation quickly leaves the plotted range. Its performance hit was\nmore significant than the 2D version while starting from 0.14, the performance hit of 2D and 3D canvases are\ncomparable.<\/p>\n<p>Firefox implementation of <code>getChannelData<\/code> has a negligible running time (almost 0). The following figure shows that the little lies are computed much quicker (about two orders of magnitude), but the impact is still significant. Note that the y-axis is again logarithmic.<\/p>\n<p><img alt=\"Performance of getChannelData in Firefox\" src=\"https:\/\/jshelter.org\/optimizations\/firefox_audio_recommended.png\"><\/p>\n<p>Martin also developed <a href=\"https:\/\/pagure.io\/JShelter\/webextension\/blob\/1c86c45f565a36a6234c210392a89e3e20f32027\/f\/tests\/performance_tests\">performance\ntests<\/a>\nbased on <a href=\"https:\/\/developer.chrome.com\/docs\/lighthouse\/\">Google Lighthouse<\/a> that run in Chrome. We\ntested on 46 pages from the 100 of <a href=\"https:\/\/tranco-list.eu\/\">Tranco<\/a> and give the <a href=\"https:\/\/developer.chrome.com\/docs\/lighthouse\/performance\/performance-scoring\/\">performance\nscore<\/a> below. The\nperformance score approximates how users perceive the loading speed of the visited page. The 25th percentile of all pages should receive a\nscore of 50.<\/p>\n<p>The average performance score of all tested pages was 66 without JShelter. When tested with JShelter\n0.12.2, the score dropped to 62.5. The average of all tested pages raised to 64 with JShelter 0.15.1.\nThe performance score was the same or better in JShelter 0.15.1 compared to 0.12.2 on 33 pages. It\nincreased on 18 pages.<\/p>","category":{"@attributes":{"term":"posts"}}},{"title":"Recent improvements in JShelter performance","link":{"@attributes":{"href":"https:\/\/jshelter.org\/pt\/optimizations\/","rel":"alternate"}},"published":"2023-09-21T15:00:00+02:00","updated":"2025-07-10T07:01:03+02:00","author":{"name":"The JShelter team"},"id":"tag:jshelter.org,2023-09-21:\/pt\/optimizations\/","summary":"<p>You might have noticed that <a href=\"\/versions\/#015\/\">recent versions<\/a> improved JShelter\nperformance. This blog post explains the improvements in more detail and\ncontains graphs. The improvements are based on the <a href=\"https:\/\/www.vut.cz\/en\/students\/final-thesis\/detail\/147218\">bachelor thesis of Martin\nZmitko<\/a>. If you are\ninterested in this topic, you will find more information in the thesis. We thank \u2026<\/p>","content":"<p>You might have noticed that <a href=\"\/versions\/#015\/\">recent versions<\/a> improved JShelter\nperformance. This blog post explains the improvements in more detail and\ncontains graphs. The improvements are based on the <a href=\"https:\/\/www.vut.cz\/en\/students\/final-thesis\/detail\/147218\">bachelor thesis of Martin\nZmitko<\/a>. If you are\ninterested in this topic, you will find more information in the thesis. We thank\nMartin for his work and his proposals.<\/p>\n<div class=\"toc\">\n<ul>\n<li><a href=\"#1-the-code-inserted-into-each-page\">1. The code inserted into each page<\/a><\/li>\n<li><a href=\"#2-improvements-to-little-lies\">2. Improvements to little-lies<\/a><\/li>\n<li><a href=\"#3-improvements-to-fpd\">3. Improvements to FPD<\/a><\/li>\n<li><a href=\"#4-other-optimizations\">4. Other optimizations<\/a><\/li>\n<li><a href=\"#5-results\">5. Results<\/a><\/li>\n<\/ul>\n<\/div>\n<h2 id=\"1-the-code-inserted-into-each-page\"><a class=\"toclink\" href=\"#1-the-code-inserted-into-each-page\">1. The code inserted into each page<\/a><\/h2>\n<p>Archaic versions of JShelter (at that time JavaScript Restrictor) generated the\nwrapping code during each page load (in so-called content scripts). However, we\nstill needed to solve the reliable code injection at that time. We wanted the\nlowest amount of work in content script. So, JShelter started to generate the\ncode in the background and send the generated code to the content script.<\/p>\n<p>Starting from <a href=\"\/versions\/#015\/\">0.5<\/a>, NSCL solved the reliable code injection.\nThe preferred and fast solution is to inject the configuration in the\n<code>BeforeNavigate<\/code> event handler. However, there is a race condition between the\n<code>BeforeNavigate<\/code> event and <code>document_start<\/code> phase of the page load. If the\nscript detects that the configuration is not available during the\n<code>document_start<\/code>, it initiates a synchronous request to retrieve the\nconfiguration before page scripts start running.<\/p>\n<p>However, Martin realized that a synchronous request takes a long time. Moreover,\nhe confirmed <a href=\"https:\/\/pagure.io\/JShelter\/webextension\/issue\/46#comment-793783\">our\nold\nobservations<\/a>\nthat the synchronous request is needed very often. The time needed to process\nthe configuration increases linearly with the size of the configuration.\nJShelter used to inject 572kB of code in the default configuration. By shifting\nthe code generation process back to content scripts, we decreased the\nconfiguration size to 21.2kB.<\/p>\n<p>During the work, we also optimized the code-generating process and eliminated\nduplicates in the code as well as Firefox-specific code in Chromium-based\nbrowsers.<\/p>\n<h2 id=\"2-improvements-to-little-lies\"><a class=\"toclink\" href=\"#2-improvements-to-little-lies\">2. Improvements to little-lies<\/a><\/h2>\n<p>As you probably know, the <a href=\"\/farbling\/\">anti-fingerprinting code<\/a> modifies the\nresults of some APIs with little lies. However, that approach is\nperformance-heavy for some APIs. The most critical are APIs that read from\ncanvas (<code>readPixels<\/code> and <code>toDataURL<\/code>) and <code>AudioBuffer.getChannelData<\/code>. For\nexample, the original <code>getChannelData<\/code> passes a reference to the underlying\nbuffer, so the browser does not need to do any computation. But JShelter needs\nto copy each item, determine how to apply the lies (ensuring consistent lies to\nthe same data) and modify selected items.<\/p>\n<p>Martin discovered that the JShelter modifications to <code>readPixels<\/code>, <code>toDataURL<\/code>,\nand <code>getChannelData<\/code> can benefit from a different iterator. More importantly,\nMartin proposed to translate the code to WebAssembly, which runs much faster.<\/p>\n<h2 id=\"3-improvements-to-fpd\"><a class=\"toclink\" href=\"#3-improvements-to-fpd\">3. Improvements to FPD<\/a><\/h2>\n<p><a href=\"\/fpd\/\">Fingerprint detector<\/a> collects information on each call of the APIs that\nare often misused for fingerprinting. Martin discovered that some serializations\nperformed during its operations are not really needed.<\/p>\n<h2 id=\"4-other-optimizations\"><a class=\"toclink\" href=\"#4-other-optimizations\">4. Other optimizations<\/a><\/h2>\n<p>Martin also implemented several other performance improvements, some aiming at\nNSCL and not only JShelter. For example, NSCL included a JavaScript library to\ncompute SHA-256, while native <code>SubtleCrypto<\/code> implementation is faster.<\/p>\n<h2 id=\"5-results\"><a class=\"toclink\" href=\"#5-results\">5. Results<\/a><\/h2>\n<p>First, let us have a look at Firefox and the improvements in 2D Canvas in\n<code>getImageData()<\/code> (note that the y-axis is logarithmic):<\/p>\n<p><img alt=\"Performance of getImageData in\nFirefox\" src=\"https:\/\/jshelter.org\/optimizations\/firefox_canvas_recommended.png\"><\/p>\n<p>As expected, the optimized implementation is slower than the original because it\nneeds much more work. Even so, the performance hit is several magnitudes lower\nthan the hit in 0.12.2.<\/p>\n<p>Now, let us have a look the improvements in 3D Canvas in <code>readPixels()<\/code> (this\ntime, we show the graph with the linear y-axis, the shape of the points\ndepicting the performance is similar for 2D and 3D canvas):<\/p>\n<p><img alt=\"Performance of readPixels in\nFirefox\" src=\"https:\/\/jshelter.org\/optimizations\/firefox_canvas3d_recommended.png\"><\/p>\n<p>See that the original implementation quickly leaves the plotted range. Its\nperformance hit was more significant than the 2D version while starting from\n0.14, the performance hit of 2D and 3D canvases are comparable.<\/p>\n<p>Firefox implementation of <code>getChannelData<\/code> has a negligible running time (almost\n0). The following figure shows that the little lies are computed much quicker\n(about two orders of magnitude), but the impact is still significant. Note that\nthe y-axis is again logarithmic.<\/p>\n<p><img alt=\"Performance of getChannelData in\nFirefox\" src=\"https:\/\/jshelter.org\/optimizations\/firefox_audio_recommended.png\"><\/p>\n<p>Martin also developed <a href=\"https:\/\/pagure.io\/JShelter\/webextension\/blob\/1c86c45f565a36a6234c210392a89e3e20f32027\/f\/tests\/performance_tests\">performance\ntests<\/a>\nbased on <a href=\"https:\/\/developer.chrome.com\/docs\/lighthouse\/\">Google Lighthouse<\/a> that\nrun in Chrome. We tested on 46 pages from the 100 of\n<a href=\"https:\/\/tranco-list.eu\/\">Tranco<\/a> and give the <a href=\"https:\/\/developer.chrome.com\/docs\/lighthouse\/performance\/performance-scoring\/\">performance\nscore<\/a>\nbelow. The performance score approximates how users perceive the loading speed\nof the visited page. The 25th percentile of all pages should receive a score of\n50.<\/p>\n<p>The average performance score of all tested pages was 66 without JShelter. When\ntested with JShelter 0.12.2, the score dropped to 62.5. The average of all\ntested pages raised to 64 with JShelter 0.15.1. The performance score was the\nsame or better in JShelter 0.15.1 compared to 0.12.2 on 33 pages. It increased\non 18 pages.<\/p>","category":{"@attributes":{"term":"pt"}}},{"title":"JShelter can be translated into different languages","link":{"@attributes":{"href":"https:\/\/jshelter.org\/i18n\/","rel":"alternate"}},"published":"2023-09-08T14:00:00+02:00","updated":"2024-03-26T12:54:03+01:00","author":{"name":"The JShelter team"},"id":"tag:jshelter.org,2023-09-08:\/i18n\/","summary":"<p>JShelter's audience is international. As not all people speak English, JShelter\nis now adding support for internationalization. Hence, it can be translated\ninto languages other than English.<\/p>\n<p>The webextension API already contains\n<a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Mozilla\/Add-ons\/WebExtensions\/API\/i18n\">APIs<\/a>\nthat help to\n<a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Mozilla\/Add-ons\/WebExtensions\/Internationalization\">translate extensions<\/a>.\nThis way we can localize not only the interface of the extension \u2026<\/p>","content":"<p>JShelter's audience is international. As not all people speak English, JShelter\nis now adding support for internationalization. Hence, it can be translated\ninto languages other than English.<\/p>\n<p>The webextension API already contains\n<a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Mozilla\/Add-ons\/WebExtensions\/API\/i18n\">APIs<\/a>\nthat help to\n<a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Mozilla\/Add-ons\/WebExtensions\/Internationalization\">translate extensions<\/a>.\nThis way we can localize not only the interface of the extension but also its\ndescription and even the name.<\/p>\n<div class=\"toc\">\n<ul>\n<li><a href=\"#status\">Status<\/a><\/li>\n<li><a href=\"#how-can-i-translate-jshelter-option-1-weblate\">How can I translate JShelter? Option 1: Weblate<\/a><\/li>\n<li><a href=\"#how-can-i-translate-jshelter-option-2-json-files-in-the-repository\">How can I translate JShelter? Option 2: JSON files in the repository<\/a><\/li>\n<li><a href=\"#final-remarks\">Final remarks<\/a><\/li>\n<\/ul>\n<\/div>\n<h3 id=\"status\"><a class=\"toclink\" href=\"#status\">Status<\/a><\/h3>\n<p>JShelter can be translated to other languages as of version 0.14. Besides\nEnglish, Czech and Russian translations are also available.<\/p>\n<p>Nevertheless, we are looking for translators in other languages. If you are\nwilling to translate JShelter to a language that you know, please read on. We\nsuggest that you contact us early so that we have information about ongoing\ntranslations and that we can tell you if someone else is already interested in\nthe same language as you.<\/p>\n<h3 id=\"how-can-i-translate-jshelter-option-1-weblate\"><a class=\"toclink\" href=\"#how-can-i-translate-jshelter-option-1-weblate\">How can I translate JShelter? Option 1: Weblate<\/a><\/h3>\n<p>We use <a href=\"https:\/\/github.com\/WeblateOrg\/weblate\">Weblate<\/a> to manage the\ntranslations. This software helps us monitor the status of the translations,\nproviding suggestions and improving the lives of the translators. If you want\nto help, visit\n<a href=\"https:\/\/hosted.weblate.org\/projects\/jshelter\/webextension\/\">JShelter on Weblate<\/a>.\nYou can send us suggestions without registration. However, some features, like\nnotifications, are only available to registered users.<\/p>\n<p>When you start translating, you should see a form like:<\/p>\n<p><img alt=\"A screenshot from Weblate\" src=\"https:\/\/jshelter.org\/i18n\/webly.png\"><\/p>\n<p>The interface shows:<\/p>\n<ol>\n<li>The name of the key of the currently translated string,<\/li>\n<li>the original English translation,<\/li>\n<li>the description of the usage of the string,<\/li>\n<li>the translation to the current target language,<\/li>\n<li>the information on similar and nearby strings that might be handy to grasp the context of the\n   string,<\/li>\n<li>glossary containing key terms used in the project.<\/li>\n<\/ol>\n<p>Most of the strings that translators face are simple, as shown above. However,\nsome strings are composed with <code>placeholders<\/code> (see the translation option 2 for\nmore details on placeholders). For example, consider\n<a href=\"https:\/\/hosted.weblate.org\/translate\/jshelter\/webextension\/en\/?checksum=a11db49ff616c6f0&amp;sort_by=-priority,position\">string <code>defaultLevelSelection<\/code><\/a>.\nIts English translation is <code>Default level ($levelName$)<\/code>. Strings enclosed by\n<code>$<\/code> signs (<code>levelName<\/code> in this case) are <code>placeholders<\/code>. Each placeholder is in\nthe database with the key composed as <code>the base key ###placeholders###\nplaceholder name<\/code>. So, in this case, the key of the placeholder is\n<code>defaultLevelSelection###placeholders###levelName<\/code>.<\/p>\n<p>Most placeholders should not be changed. The typical content of such\nplaceholders is like <code>$1<\/code>. It means that the extension generates the content,\nand typically, translators are supposed to keep the string intact. Always read\ncarefully the description of the usage of the string that indicates how the\nstring should be translated.<\/p>\n<p>Some placeholder strings contain the names of APIs or other technical terms.\nFor example, <code>jssgroupTimePrecisionDescription2###placeholders###apis<\/code> in\nEnglish contains <code>(Date, Performance, events, Gamepad API, and Web VR API)<\/code>.\nPlease read the instructions for each string carefully. In this case,\ntranslators should keep the names of the APIs but translate the punctuation and\nconjunctions.<\/p>\n<p>Please consider the translations of each placeholder in the context of the base\nstring so that the whole string makes sense. It should be easy to find the base\nkey. Look at the start of the placeholder key or to the instructions. Weblate\nshould show the base string and its placeholders in the <em>Nearby strings<\/em>\nsection.<\/p>\n<p>Strings containing URLs should not be translated. However, occasionally, there\nis a localized version of the content at the original URL. In such cases, it\nmakes sense to provide URL to the localized version.<\/p>\n<p>As we explain below, we do not support plural forms due to technical\nlimitations and our decisions. So, use phrasing that does not need plurals. See\nthe example for option 2 for more details.<\/p>\n<h3 id=\"how-can-i-translate-jshelter-option-2-json-files-in-the-repository\"><a class=\"toclink\" href=\"#how-can-i-translate-jshelter-option-2-json-files-in-the-repository\">How can I translate JShelter? Option 2: JSON files in the repository<\/a><\/h3>\n<p>Technically, translations are strings located in JSON files, one JSON file per\nlanguage. The languages are stored in the\n<a href=\"https:\/\/pagure.io\/JShelter\/webextension\/blob\/i18n\/f\/common\/_locales\"><code>common\/_locales<\/code><\/a>\ndirectory. If you do not see your language there, you can add a new language by\ncreating a new directory with the\n<a href=\"https:\/\/www.unicode.org\/cldr\/charts\/43\/supplemental\/language_plural_rules.html\">language code<\/a>\nas the name, see the\n<a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Mozilla\/Add-ons\/WebExtensions\/Internationalization#localized_string_selection\">docs<\/a>.<\/p>\n<p>Then, copy the <code>en\/messages.json<\/code> to the just created directory, and you have\nthe basis for the translation.<\/p>\n<p>Next, go through all the strings and translate them to your language.<\/p>\n<p>Typically, a translation entry looks like this:<\/p>\n<div class=\"highlight\"><pre><span><\/span><code><span class=\"w\">  <\/span><span class=\"nt\">&quot;javascriptShield&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span><span class=\"w\"><\/span>\n<span class=\"w\">    <\/span><span class=\"nt\">&quot;message&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;JavaScript Shield&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"><\/span>\n<span class=\"w\">    <\/span><span class=\"nt\">&quot;description&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;The name of the JavaScript Shield displayed at multiple places&quot;<\/span><span class=\"w\"><\/span>\n<span class=\"w\">  <\/span><span class=\"p\">},<\/span><span class=\"w\"><\/span>\n<\/code><\/pre><\/div>\n\n<p>The first line (<code>javascriptShield<\/code>) displays the name of the string. JShelter\npairs the name of the string with the locations in the source code where this\ntranslation should appear. So, do not change that line.<\/p>\n<p>The second line (key <code>message<\/code>) shows the actual translation. Usually,\ntranslators are supposed to change that line.<\/p>\n<p>The third line is the description of the context of the string. So, for\nexample, the translator would learn where and how the extension uses the\nmessage. Please do not translate the description.<\/p>\n<p>So, for example, in the Czech translation, the entry becomes:<\/p>\n<div class=\"highlight\"><pre><span><\/span><code><span class=\"w\">   <\/span><span class=\"nt\">&quot;javascriptShield&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span><span class=\"w\"><\/span>\n<span class=\"w\">     <\/span><span class=\"nt\">&quot;message&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;JavaScriptov\u00fd \u0161t\u00edt&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"><\/span>\n<span class=\"w\">     <\/span><span class=\"nt\">&quot;description&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;The name of the JavaScript Shield displayed at multiple places&quot;<\/span><span class=\"w\"><\/span>\n<span class=\"w\">   <\/span><span class=\"p\">},<\/span><span class=\"w\"><\/span>\n<\/code><\/pre><\/div>\n\n<p>However, some entries are more tricky, let us look at:<\/p>\n<div class=\"highlight\"><pre><span><\/span><code><span class=\"w\">    <\/span><span class=\"nt\">&quot;defaultLevelSelection&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span><span class=\"w\"><\/span>\n<span class=\"w\">        <\/span><span class=\"nt\">&quot;message&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;Default level ($levelName$)&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"><\/span>\n<span class=\"w\">        <\/span><span class=\"nt\">&quot;description&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;This text is displayed as the default level in the popup&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"><\/span>\n<span class=\"w\">        <\/span><span class=\"nt\">&quot;placeholders&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span><span class=\"w\"><\/span>\n<span class=\"w\">            <\/span><span class=\"nt\">&quot;levelName&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span><span class=\"w\"><\/span>\n<span class=\"w\">                <\/span><span class=\"nt\">&quot;content&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;$1&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"><\/span>\n<span class=\"w\">                <\/span><span class=\"nt\">&quot;description&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;Translated name of the default level used by the user&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"><\/span>\n<span class=\"w\">                <\/span><span class=\"nt\">&quot;example&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;Recommended, see the keys JSSL*Name like JSSL2Name&quot;<\/span><span class=\"w\"><\/span>\n<span class=\"w\">            <\/span><span class=\"p\">}<\/span><span class=\"w\"><\/span>\n<span class=\"w\">        <\/span><span class=\"p\">}<\/span><span class=\"w\"><\/span>\n<span class=\"w\">    <\/span><span class=\"p\">},<\/span><span class=\"w\"><\/span>\n<\/code><\/pre><\/div>\n\n<p>The <code>message<\/code> contains a variable enclosed by the dollar sign (in this case,\n<code>levelName<\/code>). Note that the <code>placeholders<\/code> section contains the specification\nof the variable. We add substrings with special properties to the\n<code>placeholders<\/code> section. Such substrings are either filled automatically or hold\ndata that should be translated according to the different rules than the\n<code>message<\/code>. Follow the <code>description<\/code> to learn the rules.<\/p>\n<p>In this case, the single <code>placeholders<\/code> entry contains three keys: <code>content<\/code>,\n<code>description<\/code>, and <code>example<\/code>. The meaning of the description is the same as in\nthe original case. It explains the context of the variable. Additionally,\n<code>example<\/code> gives example strings or their description to provide the translator\nwith more information about the possible content. The <code>content<\/code> controls how\nthe content is created. If the file does not give translators any further\ninstructions (like in this case), please do not translate the <code>placeholder<\/code>\nsection. So the translated entry becomes:<\/p>\n<div class=\"highlight\"><pre><span><\/span><code><span class=\"w\">    <\/span><span class=\"nt\">&quot;defaultLevelSelection&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span><span class=\"w\"><\/span>\n<span class=\"w\">        <\/span><span class=\"nt\">&quot;message&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;V\u00fdchoz\u00ed \u00farove\u0148 ($levelName$)&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"><\/span>\n<span class=\"w\">        <\/span><span class=\"nt\">&quot;description&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;This text is displayed as the default level in the popup&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"><\/span>\n<span class=\"w\">        <\/span><span class=\"nt\">&quot;placeholders&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span><span class=\"w\"><\/span>\n<span class=\"w\">            <\/span><span class=\"nt\">&quot;levelName&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span><span class=\"w\"><\/span>\n<span class=\"w\">                <\/span><span class=\"nt\">&quot;content&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;$1&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"><\/span>\n<span class=\"w\">                <\/span><span class=\"nt\">&quot;description&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;Translated name of the default level used by the user&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"><\/span>\n<span class=\"w\">                <\/span><span class=\"nt\">&quot;example&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;Recommended, see the keys JSSL*Name like JSSL2Name&quot;<\/span><span class=\"w\"><\/span>\n<span class=\"w\">            <\/span><span class=\"p\">}<\/span><span class=\"w\"><\/span>\n<span class=\"w\">        <\/span><span class=\"p\">}<\/span><span class=\"w\"><\/span>\n<span class=\"w\">    <\/span><span class=\"p\">},<\/span><span class=\"w\"><\/span>\n<\/code><\/pre><\/div>\n\n<p>See that only the <code>message<\/code> string changed.<\/p>\n<p>A more complicated example is:<\/p>\n<div class=\"highlight\"><pre><span><\/span><code><span class=\"w\">    <\/span><span class=\"nt\">&quot;jssgroupTimePrecisionDescription2&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span><span class=\"w\"><\/span>\n<span class=\"w\">        <\/span><span class=\"nt\">&quot;message&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;Limit the precision of high-resolution time stamps $apis$. Timestamps provided by the Geolocation API are wrapped as well if you enable \\&quot;$jssgroupPhysicalLocationGeolocation$\\&quot; protection&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"><\/span>\n<span class=\"w\">        <\/span><span class=\"nt\">&quot;description&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;Displayed at various places&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"><\/span>\n<span class=\"w\">        <\/span><span class=\"nt\">&quot;placeholders&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span><span class=\"w\"><\/span>\n<span class=\"w\">            <\/span><span class=\"nt\">&quot;apis&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span><span class=\"w\"><\/span>\n<span class=\"w\">                <\/span><span class=\"nt\">&quot;content&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;(Date, Performance, events, Gamepad API, and Web VR API)&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"><\/span>\n<span class=\"w\">                <\/span><span class=\"nt\">&quot;description&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;Keep the names of the APIs but translate the punctuation and conjunctions&quot;<\/span><span class=\"w\"><\/span>\n<span class=\"w\">            <\/span><span class=\"p\">},<\/span><span class=\"w\"><\/span>\n<span class=\"w\">            <\/span><span class=\"nt\">&quot;jssgroupPhysicalLocationGeolocation&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span><span class=\"w\"><\/span>\n<span class=\"w\">                <\/span><span class=\"nt\">&quot;content&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;$1&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"><\/span>\n<span class=\"w\">                <\/span><span class=\"nt\">&quot;description&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;Translated version of the jssgroupPhysicalLocationGeolocation string&quot;<\/span><span class=\"w\"><\/span>\n<span class=\"w\">            <\/span><span class=\"p\">}<\/span><span class=\"w\"><\/span>\n<span class=\"w\">        <\/span><span class=\"p\">}<\/span><span class=\"w\"><\/span>\n<span class=\"w\">    <\/span><span class=\"p\">},<\/span><span class=\"w\"><\/span>\n<\/code><\/pre><\/div>\n\n<p>This string holds two placeholders: <code>apis<\/code> and\n<code>jssgroupPhysicalLocationGeolocation<\/code>. Let us start from the end.\n<code>jssgroupPhysicalLocationGeolocation<\/code> should not be touched by the translator.\nThe text explains that <code>jssgroupPhysicalLocationGeolocation<\/code> will hold the\ntranslation of the string with the same name during the execution of the\nextension (\"Physical location (geolocation)\" in this case). Do not change\nanything in entries like <code>jssgroupPhysicalLocationGeolocation<\/code> during the\ntranslation.<\/p>\n<p>Then, there is the entry called <code>apis<\/code>. If you look at the content, it does not\nrefer to any automatically filled string like the previous examples. Instead,\nthe string holds the names of several APIs. The <code>desription<\/code> provides\ninstructions on how to handle the translation. In this case, we suggest keeping\nthe APIs' names in English. That way, the user can search for the APIs for more\ninformation. If you translate the API names, the user would likely be unable to\nfind any information about the API. However, different languages have different\nrules for punctuation, and the conjunction <em>and<\/em> must be translated. So the\ntranslation can look like:<\/p>\n<div class=\"highlight\"><pre><span><\/span><code><span class=\"w\">    <\/span><span class=\"nt\">&quot;jssgroupTimePrecisionDescription2&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span><span class=\"w\"><\/span>\n<span class=\"w\">        <\/span><span class=\"nt\">&quot;message&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;Omezuje p\u0159esnost \u010dasov\u00fdch zna\u010dek dostupn\u00fdch v API jako je $apis$. \u010casov\u00e9 zna\u010dky Geolocation API jsou tak\u00e9 zm\u011bn\u011bny, pokud je z\u00e1rove\u0148 aktivn\u00ed ochrana \u201e$jssgroupPhysicalLocationGeolocation$\u201c.&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"><\/span>\n<span class=\"w\">        <\/span><span class=\"nt\">&quot;description&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;Displayed at various places&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"><\/span>\n<span class=\"w\">        <\/span><span class=\"nt\">&quot;placeholders&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span><span class=\"w\"><\/span>\n<span class=\"w\">            <\/span><span class=\"nt\">&quot;apis&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span><span class=\"w\"><\/span>\n<span class=\"w\">                <\/span><span class=\"nt\">&quot;content&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;Date, Performance, Gamepad API, Web VR API a v r\u00e1mci ud\u00e1lost\u00ed&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"><\/span>\n<span class=\"w\">                <\/span><span class=\"nt\">&quot;description&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;Keep the names of the APIs but translate the punctuation and conjunctions&quot;<\/span><span class=\"w\"><\/span>\n<span class=\"w\">            <\/span><span class=\"p\">},<\/span><span class=\"w\"><\/span>\n<span class=\"w\">            <\/span><span class=\"nt\">&quot;jssgroupPhysicalLocationGeolocation&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span><span class=\"w\"><\/span>\n<span class=\"w\">                <\/span><span class=\"nt\">&quot;content&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;$1&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"><\/span>\n<span class=\"w\">                <\/span><span class=\"nt\">&quot;description&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;Translated version of the jssgroupPhysicalLocationGeolocation string&quot;<\/span><span class=\"w\"><\/span>\n<span class=\"w\">            <\/span><span class=\"p\">}<\/span><span class=\"w\"><\/span>\n<span class=\"w\">        <\/span><span class=\"p\">}<\/span><span class=\"w\"><\/span>\n<span class=\"w\">    <\/span><span class=\"p\">},<\/span><span class=\"w\"><\/span>\n<\/code><\/pre><\/div>\n\n<p>Note that the original translation used a different kind of quotation marks.\nThe English original uses the same character as the JSON delimiter. Hence, the\ncharacter '\\' precedes the quotation mark. The Czech translation does not use\nquotation marks conflicting with the JSON quotation marks, so it does not need\nthe '\\' sign.<\/p>\n<p>Occasionally, we extract URLs to the <code>placeholders<\/code> section to highlight that\nthey should not be translated like:<\/p>\n<div class=\"highlight\"><pre><span><\/span><code><span class=\"w\">    <\/span><span class=\"nt\">&quot;newLevelsNotRecommended&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span><span class=\"w\"><\/span>\n<span class=\"w\">        <\/span><span class=\"nt\">&quot;message&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;We do not recommend creating your own levels and changing configuration if you are concerned about browser fingerprinting. Please read &lt;a href=\\&quot;$FAQURL$\\&quot;&gt;FAQ&lt;\/a&gt; and our &lt;a href=\\&quot;$PAPERURL$\\&quot;&gt;paper&lt;\/a&gt;. By diverging from the configuration of other users, you make your re-identification easier.&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"><\/span>\n<span class=\"w\">        <\/span><span class=\"nt\">&quot;placeholders&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span><span class=\"w\"><\/span>\n<span class=\"w\">            <\/span><span class=\"nt\">&quot;faqurl&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span><span class=\"w\"><\/span>\n<span class=\"w\">                <\/span><span class=\"nt\">&quot;content&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;https:\/\/jshelter.org\/faq\/&quot;<\/span><span class=\"w\"><\/span>\n<span class=\"w\">            <\/span><span class=\"p\">},<\/span><span class=\"w\"><\/span>\n<span class=\"w\">            <\/span><span class=\"nt\">&quot;paperurl&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span><span class=\"w\"><\/span>\n<span class=\"w\">                <\/span><span class=\"nt\">&quot;content&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;https:\/\/arxiv.org\/abs\/2204.01392&quot;<\/span><span class=\"w\"><\/span>\n<span class=\"w\">            <\/span><span class=\"p\">}<\/span><span class=\"w\"><\/span>\n<span class=\"w\">        <\/span><span class=\"p\">},<\/span><span class=\"w\"><\/span>\n<span class=\"w\">        <\/span><span class=\"nt\">&quot;description&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;This message is displayed while creating a new level in options. Make sure that you keep correct HTML markup&quot;<\/span><span class=\"w\"><\/span>\n<span class=\"w\">    <\/span><span class=\"p\">},<\/span><span class=\"w\"><\/span>\n<\/code><\/pre><\/div>\n\n<p>Note that strings for translation can hold HTML markup in some cases. In that\ncase, we suggest keeping the same markup. However, if other languages handle\nsuch texts differently, translators are free to apply such rules. The\ntranslation can look like:<\/p>\n<div class=\"highlight\"><pre><span><\/span><code><span class=\"w\">    <\/span><span class=\"nt\">&quot;newLevelsNotRecommended&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span><span class=\"w\"><\/span>\n<span class=\"w\">        <\/span><span class=\"nt\">&quot;message&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;Nedoporu\u010dujeme tvorbu vlastn\u00edch \u00farovn\u00ed ochrany a zm\u011bnu konfigurace pokud pou\u017e\u00edv\u00e1te JShelter jako ochranu p\u0159ed tvorbou otisku prohl\u00ed\u017ee\u010de. P\u0159e\u010dt\u011bte si &lt;a href=\\&quot;$FAQURL$\\&quot;&gt;FAQ&lt;\/a&gt; a n\u00e1\u0161 &lt;a href=\\&quot;$PAPERURL$\\&quot;&gt;\u010dl\u00e1nek&lt;\/a&gt;. V p\u0159\u00edpad\u011b, \u017ee se va\u0161e nastaven\u00ed bude li\u0161it od nastaven\u00ed jin\u00fdch u\u017eivatel\u016f, usnad\u0148ujete va\u0161i identifikaci v budoucnu.&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"><\/span>\n<span class=\"w\">        <\/span><span class=\"nt\">&quot;placeholders&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span><span class=\"w\"><\/span>\n<span class=\"w\">            <\/span><span class=\"nt\">&quot;faqurl&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span><span class=\"w\"><\/span>\n<span class=\"w\">                <\/span><span class=\"nt\">&quot;content&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;https:\/\/jshelter.org\/faq\/&quot;<\/span><span class=\"w\"><\/span>\n<span class=\"w\">            <\/span><span class=\"p\">},<\/span><span class=\"w\"><\/span>\n<span class=\"w\">            <\/span><span class=\"nt\">&quot;paperurl&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span><span class=\"w\"><\/span>\n<span class=\"w\">                <\/span><span class=\"nt\">&quot;content&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;https:\/\/arxiv.org\/abs\/2204.01392&quot;<\/span><span class=\"w\"><\/span>\n<span class=\"w\">            <\/span><span class=\"p\">}<\/span><span class=\"w\"><\/span>\n<span class=\"w\">        <\/span><span class=\"p\">},<\/span><span class=\"w\"><\/span>\n<span class=\"w\">        <\/span><span class=\"nt\">&quot;description&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;This message is displayed while creating a new level in options. Make sure that you keep correct HTML markup&quot;<\/span><span class=\"w\"><\/span>\n<span class=\"w\">    <\/span><span class=\"p\">},<\/span><span class=\"w\"><\/span>\n<\/code><\/pre><\/div>\n\n<p>On rare occasions, we do not expect the translators to touch the <code>message<\/code>\nitself but rather work with the placeholders. For example, see:<\/p>\n<div class=\"highlight\"><pre><span><\/span><code><span class=\"w\">    <\/span><span class=\"nt\">&quot;NBSDescription&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span><span class=\"w\"><\/span>\n<span class=\"w\">        <\/span><span class=\"nt\">&quot;message&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;&lt;p&gt;$PARAGRAPH1$&lt;\/p&gt;&lt;p&gt;$PARAGRAPH2$&lt;\/p&gt;&lt;p&gt;$PARAGRAPH3$&lt;\/p&gt;&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"><\/span>\n<span class=\"w\">        <\/span><span class=\"nt\">&quot;description&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;This is the description of NBS shown in options. Please do not modify the template string in the message but translate the paragraphs in the placeholders section. If you find it necessary, you can remove or add paragraphs.&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"><\/span>\n<span class=\"w\">        <\/span><span class=\"nt\">&quot;placeholders&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span><span class=\"w\"><\/span>\n<span class=\"w\">            <\/span><span class=\"nt\">&quot;paragraph1&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span><span class=\"w\"><\/span>\n<span class=\"w\">                <\/span><span class=\"nt\">&quot;content&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;Long text.&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"><\/span>\n<span class=\"w\">                <\/span><span class=\"nt\">&quot;description&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;Paragraph 1, please translate this text, keep the URLs or replace them to a translated version of the targets.&quot;<\/span><span class=\"w\"><\/span>\n<span class=\"w\">            <\/span><span class=\"p\">},<\/span><span class=\"w\"><\/span>\n<span class=\"w\">            <\/span><span class=\"nt\">&quot;paragraph2&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span><span class=\"w\"><\/span>\n<span class=\"w\">                <\/span><span class=\"nt\">&quot;content&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;Long text.&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"><\/span>\n<span class=\"w\">                <\/span><span class=\"nt\">&quot;description&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;Paragraph 2, please translate this text.&quot;<\/span><span class=\"w\"><\/span>\n<span class=\"w\">            <\/span><span class=\"p\">},<\/span><span class=\"w\"><\/span>\n<span class=\"w\">            <\/span><span class=\"nt\">&quot;paragraph3&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span><span class=\"w\"><\/span>\n<span class=\"w\">                <\/span><span class=\"nt\">&quot;content&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;Long text.&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"><\/span>\n<span class=\"w\">                <\/span><span class=\"nt\">&quot;description&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;Paragraph 3, please translate this text, note that &lt;i&gt;Manage exception list&lt;\/i&gt; refers to the ManageWhitelist string.&quot;<\/span><span class=\"w\"><\/span>\n<span class=\"w\">            <\/span><span class=\"p\">}<\/span><span class=\"w\"><\/span>\n<span class=\"w\">        <\/span><span class=\"p\">}<\/span><span class=\"w\"><\/span>\n<span class=\"w\">    <\/span><span class=\"p\">},<\/span><span class=\"w\"><\/span>\n<\/code><\/pre><\/div>\n\n<p>You can see that the message holds a template with an HTML markup. In this\ncase, we opted for this approach for several reasons:<\/p>\n<ul>\n<li>We expect translators to preserve the same template (<code>message<\/code>) in most, if\n  not all, languages.<\/li>\n<li>Each paragraph (<code>placeholders<\/code> entry) can have specific instructions.<\/li>\n<li>The text blocks are not as long as a single block containing all text would\n  be.<\/li>\n<\/ul>\n<p>English has a single plural form for cardinal numbers (usually a suffix <code>s<\/code> or\n<code>es<\/code> appended to the singular form) and several forms of ordinal numbers (1st,\n2nd, 3rd, 4th).\n<a href=\"https:\/\/www.unicode.org\/cldr\/charts\/43\/supplemental\/language_plural_rules.html\">Other languages behave differently.<\/a>\nUnfortunately, the\n<a href=\"\/i18n_developers\/\">Webextension APIs do not handle plurals well<\/a> and JShelter\ncould use plurals only in notifications of Network Boundary Shield, so we\ndecided to rephrase the messages so plurals are not needed:<\/p>\n<div class=\"highlight\"><pre><span><\/span><code><span class=\"w\">        <\/span><span class=\"nt\">&quot;message&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;Count of detected requests\\nby $ORIGIN$\\nthat accessed local\\nnetwork: $COUNT$.&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"><\/span>\n<\/code><\/pre><\/div>\n\n<h3 id=\"final-remarks\"><a class=\"toclink\" href=\"#final-remarks\">Final remarks<\/a><\/h3>\n<p>If you want to start translating a new language, let us know. For example, you\ncan open an issue in the\n<a href=\"https:\/\/pagure.io\/JShelter\/webextension\/issues\">issue tracker<\/a> or send us\n<a href=\"mailto:jshelter@gnu.org\">e-mail<\/a>. If you are in doubt about how to translate a\nstring or do not understand its meaning, let us know in an\n<a href=\"https:\/\/pagure.io\/JShelter\/webextension\/issues\">issue<\/a> or send us\n<a href=\"mailto:jshelter@gnu.org\">e-mail<\/a>.<\/p>","category":{"@attributes":{"term":"posts"}}},{"title":"JShelter can be translated into different languages","link":{"@attributes":{"href":"https:\/\/jshelter.org\/pt\/i18n\/","rel":"alternate"}},"published":"2023-09-08T14:00:00+02:00","updated":"2025-07-10T07:01:03+02:00","author":{"name":"The JShelter team"},"id":"tag:jshelter.org,2023-09-08:\/pt\/i18n\/","summary":"<p>JShelter's audience is international. As not all people speak English, JShelter\nis now adding support for internationalization. Hence, it can be translated into\nlanguages other than English.<\/p>\n<p>The webextension API already contains\n<a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Mozilla\/Add-ons\/WebExtensions\/API\/i18n\">APIs<\/a>\nthat help to <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Mozilla\/Add-ons\/WebExtensions\/Internationalization\">translate\nextensions<\/a>.\nThis way we can localize not only the interface of the extension \u2026<\/p>","content":"<p>JShelter's audience is international. As not all people speak English, JShelter\nis now adding support for internationalization. Hence, it can be translated into\nlanguages other than English.<\/p>\n<p>The webextension API already contains\n<a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Mozilla\/Add-ons\/WebExtensions\/API\/i18n\">APIs<\/a>\nthat help to <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Mozilla\/Add-ons\/WebExtensions\/Internationalization\">translate\nextensions<\/a>.\nThis way we can localize not only the interface of the extension but also its\ndescription and even the name.<\/p>\n<div class=\"toc\">\n<ul>\n<li><a href=\"#status\">Status<\/a><\/li>\n<li><a href=\"#how-can-i-translate-jshelter-option-1-weblate\">How can I translate JShelter? Option 1: Weblate<\/a><\/li>\n<li><a href=\"#how-can-i-translate-jshelter-option-2-json-files-in-the-repository\">How can I translate JShelter? Option 2: JSON files in the repository<\/a><\/li>\n<li><a href=\"#final-remarks\">Final remarks<\/a><\/li>\n<\/ul>\n<\/div>\n<h3 id=\"status\"><a class=\"toclink\" href=\"#status\">Status<\/a><\/h3>\n<p>JShelter can be translated to other languages as of version 0.14. Besides\nEnglish, Czech and Russian translations are also available.<\/p>\n<p>Nevertheless, we are looking for translators in other languages. If you are\nwilling to translate JShelter to a language that you know, please read on. We\nsuggest that you contact us early so that we have information about ongoing\ntranslations and that we can tell you if someone else is already interested in\nthe same language as you.<\/p>\n<h3 id=\"how-can-i-translate-jshelter-option-1-weblate\"><a class=\"toclink\" href=\"#how-can-i-translate-jshelter-option-1-weblate\">How can I translate JShelter? Option 1: Weblate<\/a><\/h3>\n<p>We use <a href=\"https:\/\/github.com\/WeblateOrg\/weblate\">Weblate<\/a> to manage the\ntranslations. This software helps us monitor the status of the translations,\nproviding suggestions and improving the lives of the translators. If you want to\nhelp, visit <a href=\"https:\/\/hosted.weblate.org\/projects\/jshelter\/webextension\/\">JShelter on\nWeblate<\/a>. You can\nsend us suggestions without registration. However, some features, like\nnotifications, are only available to registered users.<\/p>\n<p>When you start translating, you should see a form like:<\/p>\n<p><img alt=\"A screenshot from Weblate\" src=\"https:\/\/jshelter.org\/i18n\/webly.png\"><\/p>\n<p>The interface shows:<\/p>\n<ol>\n<li>The name of the key of the currently translated string,<\/li>\n<li>the original English translation,<\/li>\n<li>the description of the usage of the string,<\/li>\n<li>the translation to the current target language,<\/li>\n<li>the information on similar and nearby strings that might be handy to grasp the\ncontext of the string,<\/li>\n<li>glossary containing key terms used in the project.<\/li>\n<\/ol>\n<p>Most of the strings that translators face are simple, as shown above. However,\nsome strings are composed with <code>placeholders<\/code> (see the translation option 2 for\nmore details on placeholders). For example, consider <a href=\"https:\/\/hosted.weblate.org\/translate\/jshelter\/webextension\/en\/?\nchecksum=a11db49ff616c6f0&amp;sort_by=-priority,position\">string\n<code>defaultLevelSelection<\/code><\/a>. Its English translation\nis <code>Default level ($levelName$)<\/code>. Strings enclosed by <code>$<\/code> signs (<code>levelName<\/code> in\nthis case) are <code>placeholders<\/code>. Each placeholder is in the database with the key\ncomposed as <code>the base key ###placeholders### placeholder name<\/code>. So, in this\ncase, the key of the placeholder is\n<code>defaultLevelSelection###placeholders###levelName<\/code>.<\/p>\n<p>Most placeholders should not be changed. The typical content of such placeholders\nis like <code>$1<\/code>. It means that the extension generates the content, and typically,\ntranslators are supposed to keep the string intact. Always read carefully the\ndescription of the usage of the string that indicates how the string should be\ntranslated.<\/p>\n<p>Some placeholder strings contain the names of APIs or other technical terms. For\nexample, <code>jssgroupTimePrecisionDescription2###placeholders###apis<\/code> in English\ncontains <code>(Date, Performance, events, Gamepad API, and Web VR API)<\/code>. Please read\nthe instructions for each string carefully. In this case, translators should\nkeep the names of the APIs but translate the punctuation and conjunctions.<\/p>\n<p>Please consider the translations of each placeholder in the context of the base\nstring so that the whole string makes sense. It should be easy to find the base\nkey. Look at the start of the placeholder key or to the instructions. Weblate\nshould show the base string and its placeholders in the <em>Nearby strings<\/em> section.<\/p>\n<p>Strings containing URLs should not be translated. However, occasionally, there is\na localized version of the content at the original URL. In such cases, it makes\nsense to provide URL to the localized version.<\/p>\n<p>As we explain below, we do not support plural forms due to technical limitations\nand our decisions. So, use phrasing that does not need plurals. See the example\nfor option 2 for more details.<\/p>\n<h3 id=\"how-can-i-translate-jshelter-option-2-json-files-in-the-repository\"><a class=\"toclink\" href=\"#how-can-i-translate-jshelter-option-2-json-files-in-the-repository\">How can I translate JShelter? Option 2: JSON files in the repository<\/a><\/h3>\n<p>Technically, translations are strings located in JSON files, one JSON file per\nlanguage. The languages are stored in the\n<a href=\"https:\/\/pagure.io\/JShelter\/webextension\/blob\/i18n\/f\/common\/_locales\"><code>common\/_locales<\/code><\/a>\ndirectory. If you do not see your language there, you can add a new language by\ncreating a new directory with the <a href=\"https:\/\/www.unicode.org\/cldr\/charts\/43\/supplemental\/language_plural_rules.html\">language\ncode<\/a>\nas the name, see the\n<a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Mozilla\/Add-ons\/WebExtensions\/Internationalization#localized_string_selection\">docs<\/a>.<\/p>\n<p>Then, copy the <code>en\/messages.json<\/code> to the just created directory, and you have the\nbasis for the translation.<\/p>\n<p>Next, go through all the strings and translate them to your language.<\/p>\n<p>Typically, a translation entry looks like this:<\/p>\n<div class=\"highlight\"><pre><span><\/span><code><span class=\"w\">  <\/span><span class=\"nt\">&quot;javascriptShield&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span><span class=\"w\"><\/span>\n<span class=\"w\">    <\/span><span class=\"nt\">&quot;message&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;JavaScript Shield&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"><\/span>\n<span class=\"w\">    <\/span><span class=\"nt\">&quot;description&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;The name of the JavaScript Shield displayed at multiple places&quot;<\/span><span class=\"w\"><\/span>\n<span class=\"w\">  <\/span><span class=\"p\">},<\/span><span class=\"w\"><\/span>\n<\/code><\/pre><\/div>\n\n<p>The first line (<code>javascriptShield<\/code>) displays the name of the string. JShelter\npairs the name of the string with the locations in the source code where this\ntranslation should appear. So, do not change that line.<\/p>\n<p>The second line (key <code>message<\/code>) shows the actual translation. Usually,\ntranslators are supposed to change that line.<\/p>\n<p>The third line is the description of the context of the string. So, for example,\nthe translator would learn where and how the extension uses the message. Please\ndo not translate the description.<\/p>\n<p>So, for example, in the Czech translation, the entry becomes:<\/p>\n<div class=\"highlight\"><pre><span><\/span><code><span class=\"w\">   <\/span><span class=\"nt\">&quot;javascriptShield&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span><span class=\"w\"><\/span>\n<span class=\"w\">     <\/span><span class=\"nt\">&quot;message&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;JavaScriptov\u00fd \u0161t\u00edt&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"><\/span>\n<span class=\"w\">     <\/span><span class=\"nt\">&quot;description&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;The name of the JavaScript Shield displayed at multiple places&quot;<\/span><span class=\"w\"><\/span>\n<span class=\"w\">   <\/span><span class=\"p\">},<\/span><span class=\"w\"><\/span>\n<\/code><\/pre><\/div>\n\n<p>However, some entries are more tricky, let us look at:<\/p>\n<div class=\"highlight\"><pre><span><\/span><code><span class=\"w\">    <\/span><span class=\"nt\">&quot;defaultLevelSelection&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span><span class=\"w\"><\/span>\n<span class=\"w\">        <\/span><span class=\"nt\">&quot;message&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;Default level ($levelName$)&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"><\/span>\n<span class=\"w\">        <\/span><span class=\"nt\">&quot;description&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;This text is displayed as the default level in the popup&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"><\/span>\n<span class=\"w\">        <\/span><span class=\"nt\">&quot;placeholders&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span><span class=\"w\"><\/span>\n<span class=\"w\">            <\/span><span class=\"nt\">&quot;levelName&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span><span class=\"w\"><\/span>\n<span class=\"w\">                <\/span><span class=\"nt\">&quot;content&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;$1&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"><\/span>\n<span class=\"w\">                <\/span><span class=\"nt\">&quot;description&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;Translated name of the default level used by the user&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"><\/span>\n<span class=\"w\">                <\/span><span class=\"nt\">&quot;example&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;Recommended, see the keys JSSL*Name like JSSL2Name&quot;<\/span><span class=\"w\"><\/span>\n<span class=\"w\">            <\/span><span class=\"p\">}<\/span><span class=\"w\"><\/span>\n<span class=\"w\">        <\/span><span class=\"p\">}<\/span><span class=\"w\"><\/span>\n<span class=\"w\">    <\/span><span class=\"p\">},<\/span><span class=\"w\"><\/span>\n<\/code><\/pre><\/div>\n\n<p>The <code>message<\/code> contains a variable enclosed by the dollar sign (in this case,\n<code>levelName<\/code>). Note that the <code>placeholders<\/code> section contains the specification of\nthe variable. We add substrings with special properties to the <code>placeholders<\/code>\nsection. Such substrings are either filled automatically or hold data that\nshould be translated according to the different rules than the <code>message<\/code>. Follow\nthe <code>description<\/code> to learn the rules.<\/p>\n<p>In this case, the single <code>placeholders<\/code> entry contains three keys: <code>content<\/code>,\n<code>description<\/code>, and <code>example<\/code>. The meaning of the description is the same as in\nthe original case. It explains the context of the variable. Additionally,\n<code>example<\/code> gives example strings or their description to provide the translator\nwith more information about the possible content. The <code>content<\/code> controls how the\ncontent is created. If the file does not give translators any further\ninstructions (like in this case), please do not translate the <code>placeholder<\/code>\nsection. So the translated entry becomes:<\/p>\n<div class=\"highlight\"><pre><span><\/span><code><span class=\"w\">    <\/span><span class=\"nt\">&quot;defaultLevelSelection&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span><span class=\"w\"><\/span>\n<span class=\"w\">        <\/span><span class=\"nt\">&quot;message&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;V\u00fdchoz\u00ed \u00farove\u0148 ($levelName$)&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"><\/span>\n<span class=\"w\">        <\/span><span class=\"nt\">&quot;description&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;This text is displayed as the default level in the popup&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"><\/span>\n<span class=\"w\">        <\/span><span class=\"nt\">&quot;placeholders&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span><span class=\"w\"><\/span>\n<span class=\"w\">            <\/span><span class=\"nt\">&quot;levelName&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span><span class=\"w\"><\/span>\n<span class=\"w\">                <\/span><span class=\"nt\">&quot;content&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;$1&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"><\/span>\n<span class=\"w\">                <\/span><span class=\"nt\">&quot;description&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;Translated name of the default level used by the user&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"><\/span>\n<span class=\"w\">                <\/span><span class=\"nt\">&quot;example&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;Recommended, see the keys JSSL*Name like JSSL2Name&quot;<\/span><span class=\"w\"><\/span>\n<span class=\"w\">            <\/span><span class=\"p\">}<\/span><span class=\"w\"><\/span>\n<span class=\"w\">        <\/span><span class=\"p\">}<\/span><span class=\"w\"><\/span>\n<span class=\"w\">    <\/span><span class=\"p\">},<\/span><span class=\"w\"><\/span>\n<\/code><\/pre><\/div>\n\n<p>See that only the <code>message<\/code> string changed.<\/p>\n<p>A more complicated example is:<\/p>\n<div class=\"highlight\"><pre><span><\/span><code><span class=\"w\">    <\/span><span class=\"nt\">&quot;jssgroupTimePrecisionDescription2&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span><span class=\"w\"><\/span>\n<span class=\"w\">        <\/span><span class=\"nt\">&quot;message&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;Limit the precision of high-resolution time stamps $apis$. Timestamps provided by the Geolocation API are wrapped as well if you enable \\&quot;$jssgroupPhysicalLocationGeolocation$\\&quot; protection&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"><\/span>\n<span class=\"w\">        <\/span><span class=\"nt\">&quot;description&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;Displayed at various places&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"><\/span>\n<span class=\"w\">        <\/span><span class=\"nt\">&quot;placeholders&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span><span class=\"w\"><\/span>\n<span class=\"w\">            <\/span><span class=\"nt\">&quot;apis&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span><span class=\"w\"><\/span>\n<span class=\"w\">                <\/span><span class=\"nt\">&quot;content&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;(Date, Performance, events, Gamepad API, and Web VR API)&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"><\/span>\n<span class=\"w\">                <\/span><span class=\"nt\">&quot;description&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;Keep the names of the APIs but translate the punctuation and conjunctions&quot;<\/span><span class=\"w\"><\/span>\n<span class=\"w\">            <\/span><span class=\"p\">},<\/span><span class=\"w\"><\/span>\n<span class=\"w\">            <\/span><span class=\"nt\">&quot;jssgroupPhysicalLocationGeolocation&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span><span class=\"w\"><\/span>\n<span class=\"w\">                <\/span><span class=\"nt\">&quot;content&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;$1&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"><\/span>\n<span class=\"w\">                <\/span><span class=\"nt\">&quot;description&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;Translated version of the jssgroupPhysicalLocationGeolocation string&quot;<\/span><span class=\"w\"><\/span>\n<span class=\"w\">            <\/span><span class=\"p\">}<\/span><span class=\"w\"><\/span>\n<span class=\"w\">        <\/span><span class=\"p\">}<\/span><span class=\"w\"><\/span>\n<span class=\"w\">    <\/span><span class=\"p\">},<\/span><span class=\"w\"><\/span>\n<\/code><\/pre><\/div>\n\n<p>This string holds two placeholders: <code>apis<\/code> and\n<code>jssgroupPhysicalLocationGeolocation<\/code>. Let us start from the end.\n<code>jssgroupPhysicalLocationGeolocation<\/code> should not be touched by the translator.\nThe text explains that <code>jssgroupPhysicalLocationGeolocation<\/code> will hold the\ntranslation of the string with the same name during the execution of the\nextension (\"Physical location (geolocation)\" in this case). Do not change\nanything in entries like <code>jssgroupPhysicalLocationGeolocation<\/code> during the\ntranslation.<\/p>\n<p>Then, there is the entry called <code>apis<\/code>. If you look at the content, it does not\nrefer to any automatically filled string like the previous examples. Instead,\nthe string holds the names of several APIs. The <code>desription<\/code> provides\ninstructions on how to handle the translation. In this case, we suggest keeping\nthe APIs' names in English. That way, the user can search for the APIs for more\ninformation. If you translate the API names, the user would likely be unable to\nfind any information about the API. However, different languages have different\nrules for punctuation, and the conjunction <em>and<\/em> must be translated. So the\ntranslation can look like:<\/p>\n<div class=\"highlight\"><pre><span><\/span><code><span class=\"w\">    <\/span><span class=\"nt\">&quot;jssgroupTimePrecisionDescription2&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span><span class=\"w\"><\/span>\n<span class=\"w\">        <\/span><span class=\"nt\">&quot;message&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;Omezuje p\u0159esnost \u010dasov\u00fdch zna\u010dek dostupn\u00fdch v API jako je $apis$. \u010casov\u00e9 zna\u010dky Geolocation API jsou tak\u00e9 zm\u011bn\u011bny, pokud je z\u00e1rove\u0148 aktivn\u00ed ochrana \u201e$jssgroupPhysicalLocationGeolocation$\u201c.&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"><\/span>\n<span class=\"w\">        <\/span><span class=\"nt\">&quot;description&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;Displayed at various places&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"><\/span>\n<span class=\"w\">        <\/span><span class=\"nt\">&quot;placeholders&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span><span class=\"w\"><\/span>\n<span class=\"w\">            <\/span><span class=\"nt\">&quot;apis&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span><span class=\"w\"><\/span>\n<span class=\"w\">                <\/span><span class=\"nt\">&quot;content&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;Date, Performance, Gamepad API, Web VR API a v r\u00e1mci ud\u00e1lost\u00ed&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"><\/span>\n<span class=\"w\">                <\/span><span class=\"nt\">&quot;description&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;Keep the names of the APIs but translate the punctuation and conjunctions&quot;<\/span><span class=\"w\"><\/span>\n<span class=\"w\">            <\/span><span class=\"p\">},<\/span><span class=\"w\"><\/span>\n<span class=\"w\">            <\/span><span class=\"nt\">&quot;jssgroupPhysicalLocationGeolocation&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span><span class=\"w\"><\/span>\n<span class=\"w\">                <\/span><span class=\"nt\">&quot;content&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;$1&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"><\/span>\n<span class=\"w\">                <\/span><span class=\"nt\">&quot;description&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;Translated version of the jssgroupPhysicalLocationGeolocation string&quot;<\/span><span class=\"w\"><\/span>\n<span class=\"w\">            <\/span><span class=\"p\">}<\/span><span class=\"w\"><\/span>\n<span class=\"w\">        <\/span><span class=\"p\">}<\/span><span class=\"w\"><\/span>\n<span class=\"w\">    <\/span><span class=\"p\">},<\/span><span class=\"w\"><\/span>\n<\/code><\/pre><\/div>\n\n<p>Note that the original translation used a different kind of quotation marks. The\nEnglish original uses the same character as the JSON delimiter. Hence, the\ncharacter '' precedes the quotation mark. The Czech translation does not use\nquotation marks conflicting with the JSON quotation marks, so it does not need\nthe '' sign.<\/p>\n<p>Occasionally, we extract URLs to the <code>placeholders<\/code> section to highlight that\nthey should not be translated like:<\/p>\n<div class=\"highlight\"><pre><span><\/span><code><span class=\"w\">    <\/span><span class=\"nt\">&quot;newLevelsNotRecommended&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span><span class=\"w\"><\/span>\n<span class=\"w\">        <\/span><span class=\"nt\">&quot;message&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;We do not recommend creating your own levels and changing configuration if you are concerned about browser fingerprinting. Please read &lt;a href=\\&quot;$FAQURL$\\&quot;&gt;FAQ&lt;\/a&gt; and our &lt;a href=\\&quot;$PAPERURL$\\&quot;&gt;paper&lt;\/a&gt;. By diverging from the configuration of other users, you make your re-identification easier.&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"><\/span>\n<span class=\"w\">        <\/span><span class=\"nt\">&quot;placeholders&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span><span class=\"w\"><\/span>\n<span class=\"w\">            <\/span><span class=\"nt\">&quot;faqurl&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span><span class=\"w\"><\/span>\n<span class=\"w\">                <\/span><span class=\"nt\">&quot;content&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;https:\/\/jshelter.org\/faq\/&quot;<\/span><span class=\"w\"><\/span>\n<span class=\"w\">            <\/span><span class=\"p\">},<\/span><span class=\"w\"><\/span>\n<span class=\"w\">            <\/span><span class=\"nt\">&quot;paperurl&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span><span class=\"w\"><\/span>\n<span class=\"w\">                <\/span><span class=\"nt\">&quot;content&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;https:\/\/arxiv.org\/abs\/2204.01392&quot;<\/span><span class=\"w\"><\/span>\n<span class=\"w\">            <\/span><span class=\"p\">}<\/span><span class=\"w\"><\/span>\n<span class=\"w\">        <\/span><span class=\"p\">},<\/span><span class=\"w\"><\/span>\n<span class=\"w\">        <\/span><span class=\"nt\">&quot;description&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;This message is displayed while creating a new level in options. Make sure that you keep correct HTML markup&quot;<\/span><span class=\"w\"><\/span>\n<span class=\"w\">    <\/span><span class=\"p\">},<\/span><span class=\"w\"><\/span>\n<\/code><\/pre><\/div>\n\n<p>Note that strings for translation can hold HTML markup in some cases. In that\ncase, we suggest keeping the same markup. However, if other languages handle\nsuch texts differently, translators are free to apply such rules. The\ntranslation can look like:<\/p>\n<div class=\"highlight\"><pre><span><\/span><code><span class=\"w\">    <\/span><span class=\"nt\">&quot;newLevelsNotRecommended&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span><span class=\"w\"><\/span>\n<span class=\"w\">        <\/span><span class=\"nt\">&quot;message&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;Nedoporu\u010dujeme tvorbu vlastn\u00edch \u00farovn\u00ed ochrany a zm\u011bnu konfigurace pokud pou\u017e\u00edv\u00e1te JShelter jako ochranu p\u0159ed tvorbou otisku prohl\u00ed\u017ee\u010de. P\u0159e\u010dt\u011bte si &lt;a href=\\&quot;$FAQURL$\\&quot;&gt;FAQ&lt;\/a&gt; a n\u00e1\u0161 &lt;a href=\\&quot;$PAPERURL$\\&quot;&gt;\u010dl\u00e1nek&lt;\/a&gt;. V p\u0159\u00edpad\u011b, \u017ee se va\u0161e nastaven\u00ed bude li\u0161it od nastaven\u00ed jin\u00fdch u\u017eivatel\u016f, usnad\u0148ujete va\u0161i identifikaci v budoucnu.&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"><\/span>\n<span class=\"w\">        <\/span><span class=\"nt\">&quot;placeholders&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span><span class=\"w\"><\/span>\n<span class=\"w\">            <\/span><span class=\"nt\">&quot;faqurl&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span><span class=\"w\"><\/span>\n<span class=\"w\">                <\/span><span class=\"nt\">&quot;content&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;https:\/\/jshelter.org\/faq\/&quot;<\/span><span class=\"w\"><\/span>\n<span class=\"w\">            <\/span><span class=\"p\">},<\/span><span class=\"w\"><\/span>\n<span class=\"w\">            <\/span><span class=\"nt\">&quot;paperurl&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span><span class=\"w\"><\/span>\n<span class=\"w\">                <\/span><span class=\"nt\">&quot;content&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;https:\/\/arxiv.org\/abs\/2204.01392&quot;<\/span><span class=\"w\"><\/span>\n<span class=\"w\">            <\/span><span class=\"p\">}<\/span><span class=\"w\"><\/span>\n<span class=\"w\">        <\/span><span class=\"p\">},<\/span><span class=\"w\"><\/span>\n<span class=\"w\">        <\/span><span class=\"nt\">&quot;description&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;This message is displayed while creating a new level in options. Make sure that you keep correct HTML markup&quot;<\/span><span class=\"w\"><\/span>\n<span class=\"w\">    <\/span><span class=\"p\">},<\/span><span class=\"w\"><\/span>\n<\/code><\/pre><\/div>\n\n<p>On rare occasions, we do not expect the translators to touch the <code>message<\/code> itself\nbut rather work with the placeholders. For example, see:<\/p>\n<div class=\"highlight\"><pre><span><\/span><code><span class=\"w\">    <\/span><span class=\"nt\">&quot;NBSDescription&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span><span class=\"w\"><\/span>\n<span class=\"w\">        <\/span><span class=\"nt\">&quot;message&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;&lt;p&gt;$PARAGRAPH1$&lt;\/p&gt;&lt;p&gt;$PARAGRAPH2$&lt;\/p&gt;&lt;p&gt;$PARAGRAPH3$&lt;\/p&gt;&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"><\/span>\n<span class=\"w\">        <\/span><span class=\"nt\">&quot;description&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;This is the description of NBS shown in options. Please do not modify the template string in the message but translate the paragraphs in the placeholders section. If you find it necessary, you can remove or add paragraphs.&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"><\/span>\n<span class=\"w\">        <\/span><span class=\"nt\">&quot;placeholders&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span><span class=\"w\"><\/span>\n<span class=\"w\">            <\/span><span class=\"nt\">&quot;paragraph1&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span><span class=\"w\"><\/span>\n<span class=\"w\">                <\/span><span class=\"nt\">&quot;content&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;Long text.&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"><\/span>\n<span class=\"w\">                <\/span><span class=\"nt\">&quot;description&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;Paragraph 1, please translate this text, keep the URLs or replace them to a translated version of the targets.&quot;<\/span><span class=\"w\"><\/span>\n<span class=\"w\">            <\/span><span class=\"p\">},<\/span><span class=\"w\"><\/span>\n<span class=\"w\">            <\/span><span class=\"nt\">&quot;paragraph2&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span><span class=\"w\"><\/span>\n<span class=\"w\">                <\/span><span class=\"nt\">&quot;content&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;Long text.&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"><\/span>\n<span class=\"w\">                <\/span><span class=\"nt\">&quot;description&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;Paragraph 2, please translate this text.&quot;<\/span><span class=\"w\"><\/span>\n<span class=\"w\">            <\/span><span class=\"p\">},<\/span><span class=\"w\"><\/span>\n<span class=\"w\">            <\/span><span class=\"nt\">&quot;paragraph3&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span><span class=\"w\"><\/span>\n<span class=\"w\">                <\/span><span class=\"nt\">&quot;content&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;Long text.&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"><\/span>\n<span class=\"w\">                <\/span><span class=\"nt\">&quot;description&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;Paragraph 3, please translate this text, note that &lt;i&gt;Manage exception list&lt;\/i&gt; refers to the ManageWhitelist string.&quot;<\/span><span class=\"w\"><\/span>\n<span class=\"w\">            <\/span><span class=\"p\">}<\/span><span class=\"w\"><\/span>\n<span class=\"w\">        <\/span><span class=\"p\">}<\/span><span class=\"w\"><\/span>\n<span class=\"w\">    <\/span><span class=\"p\">},<\/span><span class=\"w\"><\/span>\n<\/code><\/pre><\/div>\n\n<p>You can see that the message holds a template with an HTML markup. In this case,\nwe opted for this approach for several reasons:<\/p>\n<ul>\n<li>We expect translators to preserve the same template (<code>message<\/code>) in most, if not\nall, languages.<\/li>\n<li>Each paragraph (<code>placeholders<\/code> entry) can have specific instructions.<\/li>\n<li>The text blocks are not as long as a single block containing all text would be.<\/li>\n<\/ul>\n<p>English has a single plural form for cardinal numbers (usually a suffix <code>s<\/code> or\n<code>es<\/code> appended to the singular form) and several forms of ordinal numbers (1st,\n2nd, 3rd, 4th). <a href=\"https:\/\/www.unicode.org\/cldr\/charts\/43\/supplemental\/language_plural_rules.html\">Other languages behave\ndifferently.<\/a>\nUnfortunately, the <a href=\"\/i18n_developers\/\">Webextension APIs do not handle plurals\nwell<\/a> and JShelter could use plurals only in notifications of\nNetwork Boundary Shield, so we decided to rephrase the messages so plurals are\nnot needed:<\/p>\n<div class=\"highlight\"><pre><span><\/span><code><span class=\"w\">        <\/span><span class=\"nt\">&quot;message&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;Count of detected requests\\nby $ORIGIN$\\nthat accessed local\\nnetwork: $COUNT$.&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"><\/span>\n<\/code><\/pre><\/div>\n\n<h3 id=\"final-remarks\"><a class=\"toclink\" href=\"#final-remarks\">Final remarks<\/a><\/h3>\n<p>If you want to start translating a new language, let us know. For example, you\ncan open an issue in the <a href=\"https:\/\/pagure.io\/JShelter\/webextension\/issues\">issue\ntracker<\/a> or send us\n<a href=\"mailto:jshelter@gnu.org\">e-mail<\/a>. If you are in doubt about how to translate a\nstring or do not understand its meaning, let us know in an\n<a href=\"https:\/\/pagure.io\/JShelter\/webextension\/issues\">issue<\/a> or send us\n<a href=\"mailto:jshelter@gnu.org\">e-mail<\/a>.<\/p>","category":{"@attributes":{"term":"pt"}}},{"title":"What should a JShelter developer know about internationalization?","link":{"@attributes":{"href":"https:\/\/jshelter.org\/i18n_developers\/","rel":"alternate"}},"published":"2023-08-31T15:00:00+02:00","updated":"2025-07-10T07:01:03+02:00","author":{"name":"The JShelter team"},"id":"tag:jshelter.org,2023-08-31:\/i18n_developers\/","summary":"<p>We are working to improve the internationalization of JShelter. While the webextension API already contains <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Mozilla\/Add-ons\/WebExtensions\/API\/i18n\">APIs<\/a> for internationalization, not everything works great. This post is written for webextension developers as well as JShelter developers working with strings presented to our users. Please see our <a href=\"\/i18n\/\">other post<\/a> if you are looking \u2026<\/p>","content":"<p>We are working to improve the internationalization of JShelter. While the webextension API already contains <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Mozilla\/Add-ons\/WebExtensions\/API\/i18n\">APIs<\/a> for internationalization, not everything works great. This post is written for webextension developers as well as JShelter developers working with strings presented to our users. Please see our <a href=\"\/i18n\/\">other post<\/a> if you are looking for ways to translate JShelter.<\/p>\n<div class=\"toc\">\n<ul>\n<li><a href=\"#translating-manifest-css-files-and-js-files\">Translating manifest, CSS files, and JS files<\/a><\/li>\n<li><a href=\"#translating-html-files\">Translating HTML files<\/a><\/li>\n<li><a href=\"#language-priorities\">Language priorities<\/a><\/li>\n<li><a href=\"#handling-plurals\">Handling plurals<\/a><\/li>\n<li><a href=\"#placeholders-used-in-complex-messages\">Placeholders used in complex messages<\/a><\/li>\n<li><a href=\"#additional-reading\">Additional reading<\/a><\/li>\n<\/ul>\n<\/div>\n<h3 id=\"translating-manifest-css-files-and-js-files\"><a class=\"toclink\" href=\"#translating-manifest-css-files-and-js-files\">Translating manifest, CSS files, and JS files<\/a><\/h3>\n<p>Let us start with the simple and easy items. Adding your translated strings to the\n<a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Mozilla\/Add-ons\/WebExtensions\/Internationalization#internationalizing_manifest.json\">manifest<\/a>\nand\n<a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Mozilla\/Add-ons\/WebExtensions\/Internationalization#locale-dependent_css\">CSS<\/a>\nfiles is really simple and straightforward. For example, if you want to provide a translatable\ndescription of your extension, you would change your <code>manifest.json<\/code> to contain a line like:<\/p>\n<div class=\"highlight\"><pre><span><\/span><code><span class=\"p\">{<\/span><span class=\"w\"><\/span>\n<span class=\"w\">    <\/span><span class=\"err\">...<\/span><span class=\"w\"><\/span>\n<span class=\"w\">    <\/span><span class=\"nt\">&quot;description&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;__MSG_extensionDescription__&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"><\/span>\n<span class=\"w\">    <\/span><span class=\"err\">...<\/span><span class=\"w\"><\/span>\n<span class=\"p\">}<\/span><span class=\"w\"><\/span>\n<\/code><\/pre><\/div>\n\n<p>You add the description as <code>extensionDescription<\/code> to your <code>messages.json<\/code>:<\/p>\n<div class=\"highlight\"><pre><span><\/span><code><span class=\"w\">    <\/span><span class=\"nt\">&quot;extensionDescription&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span><span class=\"w\"><\/span>\n<span class=\"w\">        <\/span><span class=\"nt\">&quot;message&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;Extension for increasing security and privacy level of the user.&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"><\/span>\n<span class=\"w\">        <\/span><span class=\"nt\">&quot;description&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;Description of the extension.&quot;<\/span><span class=\"w\"><\/span>\n<span class=\"w\">    <\/span><span class=\"p\">},<\/span><span class=\"w\"><\/span>\n<\/code><\/pre><\/div>\n\n<p>Similarly, you can localize CSS files like:<\/p>\n<div class=\"highlight\"><pre><span><\/span><code><span class=\"nt\">input<\/span><span class=\"p\">:<\/span><span class=\"nd\">checked<\/span><span class=\"w\"> <\/span><span class=\"o\">+<\/span><span class=\"w\"> <\/span><span class=\"p\">.<\/span><span class=\"nc\">slider<\/span><span class=\"p\">:<\/span><span class=\"nd\">before<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span><span class=\"w\"><\/span>\n<span class=\"w\">    <\/span><span class=\"err\">...<\/span><span class=\"w\"><\/span>\n<span class=\"w\">    <\/span><span class=\"k\">content<\/span><span class=\"p\">:<\/span><span class=\"s2\">&quot;__MSG_ShieldOnSlider__&quot;<\/span><span class=\"p\">;<\/span><span class=\"w\"><\/span>\n<span class=\"w\">    <\/span><span class=\"err\">...<\/span><span class=\"w\"><\/span>\n<span class=\"p\">}<\/span><span class=\"w\"><\/span>\n<\/code><\/pre><\/div>\n\n<p>Afterward, you define <code>ShieldOnSlider<\/code>, and you are done.<\/p>\n<p>Translations in JavaScript files work a little bit differently, but it is easy to adapt your\nJavaScript files. You just use the <code>browser.i18n.getMessage<\/code> <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Mozilla\/Add-ons\/WebExtensions\/API\/i18n\/getMessage\">API<\/a>. You provide the key in the <code>messages.json<\/code>. This time, you can add parameters that can be utilized inside the <code>messages.json<\/code> file. For example, you can pass a string that should appear inside the translated string:<\/p>\n<div class=\"highlight\"><pre><span><\/span><code><span class=\"nx\">browser<\/span><span class=\"p\">.<\/span><span class=\"nx\">i18n<\/span><span class=\"p\">.<\/span><span class=\"nx\">getMessage<\/span><span class=\"p\">(<\/span><span class=\"s2\">&quot;defaultLevelSelection&quot;<\/span><span class=\"p\">,<\/span> <span class=\"nx\">default_level<\/span><span class=\"p\">.<\/span><span class=\"nx\">level_text<\/span><span class=\"p\">)<\/span>\n<\/code><\/pre><\/div>\n\n<p>and the <code>message.json<\/code> can contain something like:<\/p>\n<div class=\"highlight\"><pre><span><\/span><code><span class=\"w\">    <\/span><span class=\"nt\">&quot;defaultLevelSelection&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span><span class=\"w\"><\/span>\n<span class=\"w\">        <\/span><span class=\"nt\">&quot;message&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;Default level ($levelName$)&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"><\/span>\n<span class=\"w\">        <\/span><span class=\"nt\">&quot;description&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;This text is displayed as the default level in the popup&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"><\/span>\n<span class=\"w\">        <\/span><span class=\"nt\">&quot;placeholders&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span><span class=\"w\"><\/span>\n<span class=\"w\">            <\/span><span class=\"nt\">&quot;levelName&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span><span class=\"w\"><\/span>\n<span class=\"w\">                <\/span><span class=\"nt\">&quot;content&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;$1&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"><\/span>\n<span class=\"w\">                <\/span><span class=\"nt\">&quot;description&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;Translated name of the default level used by the user&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"><\/span>\n<span class=\"w\">                <\/span><span class=\"nt\">&quot;example&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;Recommended, see the keys JSSL*Name like JSSL2Name&quot;<\/span><span class=\"w\"><\/span>\n<span class=\"w\">            <\/span><span class=\"p\">}<\/span><span class=\"w\"><\/span>\n<span class=\"w\">        <\/span><span class=\"p\">}<\/span><span class=\"w\"><\/span>\n<span class=\"w\">    <\/span><span class=\"p\">},<\/span><span class=\"w\"><\/span>\n<\/code><\/pre><\/div>\n\n<p>If you like the placeholders, for example, because you read in the best practices that placeholder substitutions help specify parts that you <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Mozilla\/Add-ons\/WebExtensions\/Internationalization#hardcoded_substitution\">do not want\ntranslated<\/a>.\nYou are out of luck if you want to add parameters to your manifest or CSS files. Luckily, JShelter\ndoes not need parameters in manifest and CSS files, and such a need is rare.<\/p>\n<h3 id=\"translating-html-files\"><a class=\"toclink\" href=\"#translating-html-files\">Translating HTML files<\/a><\/h3>\n<p>Webextensions contain HTML pages. For example, you can configure <code>options_ui<\/code> or <code>default_popup<\/code> in\n<code>manifest.json<\/code>. Even so, the <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Mozilla\/Add-ons\/WebExtensions\/Internationalization\">internationalization page on MDN<\/a> is quiet about the internationalization of HTML files. Let us have a <a href=\"https:\/\/stackoverflow.com\/questions\/25467009\/internationalization-of-html-pages-for-my-google-chrome-extension\">look at what others do<\/a>.<\/p>\n<p>In essence, others add some markup to the HTML file and later process that markup in JS files. For\nexample, in JShelter, we add <code>data-localize<\/code> attribute to each element we want to translate. The attribute\nholds the key in the <code>message.json<\/code>. For example, JShelter defines:<\/p>\n<div class=\"highlight\"><pre><span><\/span><code><span class=\"p\">&lt;<\/span><span class=\"nt\">label<\/span> <span class=\"na\">for<\/span><span class=\"o\">=<\/span><span class=\"s\">&quot;nbs-switch&quot;<\/span> <span class=\"na\">data-localize<\/span><span class=\"o\">=<\/span><span class=\"s\">&quot;networkBoundaryShield&quot;<\/span><span class=\"p\">&gt;<\/span>Network Boundary Shield<span class=\"p\">&lt;\/<\/span><span class=\"nt\">label<\/span><span class=\"p\">&gt;<\/span>\n<\/code><\/pre><\/div>\n\n<p>We added a <a href=\"https:\/\/pagure.io\/JShelter\/webextension\/blob\/bff8ce9c69ca28c1952898125983429c1f7f8a32\/f\/common\/i18n_translate_dom.js\">translation file<\/a>\n<code>i18n_translate_dom.js<\/code> to all HTML pages with translatable elements. The script is\nsimple. It finds elements with the correct attributes and forwards the strings to the\n<code>browser.i18n.getMessage<\/code>\n<a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Mozilla\/Add-ons\/WebExtensions\/API\/i18n\/getMessage\">API<\/a>.<\/p>\n<p>Still, one needs to take care of special sections in the pages, like <a href=\"https:\/\/pagure.io\/JShelter\/webextension\/blob\/bff8ce9c69ca28c1952898125983429c1f7f8a32\/f\/common\/i18n_translate_dom.js#_43\">templates<\/a>.<\/p>\n<p>The lack of a standard way to cope with HTML translations means that if you go to different\nwebextension, they will likely have a similar script, but the details would be different. That is not optimal.<\/p>\n<h3 id=\"language-priorities\"><a class=\"toclink\" href=\"#language-priorities\">Language priorities<\/a><\/h3>\n<p>Webextension manifest file specifies <code>default_locale<\/code> as the default language. This language is used\nas the last resort to pick untranslated strings. Each language can have variants like <code>en_US<\/code> and\n<code>en_UK<\/code>. Translators can create <code>message.json<\/code> for variants and the base language (like <code>en<\/code>).\nBrowsers select translations based on <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Mozilla\/Add-ons\/WebExtensions\/Internationalization#localized_string_selection\">the algorithm documented on MDN<\/a>. First, they look for the variant, then for the base language, and if they are not successful, they go to the <code>default_locale<\/code>. The API returns an empty string if the default language does not contain the key.<\/p>\n<p>Unfortunately, there is no way to tweak the algorithm. For example, some languages are similar.\nCzech speakers mostly understand Slovaks and vice-versa. However, JShelter cannot tweak the\nalgorithm to look at the Czech translation if a Slovak translation is missing.<\/p>\n<h3 id=\"handling-plurals\"><a class=\"toclink\" href=\"#handling-plurals\">Handling plurals<\/a><\/h3>\n<p>Plurals in English are simple for cardinal numbers. There is just the singular and plural version.\nHowever, English has several forms for ordinal numbers, like 1st, 2nd, 3rd, 4th, or 21st. Other languages\nbehave\n<a href=\"https:\/\/www.unicode.org\/cldr\/charts\/43\/supplemental\/language_plural_rules.html\">differently<\/a>. In\nessence, almost every language has a specific handling of plurals.<\/p>\n<p>Although there is the <code>Intl.PluralRules()<\/code> <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/JavaScript\/Reference\/Global_Objects\/Intl\/PluralRules\">API<\/a> in JavaScript that is available to webextensions, there is no direct support for plural messages in the <code>browser.i18n<\/code> API.<\/p>\n<p>We considered creating several keys for the plural forms. For example, suppose that JShelter needs\nto translate a string with <code>message.json<\/code> key <code>pluralExample<\/code>. We would create a code like:<\/p>\n<div class=\"highlight\"><pre><span><\/span><code><span class=\"kd\">let<\/span> <span class=\"nx\">pluralCategory<\/span> <span class=\"o\">=<\/span> <span class=\"p\">(<\/span><span class=\"ow\">new<\/span> <span class=\"nb\">Intl<\/span><span class=\"p\">.<\/span><span class=\"nx\">PluralRules<\/span><span class=\"p\">()).<\/span><span class=\"nx\">select<\/span><span class=\"p\">(<\/span><span class=\"nx\">count<\/span><span class=\"p\">);<\/span>\n<span class=\"kd\">let<\/span> <span class=\"nx\">message<\/span> <span class=\"o\">=<\/span> <span class=\"nx\">browser<\/span><span class=\"p\">.<\/span><span class=\"nx\">i18n<\/span><span class=\"p\">.<\/span><span class=\"nx\">getMessage<\/span><span class=\"p\">(<\/span><span class=\"s2\">&quot;pluralExample&quot;<\/span> <span class=\"o\">+<\/span> <span class=\"nx\">pluralCategory<\/span><span class=\"p\">,<\/span> <span class=\"nx\">count<\/span><span class=\"p\">);<\/span>\n<\/code><\/pre><\/div>\n\n<p>At first sight, this is a straightforward solution. However, English defines only categories\n\"one\" and \"other.\" Imagine that the user uses a different locale with the category \"few.\" If JShelter supports that language and that language\ndefines <code>pluralExamplefew<\/code>, great, everything works. But imagine the key <code>pluralExamplefew<\/code> is missing for that language. The <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Mozilla\/Add-ons\/WebExtensions\/Internationalization#localized_string_selection\">string selection algorithm<\/a> would search for <code>pluralExamplefew<\/code> in English <code>message.json<\/code>. However, that key would not be defined in English. So, the string selection algorithm would yield an empty string.<\/p>\n<p>There are several solutions to the problem:<\/p>\n<ul>\n<li>Define all variants for the default locale language. We do not like that idea because it would be\n  confusing for the translators \u2014 why is there <code>pluralExamplefew<\/code> and other categories if only <code>one<\/code>\n  and <code>other<\/code> are used in English? They might attempt to remove the unused variants. Moreover, we would\n  unnecessarily overload the translators as they would need to provide the default translation even\n  if that is the same as \"other.\" Finally, translators of other languages would likely be confused\n      and add their translations that would overwhelm them as well.<\/li>\n<li>We could create code that handles the missing translations. For example, the program should check\n  that <code>message<\/code> is not empty. If empty, it would get the plural category for the English locale\n  and the English translation. We might opt for this path in the future.<\/li>\n<li>There are libraries like <a href=\"https:\/\/github.com\/joelspadin\/webextension-plural\">webextension-plural<\/a>\n  that specialize in this task. However, <code>webextension-plural<\/code> has not been developed for several years.<\/li>\n<\/ul>\n<p>As JShelter would benefit from plurals only in notifications of Network Boundary Shield that we\nmight be forced to remove in Manifest v3, we decided not to write complex code to handle all\nexceptions and not to add additional dependencies. We decided to generate messages like \"Blocked\nmessages: 5\".<\/p>\n<h3 id=\"placeholders-used-in-complex-messages\"><a class=\"toclink\" href=\"#placeholders-used-in-complex-messages\">Placeholders used in complex messages<\/a><\/h3>\n<p><a href=\"https:\/\/mozilla-l10n.github.io\/documentation\/localization\/dev_best_practices.html#splitting-and-composing-sentences\">Developers should not make assumptions about the composition of the sentences<\/a>. However, some texts need special rules.<\/p>\n<p>Consider the buttons for adding and removing exceptions for Network Boundary Shield and Fingerprint Detector. For example, the \"Enable for the selected domains\" button caption. We want to give the user a full and clear explanation; hence, the text is long. But we also want to emphasize the word \"Enable.\" So the button caption uses HTML markup: <code>&lt;strong&gt;Enable&lt;\/strong&gt; for the selected domains<\/code>.<\/p>\n<p>We decided to use placeholders to describe to translators how to handle the translation:<\/p>\n<div class=\"highlight\"><pre><span><\/span><code><span class=\"w\">    <\/span><span class=\"nt\">&quot;ButtonEnableForSelectedDomains&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span><span class=\"w\"><\/span>\n<span class=\"w\">        <\/span><span class=\"nt\">&quot;message&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;&lt;strong&gt;$ENABLE$&lt;\/strong&gt; $FORTHEDOMAIN$&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"><\/span>\n<span class=\"w\">        <\/span><span class=\"nt\">&quot;description&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;A button caption that can be used generically by JShelter, e.g., in the options; if necessary, edit the structure of the message but make sure to emphasize the enablement. Translate the placeholders.&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"><\/span>\n<span class=\"w\">        <\/span><span class=\"nt\">&quot;placeholders&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span><span class=\"w\"><\/span>\n<span class=\"w\">            <\/span><span class=\"nt\">&quot;enable&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span><span class=\"w\"><\/span>\n<span class=\"w\">                <\/span><span class=\"nt\">&quot;content&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;Enable&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"><\/span>\n<span class=\"w\">                <\/span><span class=\"nt\">&quot;description&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;Please translate&quot;<\/span><span class=\"w\"><\/span>\n<span class=\"w\">            <\/span><span class=\"p\">},<\/span><span class=\"w\"><\/span>\n<span class=\"w\">            <\/span><span class=\"nt\">&quot;forTheDomain&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span><span class=\"w\"><\/span>\n<span class=\"w\">                <\/span><span class=\"nt\">&quot;content&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;for the selected domains&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"><\/span>\n<span class=\"w\">                <\/span><span class=\"nt\">&quot;description&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;Please translate&quot;<\/span><span class=\"w\"><\/span>\n<span class=\"w\">            <\/span><span class=\"p\">}<\/span><span class=\"w\"><\/span>\n<span class=\"w\">        <\/span><span class=\"p\">}<\/span><span class=\"w\"><\/span>\n<span class=\"w\">    <\/span><span class=\"p\">},<\/span><span class=\"w\"><\/span>\n<\/code><\/pre><\/div>\n\n<p>This way, translators are free to change the structure of the message. For example, consider that the translator decides that an appropriate translation to Czech is \"Vybran\u00e9 dom\u00e9ny\n&lt;strong&gt;povol&lt;\/strong&gt;\". The word \"enable\" is translated as \"povol\". The translator can generate\ntext like:<\/p>\n<div class=\"highlight\"><pre><span><\/span><code><span class=\"w\">    <\/span><span class=\"nt\">&quot;ButtonEnableForSelectedDomains&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span><span class=\"w\"><\/span>\n<span class=\"w\">        <\/span><span class=\"nt\">&quot;message&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;$SELCTEDDOMAIN$ &lt;strong&gt;$ENABLE$&lt;\/strong&gt; &quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"><\/span>\n<span class=\"w\">        <\/span><span class=\"nt\">&quot;placeholders&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span><span class=\"w\"><\/span>\n<span class=\"w\">            <\/span><span class=\"nt\">&quot;enable&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span><span class=\"w\"><\/span>\n<span class=\"w\">                <\/span><span class=\"nt\">&quot;content&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;povol&quot;<\/span><span class=\"w\"><\/span>\n<span class=\"w\">            <\/span><span class=\"p\">},<\/span><span class=\"w\"><\/span>\n<span class=\"w\">            <\/span><span class=\"nt\">&quot;selectedDomains&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span><span class=\"w\"><\/span>\n<span class=\"w\">                <\/span><span class=\"nt\">&quot;content&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;Vybran\u00e9 dom\u00e9ny&quot;<\/span><span class=\"w\"><\/span>\n<span class=\"w\">            <\/span><span class=\"p\">}<\/span><span class=\"w\"><\/span>\n<span class=\"w\">        <\/span><span class=\"p\">}<\/span><span class=\"w\"><\/span>\n<span class=\"w\">    <\/span><span class=\"p\">},<\/span><span class=\"w\"><\/span>\n<\/code><\/pre><\/div>\n\n<p>All perfect until we decided to use <a href=\"https:\/\/hosted.weblate.org\/projects\/jshelter\/webextension\/\">Weblate<\/a> to help with the translation, for example, to notify translators about new and changed strings that need translations. According to the <a href=\"https:\/\/docs.weblate.org\/en\/latest\/formats\/webextension.html\">docs<\/a>, Weblate does support Webextension JSON. <a href=\"https:\/\/readthedocs.org\/projects\/weblate\/downloads\/pdf\/weblate-3.9.1\/\">Weblate manual<\/a> lists `placeholders as supported. The Weblate UI does not properly display placeholders. Translators <a href=\"https:\/\/github.com\/WeblateOrg\/weblate\/issues\/3398\">do not see the description<\/a> and example content of the placeholders. They cannot translate the content of the placeholder.<\/p>\n<p>In this case, we could change the definition to something like:<\/p>\n<div class=\"highlight\"><pre><span><\/span><code><span class=\"w\">    <\/span><span class=\"nt\">&quot;ButtonEnableForSelectedDomains&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span><span class=\"w\"><\/span>\n<span class=\"w\">        <\/span><span class=\"nt\">&quot;message&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;&lt;strong&gt;Enable&lt;\/strong&gt; for the selected domains&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"><\/span>\n<span class=\"w\">        <\/span><span class=\"nt\">&quot;description&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;A button caption that can be used generically by JShelter, e.g., in the options; if necessary, edit the structure of the message but make sure to emphasize the enablement.&quot;<\/span><span class=\"w\"><\/span>\n<span class=\"w\">    <\/span><span class=\"p\">},<\/span><span class=\"w\"><\/span>\n<\/code><\/pre><\/div>\n\n<p>However, we have other complex cases where dividing the message into placeholders makes sense. For\nexample, we suggest different rules for translating a part of the message, like API names.\nHence, we created <a href=\"https:\/\/pagure.io\/JShelter\/webextension\/blob\/main\/f\/tools\/i18n\">scripts<\/a> to help synchronize the strings between the repository and Weblate so that all strings can be translated in Weblate.\nA developer needs to run the synchronization scripts manually. The expected order is to first\npropagate changes from Grammarly to main (or other branch), and after that, propagate the changes from\nthat branch in the repository to Weblate.<\/p>\n<h3 id=\"additional-reading\"><a class=\"toclink\" href=\"#additional-reading\">Additional reading<\/a><\/h3>\n<p>If you are a JShelter developer or are interested in helping JShelter's internationalization development, please read <a href=\"https:\/\/mozilla-l10n.github.io\/documentation\/localization\/dev_best_practices.html\">localization best practices for developers<\/a>, <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Mozilla\/Add-ons\/WebExtensions\/Internationalization\">MDN guide on webextension internationalization<\/a>, and the <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Mozilla\/Add-ons\/WebExtensions\/API\/i18n\">i18n API documentation<\/a>.<\/p>","category":{"@attributes":{"term":"posts"}}},{"title":"What should a JShelter developer know about internationalization?","link":{"@attributes":{"href":"https:\/\/jshelter.org\/pt\/i18n_developers\/","rel":"alternate"}},"published":"2023-08-31T15:00:00+02:00","updated":"2025-07-10T07:01:03+02:00","author":{"name":"The JShelter team"},"id":"tag:jshelter.org,2023-08-31:\/pt\/i18n_developers\/","summary":"<p>We are working to improve the internationalization of JShelter. While the\nwebextension API already contains\n<a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Mozilla\/Add-ons\/WebExtensions\/API\/i18n\">APIs<\/a>\nfor internationalization, not everything works great. This post is written for\nwebextension developers as well as JShelter developers working with strings\npresented to our users. Please see our <a href=\"\/i18n\/\">other post<\/a> if you are looking \u2026<\/p>","content":"<p>We are working to improve the internationalization of JShelter. While the\nwebextension API already contains\n<a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Mozilla\/Add-ons\/WebExtensions\/API\/i18n\">APIs<\/a>\nfor internationalization, not everything works great. This post is written for\nwebextension developers as well as JShelter developers working with strings\npresented to our users. Please see our <a href=\"\/i18n\/\">other post<\/a> if you are looking\nfor ways to translate JShelter.<\/p>\n<div class=\"toc\">\n<ul>\n<li><a href=\"#translating-manifest-css-files-and-js-files\">Translating manifest, CSS files, and JS files<\/a><\/li>\n<li><a href=\"#translating-html-files\">Translating HTML files<\/a><\/li>\n<li><a href=\"#language-priorities\">Language priorities<\/a><\/li>\n<li><a href=\"#handling-plurals\">Handling plurals<\/a><\/li>\n<li><a href=\"#placeholders-used-in-complex-messages\">Placeholders used in complex messages<\/a><\/li>\n<li><a href=\"#additional-reading\">Additional reading<\/a><\/li>\n<\/ul>\n<\/div>\n<h3 id=\"translating-manifest-css-files-and-js-files\"><a class=\"toclink\" href=\"#translating-manifest-css-files-and-js-files\">Translating manifest, CSS files, and JS files<\/a><\/h3>\n<p>Let us start with the simple and easy items. Adding your translated strings to\nthe\n<a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Mozilla\/Add-ons\/WebExtensions\/Internationalization#internationalizing_manifest.json\">manifest<\/a>\nand\n<a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Mozilla\/Add-ons\/WebExtensions\/Internationalization#locale-dependent_css\">CSS<\/a>\nfiles is really simple and straightforward. For example, if you want to provide\na translatable description of your extension, you would change your\n<code>manifest.json<\/code> to contain a line like:<\/p>\n<div class=\"highlight\"><pre><span><\/span><code><span class=\"p\">{<\/span><span class=\"w\"><\/span>\n<span class=\"w\">    <\/span><span class=\"err\">...<\/span><span class=\"w\"><\/span>\n<span class=\"w\">    <\/span><span class=\"nt\">&quot;description&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;__MSG_extensionDescription__&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"><\/span>\n<span class=\"w\">    <\/span><span class=\"err\">...<\/span><span class=\"w\"><\/span>\n<span class=\"p\">}<\/span><span class=\"w\"><\/span>\n<\/code><\/pre><\/div>\n\n<p>You add the description as <code>extensionDescription<\/code> to your <code>messages.json<\/code>:<\/p>\n<div class=\"highlight\"><pre><span><\/span><code><span class=\"w\">    <\/span><span class=\"nt\">&quot;extensionDescription&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span><span class=\"w\"><\/span>\n<span class=\"w\">        <\/span><span class=\"nt\">&quot;message&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;Extension for increasing security and privacy level of the user.&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"><\/span>\n<span class=\"w\">        <\/span><span class=\"nt\">&quot;description&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;Description of the extension.&quot;<\/span><span class=\"w\"><\/span>\n<span class=\"w\">    <\/span><span class=\"p\">},<\/span><span class=\"w\"><\/span>\n<\/code><\/pre><\/div>\n\n<p>Similarly, you can localize CSS files like:<\/p>\n<div class=\"highlight\"><pre><span><\/span><code><span class=\"nt\">input<\/span><span class=\"p\">:<\/span><span class=\"nd\">checked<\/span><span class=\"w\"> <\/span><span class=\"o\">+<\/span><span class=\"w\"> <\/span><span class=\"p\">.<\/span><span class=\"nc\">slider<\/span><span class=\"p\">:<\/span><span class=\"nd\">before<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span><span class=\"w\"><\/span>\n<span class=\"w\">    <\/span><span class=\"err\">...<\/span><span class=\"w\"><\/span>\n<span class=\"w\">    <\/span><span class=\"k\">content<\/span><span class=\"p\">:<\/span><span class=\"s2\">&quot;__MSG_ShieldOnSlider__&quot;<\/span><span class=\"p\">;<\/span><span class=\"w\"><\/span>\n<span class=\"w\">    <\/span><span class=\"err\">...<\/span><span class=\"w\"><\/span>\n<span class=\"p\">}<\/span><span class=\"w\"><\/span>\n<\/code><\/pre><\/div>\n\n<p>Afterward, you define <code>ShieldOnSlider<\/code>, and you are done.<\/p>\n<p>Translations in JavaScript files work a little bit differently, but it is easy to\nadapt your JavaScript files. You just use the <code>browser.i18n.getMessage<\/code>\n<a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Mozilla\/Add-ons\/WebExtensions\/API\/i18n\/getMessage\">API<\/a>.\nYou provide the key in the <code>messages.json<\/code>. This time, you can add parameters\nthat can be utilized inside the <code>messages.json<\/code> file. For example, you can pass\na string that should appear inside the translated string:<\/p>\n<div class=\"highlight\"><pre><span><\/span><code><span class=\"nx\">browser<\/span><span class=\"p\">.<\/span><span class=\"nx\">i18n<\/span><span class=\"p\">.<\/span><span class=\"nx\">getMessage<\/span><span class=\"p\">(<\/span><span class=\"s2\">&quot;defaultLevelSelection&quot;<\/span><span class=\"p\">,<\/span> <span class=\"nx\">default_level<\/span><span class=\"p\">.<\/span><span class=\"nx\">level_text<\/span><span class=\"p\">)<\/span>\n<\/code><\/pre><\/div>\n\n<p>and the <code>message.json<\/code> can contain something like:<\/p>\n<div class=\"highlight\"><pre><span><\/span><code><span class=\"w\">    <\/span><span class=\"nt\">&quot;defaultLevelSelection&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span><span class=\"w\"><\/span>\n<span class=\"w\">        <\/span><span class=\"nt\">&quot;message&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;Default level ($levelName$)&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"><\/span>\n<span class=\"w\">        <\/span><span class=\"nt\">&quot;description&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;This text is displayed as the default level in the popup&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"><\/span>\n<span class=\"w\">        <\/span><span class=\"nt\">&quot;placeholders&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span><span class=\"w\"><\/span>\n<span class=\"w\">            <\/span><span class=\"nt\">&quot;levelName&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span><span class=\"w\"><\/span>\n<span class=\"w\">                <\/span><span class=\"nt\">&quot;content&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;$1&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"><\/span>\n<span class=\"w\">                <\/span><span class=\"nt\">&quot;description&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;Translated name of the default level used by the user&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"><\/span>\n<span class=\"w\">                <\/span><span class=\"nt\">&quot;example&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;Recommended, see the keys JSSL*Name like JSSL2Name&quot;<\/span><span class=\"w\"><\/span>\n<span class=\"w\">            <\/span><span class=\"p\">}<\/span><span class=\"w\"><\/span>\n<span class=\"w\">        <\/span><span class=\"p\">}<\/span><span class=\"w\"><\/span>\n<span class=\"w\">    <\/span><span class=\"p\">},<\/span><span class=\"w\"><\/span>\n<\/code><\/pre><\/div>\n\n<p>If you like the placeholders, for example, because you read in the best practices\nthat placeholder substitutions help specify parts that you <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Mozilla\/Add-ons\/WebExtensions\/Internationalization#hardcoded_substitution\">do not want\ntranslated<\/a>.\nYou are out of luck if you want to add parameters to your manifest or CSS files.\nLuckily, JShelter does not need parameters in manifest and CSS files, and such a\nneed is rare.<\/p>\n<h3 id=\"translating-html-files\"><a class=\"toclink\" href=\"#translating-html-files\">Translating HTML files<\/a><\/h3>\n<p>Webextensions contain HTML pages. For example, you can configure <code>options_ui<\/code> or\n<code>default_popup<\/code> in <code>manifest.json<\/code>. Even so, the <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Mozilla\/Add-ons\/WebExtensions\/Internationalization\">internationalization page on\nMDN<\/a>\nis quiet about the internationalization of HTML files. Let us have a <a href=\"https:\/\/stackoverflow.com\/questions\/25467009\/internationalization-of-html-pages-for-my-google-chrome-extension\">look at\nwhat others\ndo<\/a>.<\/p>\n<p>In essence, others add some markup to the HTML file and later process that markup\nin JS files. For example, in JShelter, we add <code>data-localize<\/code> attribute to each\nelement we want to translate. The attribute holds the key in the <code>message.json<\/code>.\nFor example, JShelter defines:<\/p>\n<div class=\"highlight\"><pre><span><\/span><code><span class=\"p\">&lt;<\/span><span class=\"nt\">label<\/span> <span class=\"na\">for<\/span><span class=\"o\">=<\/span><span class=\"s\">&quot;nbs-switch&quot;<\/span> <span class=\"na\">data-localize<\/span><span class=\"o\">=<\/span><span class=\"s\">&quot;networkBoundaryShield&quot;<\/span><span class=\"p\">&gt;<\/span>Network Boundary Shield<span class=\"p\">&lt;\/<\/span><span class=\"nt\">label<\/span><span class=\"p\">&gt;<\/span>\n<\/code><\/pre><\/div>\n\n<p>We added a <a href=\"https:\/\/pagure.io\/JShelter\/webextension\/blob\/bff8ce9c69ca28c1952898125983429c1f7f8a32\/f\/common\/i18n_translate_dom.js\">translation\nfile<\/a>\n<code>i18n_translate_dom.js<\/code> to all HTML pages with translatable elements. The script\nis simple. It finds elements with the correct attributes and forwards the\nstrings to the <code>browser.i18n.getMessage<\/code>\n<a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Mozilla\/Add-ons\/WebExtensions\/API\/i18n\/getMessage\">API<\/a>.<\/p>\n<p>Still, one needs to take care of special sections in the pages, like\n<a href=\"https:\/\/pagure.io\/JShelter\/webextension\/blob\/bff8ce9c69ca28c1952898125983429c1f7f8a32\/f\/common\/i18n_translate_dom.js#_43\">templates<\/a>.<\/p>\n<p>The lack of a standard way to cope with HTML translations means that if you go to\ndifferent webextension, they will likely have a similar script, but the details\nwould be different. That is not optimal.<\/p>\n<h3 id=\"language-priorities\"><a class=\"toclink\" href=\"#language-priorities\">Language priorities<\/a><\/h3>\n<p>Webextension manifest file specifies <code>default_locale<\/code> as the default language.\nThis language is used as the last resort to pick untranslated strings. Each\nlanguage can have variants like <code>en_US<\/code> and <code>en_UK<\/code>. Translators can create\n<code>message.json<\/code> for variants and the base language (like <code>en<\/code>). Browsers select\ntranslations based on <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Mozilla\/Add-ons\/WebExtensions\/Internationalization#localized_string_selection\">the algorithm documented on\nMDN<\/a>.\nFirst, they look for the variant, then for the base language, and if they are\nnot successful, they go to the <code>default_locale<\/code>. The API returns an empty string\nif the default language does not contain the key.<\/p>\n<p>Unfortunately, there is no way to tweak the algorithm. For example, some\nlanguages are similar. Czech speakers mostly understand Slovaks and vice-versa.\nHowever, JShelter cannot tweak the algorithm to look at the Czech translation if\na Slovak translation is missing.<\/p>\n<h3 id=\"handling-plurals\"><a class=\"toclink\" href=\"#handling-plurals\">Handling plurals<\/a><\/h3>\n<p>Plurals in English are simple for cardinal numbers. There is just the singular\nand plural version. However, English has several forms for ordinal numbers, like\n1st, 2nd, 3rd, 4th, or 21st. Other languages behave\n<a href=\"https:\/\/www.unicode.org\/cldr\/charts\/43\/supplemental\/language_plural_rules.html\">differently<\/a>.\nIn essence, almost every language has a specific handling of plurals.<\/p>\n<p>Although there is the <code>Intl.PluralRules()<\/code>\n<a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/JavaScript\/Reference\/Global_Objects\/Intl\/PluralRules\">API<\/a>\nin JavaScript that is available to webextensions, there is no direct support for\nplural messages in the <code>browser.i18n<\/code> API.<\/p>\n<p>We considered creating several keys for the plural forms. For example, suppose\nthat JShelter needs to translate a string with <code>message.json<\/code> key\n<code>pluralExample<\/code>. We would create a code like:<\/p>\n<div class=\"highlight\"><pre><span><\/span><code><span class=\"kd\">let<\/span> <span class=\"nx\">pluralCategory<\/span> <span class=\"o\">=<\/span> <span class=\"p\">(<\/span><span class=\"ow\">new<\/span> <span class=\"nb\">Intl<\/span><span class=\"p\">.<\/span><span class=\"nx\">PluralRules<\/span><span class=\"p\">()).<\/span><span class=\"nx\">select<\/span><span class=\"p\">(<\/span><span class=\"nx\">count<\/span><span class=\"p\">);<\/span>\n<span class=\"kd\">let<\/span> <span class=\"nx\">message<\/span> <span class=\"o\">=<\/span> <span class=\"nx\">browser<\/span><span class=\"p\">.<\/span><span class=\"nx\">i18n<\/span><span class=\"p\">.<\/span><span class=\"nx\">getMessage<\/span><span class=\"p\">(<\/span><span class=\"s2\">&quot;pluralExample&quot;<\/span> <span class=\"o\">+<\/span> <span class=\"nx\">pluralCategory<\/span><span class=\"p\">,<\/span> <span class=\"nx\">count<\/span><span class=\"p\">);<\/span>\n<\/code><\/pre><\/div>\n\n<p>At first sight, this is a straightforward solution. However, English defines only\ncategories \"one\" and \"other.\" Imagine that the user uses a different locale with\nthe category \"few.\" If JShelter supports that language and that language defines\n<code>pluralExamplefew<\/code>, great, everything works. But imagine the key\n<code>pluralExamplefew<\/code> is missing for that language. The <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Mozilla\/Add-ons\/WebExtensions\/Internationalization#localized_string_selection\">string selection\nalgorithm<\/a>\nwould search for <code>pluralExamplefew<\/code> in English <code>message.json<\/code>. However, that key\nwould not be defined in English. So, the string selection algorithm would yield\nan empty string.<\/p>\n<p>There are several solutions to the problem:<\/p>\n<ul>\n<li>Define all variants for the default locale language. We do not like that idea\nbecause it would be confusing for the translators \u2014 why is there\n<code>pluralExamplefew<\/code> and other categories if only <code>one<\/code> and <code>other<\/code> are used in\nEnglish? They might attempt to remove the unused variants. Moreover, we would\nunnecessarily overload the translators as they would need to provide the default\ntranslation even if that is the same as \"other.\" Finally, translators of other\nlanguages would likely be confused and add their translations that would\noverwhelm them as well.<\/li>\n<li>We could create code that handles the missing translations. For example, the\nprogram should check that <code>message<\/code> is not empty. If empty, it would get the\nplural category for the English locale and the English translation. We might opt\nfor this path in the future.<\/li>\n<li>There are libraries like\n<a href=\"https:\/\/github.com\/joelspadin\/webextension-plural\">webextension-plural<\/a> that\nspecialize in this task. However, <code>webextension-plural<\/code> has not been developed\nfor several years.<\/li>\n<\/ul>\n<p>As JShelter would benefit from plurals only in notifications of Network Boundary\nShield that we might be forced to remove in Manifest v3, we decided not to write\ncomplex code to handle all exceptions and not to add additional dependencies. We\ndecided to generate messages like \"Blocked messages: 5\".<\/p>\n<h3 id=\"placeholders-used-in-complex-messages\"><a class=\"toclink\" href=\"#placeholders-used-in-complex-messages\">Placeholders used in complex messages<\/a><\/h3>\n<p><a href=\"https:\/\/mozilla-l10n.github.io\/documentation\/localization\/dev_best_practices.html#splitting-and-composing-sentences\">Developers should not make assumptions about the composition of the\nsentences<\/a>.\nHowever, some texts need special rules.<\/p>\n<p>Consider the buttons for adding and removing exceptions for Network Boundary\nShield and Fingerprint Detector. For example, the \"Enable for the selected\ndomains\" button caption. We want to give the user a full and clear explanation;\nhence, the text is long. But we also want to emphasize the word \"Enable.\" So the\nbutton caption uses HTML markup:\n<code>&lt;strong&gt;Enable&lt;\/strong&gt; for the selected domains<\/code>.<\/p>\n<p>We decided to use placeholders to describe to translators how to handle the\ntranslation:<\/p>\n<div class=\"highlight\"><pre><span><\/span><code><span class=\"w\">    <\/span><span class=\"nt\">&quot;ButtonEnableForSelectedDomains&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span><span class=\"w\"><\/span>\n<span class=\"w\">        <\/span><span class=\"nt\">&quot;message&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;&lt;strong&gt;$ENABLE$&lt;\/strong&gt; $FORTHEDOMAIN$&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"><\/span>\n<span class=\"w\">        <\/span><span class=\"nt\">&quot;description&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;A button caption that can be used generically by JShelter, e.g., in the options; if necessary, edit the structure of the message but make sure to emphasize the enablement. Translate the placeholders.&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"><\/span>\n<span class=\"w\">        <\/span><span class=\"nt\">&quot;placeholders&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span><span class=\"w\"><\/span>\n<span class=\"w\">            <\/span><span class=\"nt\">&quot;enable&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span><span class=\"w\"><\/span>\n<span class=\"w\">                <\/span><span class=\"nt\">&quot;content&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;Enable&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"><\/span>\n<span class=\"w\">                <\/span><span class=\"nt\">&quot;description&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;Please translate&quot;<\/span><span class=\"w\"><\/span>\n<span class=\"w\">            <\/span><span class=\"p\">},<\/span><span class=\"w\"><\/span>\n<span class=\"w\">            <\/span><span class=\"nt\">&quot;forTheDomain&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span><span class=\"w\"><\/span>\n<span class=\"w\">                <\/span><span class=\"nt\">&quot;content&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;for the selected domains&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"><\/span>\n<span class=\"w\">                <\/span><span class=\"nt\">&quot;description&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;Please translate&quot;<\/span><span class=\"w\"><\/span>\n<span class=\"w\">            <\/span><span class=\"p\">}<\/span><span class=\"w\"><\/span>\n<span class=\"w\">        <\/span><span class=\"p\">}<\/span><span class=\"w\"><\/span>\n<span class=\"w\">    <\/span><span class=\"p\">},<\/span><span class=\"w\"><\/span>\n<\/code><\/pre><\/div>\n\n<p>This way, translators are free to change the structure of the message. For\nexample, consider that the translator decides that an appropriate translation to\nCzech is \"Vybran\u00e9 dom\u00e9ny &lt;strong&gt;povol&lt;\/strong&gt;\". The word \"enable\"\nis translated as \"povol\". The translator can generate text like:<\/p>\n<div class=\"highlight\"><pre><span><\/span><code><span class=\"w\">    <\/span><span class=\"nt\">&quot;ButtonEnableForSelectedDomains&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span><span class=\"w\"><\/span>\n<span class=\"w\">        <\/span><span class=\"nt\">&quot;message&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;$SELCTEDDOMAIN$ &lt;strong&gt;$ENABLE$&lt;\/strong&gt; &quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"><\/span>\n<span class=\"w\">        <\/span><span class=\"nt\">&quot;placeholders&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span><span class=\"w\"><\/span>\n<span class=\"w\">            <\/span><span class=\"nt\">&quot;enable&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span><span class=\"w\"><\/span>\n<span class=\"w\">                <\/span><span class=\"nt\">&quot;content&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;povol&quot;<\/span><span class=\"w\"><\/span>\n<span class=\"w\">            <\/span><span class=\"p\">},<\/span><span class=\"w\"><\/span>\n<span class=\"w\">            <\/span><span class=\"nt\">&quot;selectedDomains&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span><span class=\"w\"><\/span>\n<span class=\"w\">                <\/span><span class=\"nt\">&quot;content&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;Vybran\u00e9 dom\u00e9ny&quot;<\/span><span class=\"w\"><\/span>\n<span class=\"w\">            <\/span><span class=\"p\">}<\/span><span class=\"w\"><\/span>\n<span class=\"w\">        <\/span><span class=\"p\">}<\/span><span class=\"w\"><\/span>\n<span class=\"w\">    <\/span><span class=\"p\">},<\/span><span class=\"w\"><\/span>\n<\/code><\/pre><\/div>\n\n<p>All perfect until we decided to use\n<a href=\"https:\/\/hosted.weblate.org\/projects\/jshelter\/webextension\/\">Weblate<\/a> to help\nwith the translation, for example, to notify translators about new and changed\nstrings that need translations. According to the\n<a href=\"https:\/\/docs.weblate.org\/en\/latest\/formats\/webextension.html\">docs<\/a>, Weblate\ndoes support Webextension JSON. <a href=\"https:\/\/readthedocs.org\/projects\/weblate\/downloads\/pdf\/weblate-3.9.1\/\">Weblate\nmanual<\/a>\nlists\n`placeholders as supported. The Weblate UI does not properly display placeholders. Translators <a href=\"https:\/\/github.com\/WeblateOrg\/weblate\/issues\/3398\">do not see the description<\/a> and example content of the placeholders. They cannot translate the content of the placeholder.<\/p>\n<p>In this case, we could change the definition to something like:<\/p>\n<div class=\"highlight\"><pre><span><\/span><code><span class=\"w\">    <\/span><span class=\"nt\">&quot;ButtonEnableForSelectedDomains&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"p\">{<\/span><span class=\"w\"><\/span>\n<span class=\"w\">        <\/span><span class=\"nt\">&quot;message&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;&lt;strong&gt;Enable&lt;\/strong&gt; for the selected domains&quot;<\/span><span class=\"p\">,<\/span><span class=\"w\"><\/span>\n<span class=\"w\">        <\/span><span class=\"nt\">&quot;description&quot;<\/span><span class=\"p\">:<\/span><span class=\"w\"> <\/span><span class=\"s2\">&quot;A button caption that can be used generically by JShelter, e.g., in the options; if necessary, edit the structure of the message but make sure to emphasize the enablement.&quot;<\/span><span class=\"w\"><\/span>\n<span class=\"w\">    <\/span><span class=\"p\">},<\/span><span class=\"w\"><\/span>\n<\/code><\/pre><\/div>\n\n<p>However, we have other complex cases where dividing the message into placeholders\nmakes sense. For example, we suggest different rules for translating a part of\nthe message, like API names. Hence, we created\n<a href=\"https:\/\/pagure.io\/JShelter\/webextension\/blob\/main\/f\/tools\/i18n\">scripts<\/a> to\nhelp synchronize the strings between the repository and Weblate so that all\nstrings can be translated in Weblate. A developer needs to run the\nsynchronization scripts manually. The expected order is to first propagate\nchanges from Grammarly to main (or other branch), and after that, propagate the\nchanges from that branch in the repository to Weblate.<\/p>\n<h3 id=\"additional-reading\"><a class=\"toclink\" href=\"#additional-reading\">Additional reading<\/a><\/h3>\n<p>If you are a JShelter developer or are interested in helping JShelter's\ninternationalization development, please read <a href=\"https:\/\/mozilla-l10n.github.io\/documentation\/localization\/dev_best_practices.html\">localization best practices for\ndevelopers<\/a>,\n<a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Mozilla\/Add-ons\/WebExtensions\/Internationalization\">MDN guide on webextension\ninternationalization<\/a>,\nand the <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Mozilla\/Add-ons\/WebExtensions\/API\/i18n\">i18n API\ndocumentation<\/a>.<\/p>","category":{"@attributes":{"term":"pt"}}},{"title":"Final remarks on the JSR project","link":{"@attributes":{"href":"https:\/\/jshelter.org\/jsrfinal\/","rel":"alternate"}},"published":"2022-05-10T18:00:00+02:00","updated":"2024-03-26T12:54:03+01:00","author":{"name":"The JShelter team"},"id":"tag:jshelter.org,2022-05-10:\/jsrfinal\/","summary":"<p>The <a href=\"https:\/\/nlnet.nl\/project\/JSRestrictor\/\">JavaScript Restrictor<\/a> (<a href=\"\/support\/\">JSR<\/a>) project (supported by <a href=\"https:\/\/nlnet.nl\/PET\">NGI0 PET\nFund<\/a>, a fund established by <a href=\"https:\/\/nlnet.nl\/\">NLnet<\/a> with financial support\nfrom the European Commission's <a href=\"https:\/\/ngi.eu\/\">Next Generation Internet<\/a> programme, under the\naegis of DG Communications Networks, Content and Technology under grant agreement No 825310) is\nheading towards its end, and we summarize what \u2026<\/p>","content":"<p>The <a href=\"https:\/\/nlnet.nl\/project\/JSRestrictor\/\">JavaScript Restrictor<\/a> (<a href=\"\/support\/\">JSR<\/a>) project (supported by <a href=\"https:\/\/nlnet.nl\/PET\">NGI0 PET\nFund<\/a>, a fund established by <a href=\"https:\/\/nlnet.nl\/\">NLnet<\/a> with financial support\nfrom the European Commission's <a href=\"https:\/\/ngi.eu\/\">Next Generation Internet<\/a> programme, under the\naegis of DG Communications Networks, Content and Technology under grant agreement No 825310) is\nheading towards its end, and we summarize what the project gave JShelter and reiterate the chosen\napproaches.<\/p>\n<h3 id=\"what-are-the-steps-that-jshelter-takes-to-protect-users\"><a class=\"toclink\" href=\"#what-are-the-steps-that-jshelter-takes-to-protect-users\">What are the steps that JShelter takes to protect users?<\/a><\/h3>\n<p>During the NGI0 PET Fund JSR project, we investigated fingerprinting scripts and prepared wrappers,\ndeveloped Fingerprint Detector, ported anti-fingerprinting mechanisms from Brave, and improved the\nreliability of the code-injection mechanisms (the credit for the relaible injection goes to <a href=\"https:\/\/nlnet.nl\/project\/JShelter\/\">the\nparallel NGI0 PET project<\/a>). Let us go\nthrough the list in more detail:<\/p>\n<p>We reviewed literature focusing on the fingerprinting scripts and studied APIs <a href=\"https:\/\/github.com\/polcak\/jsrestrictor\/issues\/66\">declined by\nApple<\/a>. During the project, we added or improved wrappers to JavaScript Shield for:<\/p>\n<ul>\n<li>All API calls providing timestamps,<\/li>\n<li><code>window.name<\/code>,<\/li>\n<li><code>MediaDevices.prototype.enumerateDevices<\/code><\/li>\n<li>Beacon API,<\/li>\n<li>Canvas API and Web GL API,<\/li>\n<li>Audio API,<\/li>\n<li><code>Navigator.prototype.hardwareConcurrency<\/code><\/li>\n<li><code>Navigator.prototype.deviceMemory<\/code><\/li>\n<li><code>Navigator.prototype.getGamepads<\/code><\/li>\n<li><code>Navigator.prototype.activeVRDisplays<\/code><\/li>\n<li><code>Navigator.prototype.plugins<\/code><\/li>\n<li>Sensor API: Magnetometer, Accelerometer, LinearAccelerationSensor, GravitySensor, Gyroscope,<\/li>\n<li>AbsoluteOrientationSensor, RelativeOrientationSensor, and AmbientLightSensor<\/li>\n<li>BigInt typed arrays<\/li>\n<li><code>Navigator.prototype.requestMediaKeySystemAccess<\/code><\/li>\n<li><code>MediaCapabilities.prototype.encodingInfo<\/code><\/li>\n<li><code>MediaCapabilities.prototype.decodingInfo<\/code><\/li>\n<li><code>HTMLMediaElement.prototype.canPlayType<\/code><\/li>\n<li>Network Information API<\/li>\n<li>Web NFC API<\/li>\n<li>Cooperative Scheduling of Background Tasks API<\/li>\n<li>User idle detection<\/li>\n<\/ul>\n<p>Note that JShelter modifies APIs depending on the selected JavaScript Shield level and the tweaks for the visited domain.<\/p>\n<p>Previous literature modifies outputs of some APIs differently during repeated\ncalls. JShelter <a href=\"\/farbling\/\">adopted the model of Brave<\/a>. Hence, different origins read different values from the same APIs. One origin reads the same values for each repeated\nreading in the same browser session. Therefore, a fingerprint computed from these values is\ndifferent for each origin and session, making cross-site correlation harder. Note that some identifiers like an IP address\nare outside of JShelter reach. JShelter provides different readings to the same origin in a new\nbrowser session.<\/p>\n<p><a href=\"https:\/\/github.com\/plaperdr\/fprandom\">FP-Random<\/a>  modifies data inserted into the canvas. For example, if the page wants to draw with orange colour,\nFP-Random draws with a different shade of orange. We do not think that this is a good strategy for JShelter:<\/p>\n<ul>\n<li>JShelter currently does not modify what users see in canvases (i.e. the browser does not modify\n  the visual representation). JShelter only modifies read data - so each script sees different\n    values than users. In other words, while FP-Random breaks both visual representation and export\n    functions, JShelter breaks only export functions.<\/li>\n<li>Web GL offers visual effects produced by lighting, textures and other techniques. Identifying all\n  mechanisms that need to be wrapped and modified seem to be too complex and out of the reach of\n    this project.<\/li>\n<\/ul>\n<p>JShelter provides <a href=\"\/fpdetection\/\">Fingerprint Detector<\/a> (FPD), a module that heuristically detects fingerprinting and\nnotifies users with an option to block future communications. This anti-fingerprinting mechanism is\northogonal to the little lies anti-fingerprinting mechanisms in JavaScript Shield, and we advise using both mechanisms.\nLittle lies help if the fingerprinting scripts upload some readings before FPD detects the attempt\nor the user deactivates FPD for a website. FPD provides an additional safety net for fingerprinters\ntrying to nullify the little lies. FPD also provides a report page that can educate users about the\nAPIs misused for fingerprinting. We developed code for crawling websites and detecting APIs often blocked\nby uBlock Origin with the goal of gradually improving heuristics. We do not have an automatic\ngeneration of heuristics, and manual oversight is needed.<\/p>\n<p>The badge icon does not show level ID anymore. It shows the number of wrapping groups accessed by the current page as a number; the colour informs the user about the likelihood that the current page tries to fingerprint the user. The user can see more details about the activated wrappers and FPD findings in the popup window.<\/p>\n<p>JShelter depends on <a href=\"https:\/\/github.com\/hackademix\/nscl\/\">NSCL<\/a> (developed outside JSR project) that provides reliable\ncross-browser support to inject scripts before page scripts can access original APIs. That solved\nseveral long-standing bugs and allowed the extension to be used with confidence. However, NSCL does\nnot implement reliable code injections into WebWorkers, so we apply Strict WebWorker protection by\ndefault. The protection disables WebWorkers and replaces them with a polyfill.<\/p>\n<h3 id=\"issues\"><a class=\"toclink\" href=\"#issues\">Issues<\/a><\/h3>\n<p>During the final stages of the NGI0 PET Fund project JSR project, we investigated the consistency of the\nmechanisms and their real-world deployment. We closed <a href=\"https:\/\/pagure.io\/JShelter\/webextension\/issues?status=Closed&amp;milestone=NLNet+evaluation\">6 issues on Pagure<\/a> and <a href=\"https:\/\/github.com\/polcak\/jsrestrictor\/issues?q=is%3Aissue+label%3A%22NLNet+project+evaluation+phase%22+is%3Aclosed\">13 issues on Github<\/a>.<\/p>\n<p>5 investigated issues remain open on <a href=\"https:\/\/github.com\/polcak\/jsrestrictor\/issues?q=is%3Aissue+label%3A%22NLNet+project+evaluation+phase%22+is%3Aopen\">Github<\/a>. For three we need more details or cannot reproduce the issue, two refer to bugs that we are trying to fix (the issues were delegated to other JShelter developer outside the JSR project).<\/p>\n<p>We opened <a href=\"https:\/\/pagure.io\/JShelter\/webextension\/issues?status=Open&amp;tags=enhancement&amp;milestone=NLNet+evaluation&amp;close_status=\">8 issues<\/a> that cover possible enhancement of the JShelter, future directions, and possible ideas for future projects. We have additional <a href=\"https:\/\/pagure.io\/JShelter\/webextension\/issues?status=Open&amp;milestone=NLNet+evaluation&amp;close_status=\">5 opened issues on Pagure<\/a> that we investigated during the evaluation phase. <a href=\"https:\/\/pagure.io\/JShelter\/webextension\/issue\/55\">One<\/a> is a duplicate of the issue opened at Github, <a href=\"https:\/\/pagure.io\/JShelter\/webextension\/issue\/37\">another<\/a> is almost closed. We are working on all 5 issues outside the JSR project.<\/p>\n<h3 id=\"conclusion\"><a class=\"toclink\" href=\"#conclusion\">Conclusion<\/a><\/h3>\n<p>JShelter is not perfect, but we believe that we are heading in the right direction. We want to\ncontinue to work on this project. However, be patient with some issues that need time and a lot of\nwork to be solved. The JSR project and the <a href=\"https:\/\/nlnet.nl\/project\/JShelter\/\">parallel NGI0 PET Fund project<\/a> transformed the original JavaScript Restrictor extension to JShelter, suitable for everyday protection.<\/p>\n<p>If you have not noticed yet, we created a <a href=\"\/faq\/\">FAQ section<\/a> and the page describing <a href=\"\/threatmodel\/\">JShelter\nthreat model<\/a> during the final stages of the JSR project.<\/p>","category":{"@attributes":{"term":"posts"}}},{"title":"Final remarks on the JSR project","link":{"@attributes":{"href":"https:\/\/jshelter.org\/pt\/jsrfinal\/","rel":"alternate"}},"published":"2022-05-10T18:00:00+02:00","updated":"2025-07-10T07:01:03+02:00","author":{"name":"The JShelter team"},"id":"tag:jshelter.org,2022-05-10:\/pt\/jsrfinal\/","summary":"<p>The <a href=\"https:\/\/nlnet.nl\/project\/JSRestrictor\/\">JavaScript Restrictor<\/a>\n(<a href=\"\/support\/\">JSR<\/a>) project (supported by <a href=\"https:\/\/nlnet.nl\/PET\">NGI0 PET\nFund<\/a>,\na fund established by <a href=\"https:\/\/nlnet.nl\/\">NLnet<\/a> with financial support from the\nEuropean Commission's <a href=\"https:\/\/ngi.eu\/\">Next Generation Internet<\/a> programme,\nunder the aegis of DG Communications Networks, Content and Technology under\ngrant agreement No 825310) is heading towards its end, and we summarize what \u2026<\/p>","content":"<p>The <a href=\"https:\/\/nlnet.nl\/project\/JSRestrictor\/\">JavaScript Restrictor<\/a>\n(<a href=\"\/support\/\">JSR<\/a>) project (supported by <a href=\"https:\/\/nlnet.nl\/PET\">NGI0 PET\nFund<\/a>,\na fund established by <a href=\"https:\/\/nlnet.nl\/\">NLnet<\/a> with financial support from the\nEuropean Commission's <a href=\"https:\/\/ngi.eu\/\">Next Generation Internet<\/a> programme,\nunder the aegis of DG Communications Networks, Content and Technology under\ngrant agreement No 825310) is heading towards its end, and we summarize what the\nproject gave JShelter and reiterate the chosen approaches.<\/p>\n<h3 id=\"what-are-the-steps-that-jshelter-takes-to-protect-users\"><a class=\"toclink\" href=\"#what-are-the-steps-that-jshelter-takes-to-protect-users\">What are the steps that JShelter takes to protect users?<\/a><\/h3>\n<p>During the NGI0 PET Fund JSR project, we investigated fingerprinting scripts and\nprepared wrappers, developed Fingerprint Detector, ported anti-fingerprinting\nmechanisms from Brave, and improved the reliability of the code-injection\nmechanisms (the credit for the relaible injection goes to <a href=\"https:\/\/nlnet.nl\/project\/JShelter\/\">the\nparallel NGI0 PET\nproject<\/a>). Let us go through the list in\nmore detail:<\/p>\n<p>We reviewed literature focusing on the fingerprinting scripts and studied APIs\n<a href=\"https:\/\/github.com\/polcak\/jsrestrictor\/issues\/66\">declined by\nApple<\/a>. During\nthe project, we added or improved wrappers to JavaScript Shield for:<\/p>\n<ul>\n<li>All API calls providing timestamps,<\/li>\n<li><code>window.name<\/code>,<\/li>\n<li><code>MediaDevices.prototype.enumerateDevices<\/code><\/li>\n<li>Beacon API,<\/li>\n<li>Canvas API and Web GL API,<\/li>\n<li>Audio API,<\/li>\n<li><code>Navigator.prototype.hardwareConcurrency<\/code><\/li>\n<li><code>Navigator.prototype.deviceMemory<\/code><\/li>\n<li><code>Navigator.prototype.getGamepads<\/code><\/li>\n<li><code>Navigator.prototype.activeVRDisplays<\/code><\/li>\n<li><code>Navigator.prototype.plugins<\/code><\/li>\n<li>Sensor API: Magnetometer, Accelerometer, LinearAccelerationSensor,\nGravitySensor, Gyroscope,<\/li>\n<li>AbsoluteOrientationSensor, RelativeOrientationSensor, and AmbientLightSensor<\/li>\n<li>BigInt typed arrays<\/li>\n<li><code>Navigator.prototype.requestMediaKeySystemAccess<\/code><\/li>\n<li><code>MediaCapabilities.prototype.encodingInfo<\/code><\/li>\n<li><code>MediaCapabilities.prototype.decodingInfo<\/code><\/li>\n<li><code>HTMLMediaElement.prototype.canPlayType<\/code><\/li>\n<li>Network Information API<\/li>\n<li>Web NFC API<\/li>\n<li>Cooperative Scheduling of Background Tasks API<\/li>\n<li>User idle detection<\/li>\n<\/ul>\n<p>Note that JShelter modifies APIs depending on the selected JavaScript Shield\nlevel and the tweaks for the visited domain.<\/p>\n<p>Previous literature modifies outputs of some APIs differently during repeated\ncalls. JShelter <a href=\"\/farbling\/\">adopted the model of Brave<\/a>. Hence, different\norigins read different values from the same APIs. One origin reads the same\nvalues for each repeated reading in the same browser session. Therefore, a\nfingerprint computed from these values is different for each origin and session,\nmaking cross-site correlation harder. Note that some identifiers like an IP\naddress are outside of JShelter reach. JShelter provides different readings to\nthe same origin in a new browser session.<\/p>\n<p><a href=\"https:\/\/github.com\/plaperdr\/fprandom\">FP-Random<\/a> modifies data inserted into the\ncanvas. For example, if the page wants to draw with orange colour, FP-Random\ndraws with a different shade of orange. We do not think that this is a good\nstrategy for JShelter:<\/p>\n<ul>\n<li>JShelter currently does not modify what users see in canvases (i.e. the browser\ndoes not modify the visual representation). JShelter only modifies read data -\nso each script sees different values than users. In other words, while FP-Random\nbreaks both visual representation and export functions, JShelter breaks only\nexport functions.<\/li>\n<li>Web GL offers visual effects produced by lighting, textures and other\ntechniques. Identifying all mechanisms that need to be wrapped and modified\nseem to be too complex and out of the reach of this project.<\/li>\n<\/ul>\n<p>JShelter provides <a href=\"\/fpdetection\/\">Fingerprint Detector<\/a> (FPD), a module that\nheuristically detects fingerprinting and notifies users with an option to block\nfuture communications. This anti-fingerprinting mechanism is orthogonal to the\nlittle lies anti-fingerprinting mechanisms in JavaScript Shield, and we advise\nusing both mechanisms. Little lies help if the fingerprinting scripts upload\nsome readings before FPD detects the attempt or the user deactivates FPD for a\nwebsite. FPD provides an additional safety net for fingerprinters trying to\nnullify the little lies. FPD also provides a report page that can educate users\nabout the APIs misused for fingerprinting. We developed code for crawling\nwebsites and detecting APIs often blocked by uBlock Origin with the goal of\ngradually improving heuristics. We do not have an automatic generation of\nheuristics, and manual oversight is needed.<\/p>\n<p>The badge icon does not show level ID anymore. It shows the number of wrapping\ngroups accessed by the current page as a number; the colour informs the user\nabout the likelihood that the current page tries to fingerprint the user. The\nuser can see more details about the activated wrappers and FPD findings in the\npopup window.<\/p>\n<p>JShelter depends on <a href=\"https:\/\/github.com\/hackademix\/nscl\/\">NSCL<\/a> (developed\noutside JSR project) that provides reliable cross-browser support to inject\nscripts before page scripts can access original APIs. That solved several\nlong-standing bugs and allowed the extension to be used with confidence.\nHowever, NSCL does not implement reliable code injections into WebWorkers, so we\napply Strict WebWorker protection by default. The protection disables WebWorkers\nand replaces them with a polyfill.<\/p>\n<h3 id=\"issues\"><a class=\"toclink\" href=\"#issues\">Issues<\/a><\/h3>\n<p>During the final stages of the NGI0 PET Fund project JSR project, we investigated\nthe consistency of the mechanisms and their real-world deployment. We closed <a href=\"https:\/\/pagure.io\/JShelter\/webextension\/issues?\nstatus=Closed&amp;milestone=NLNet+evaluation\">6\nissues on Pagure<\/a> and <a href=\"https:\/\/github.com\/polcak\/jsrestrictor\/issues?\nq=is%3Aissue+label%3A%22NLNet+project+evaluation+phase%22+is%3Aclosed\">13 issues on\nGithub<\/a>.<\/p>\n<p>5 investigated issues remain open on\n<a href=\"https:\/\/github.com\/polcak\/jsrestrictor\/issues?\nq=is%3Aissue+label%3A%22NLNet+project+evaluation+phase%22+is%3Aopen\">Github<\/a>. For three\nwe need more details or cannot reproduce the issue, two refer to bugs that we\nare trying to fix (the issues were delegated to other JShelter developer outside\nthe JSR project).<\/p>\n<p>We opened <a href=\"https:\/\/pagure.io\/JShelter\/webextension\/issues?\nstatus=Open&amp;tags=enhancement&amp;milestone=NLNet+evaluation&amp;close_status=\">8 issues<\/a> that\ncover possible enhancement of the JShelter, future directions, and possible\nideas for future projects. We have additional <a href=\"https:\/\/pagure.io\/JShelter\/webextension\/issues?\nstatus=Open&amp;milestone=NLNet+evaluation&amp;close_status=\">5 opened issues on\nPagure<\/a> that we investigated\nduring the evaluation phase.\n<a href=\"https:\/\/pagure.io\/JShelter\/webextension\/issue\/55\">One<\/a> is a duplicate of the\nissue opened at Github,\n<a href=\"https:\/\/pagure.io\/JShelter\/webextension\/issue\/37\">another<\/a> is almost closed. We\nare working on all 5 issues outside the JSR project.<\/p>\n<h3 id=\"conclusion\"><a class=\"toclink\" href=\"#conclusion\">Conclusion<\/a><\/h3>\n<p>JShelter is not perfect, but we believe that we are heading in the right\ndirection. We want to continue to work on this project. However, be patient with\nsome issues that need time and a lot of work to be solved. The JSR project and\nthe <a href=\"https:\/\/nlnet.nl\/project\/JShelter\/\">parallel NGI0 PET Fund project<\/a>\ntransformed the original JavaScript Restrictor extension to JShelter, suitable\nfor everyday protection.<\/p>\n<p>If you have not noticed yet, we created a <a href=\"\/faq\/\">FAQ section<\/a> and the page\ndescribing <a href=\"\/threatmodel\/\">JShelter\nthreat model<\/a> during the final stages of the\nJSR project.<\/p>","category":{"@attributes":{"term":"pt"}}},{"title":"Protection against fingerprinting with Generic Sensor API","link":{"@attributes":{"href":"https:\/\/jshelter.org\/sensorapi\/","rel":"alternate"}},"published":"2022-05-06T15:34:00+02:00","updated":"2024-03-26T12:54:03+01:00","author":{"name":"The JShelter team"},"id":"tag:jshelter.org,2022-05-06:\/sensorapi\/","summary":"<p>Today devices contain <a href=\"https:\/\/www.researchgate.net\/publication\/224170986_A_survey_of_mobile_phone_sensing_IEEE_Commun_Mag\">various sensors<\/a> for reading information about the device's position, state, and environment. Such equipment is typical for mobile devices like cellphones, tablets, or laptops that often include sensors for obtaining geolocation or device orientation data. Another example is a smartwatch that could monitor the heartbeat rate of \u2026<\/p>","content":"<p>Today devices contain <a href=\"https:\/\/www.researchgate.net\/publication\/224170986_A_survey_of_mobile_phone_sensing_IEEE_Commun_Mag\">various sensors<\/a> for reading information about the device's position, state, and environment. Such equipment is typical for mobile devices like cellphones, tablets, or laptops that often include sensors for obtaining geolocation or device orientation data. Another example is a smartwatch that could monitor the heartbeat rate of the wearer, or a car with a tire pressure sensor, etc. While the benefits of having sensors are undisputed, allowing websites to access their readings represents a considerable danger.<\/p>\n<p>This post contains:<\/p>\n<div class=\"toc\">\n<ul>\n<li><a href=\"#generic-sensor-api\">Generic Sensor API<\/a><ul>\n<li><a href=\"#browser-support\">Browser Support<\/a><\/li>\n<li><a href=\"#sensor-types\">Sensor Types<\/a><\/li>\n<li><a href=\"#threats\">Threats<\/a><\/li>\n<\/ul>\n<\/li>\n<li><a href=\"#timestamps\">Timestamps<\/a><\/li>\n<li><a href=\"#global-orientation-settings\">Global Orientation Settings<\/a><\/li>\n<li><a href=\"#ambientlightsensor\">AmbientLightSensor<\/a><ul>\n<li><a href=\"#fingerprinting-with-ambientlightsensor\">Fingerprinting with AmbientLightSensor<\/a><\/li>\n<li><a href=\"#wrapping-the-ambientlightsensor-readings\">Wrapping the AmbientLightSensor readings<\/a><\/li>\n<\/ul>\n<\/li>\n<li><a href=\"#accelerometer\">Accelerometer<\/a><ul>\n<li><a href=\"#fingerprinting-with-accelerometer\">Fingerprinting with Accelerometer<\/a><\/li>\n<li><a href=\"#wrapping-the-accelerometer-readings\">Wrapping the Accelerometer readings<\/a><\/li>\n<\/ul>\n<\/li>\n<li><a href=\"#gyroscope\">Gyroscope<\/a><ul>\n<li><a href=\"#fingerprinting-with-gyroscope\">Fingerprinting with Gyroscope<\/a><\/li>\n<li><a href=\"#wrapping-the-gyroscope-readings\">Wrapping the Gyroscope Readings<\/a><\/li>\n<\/ul>\n<\/li>\n<li><a href=\"#magnetometer\">Magnetometer<\/a><ul>\n<li><a href=\"#fingerprinting-with-magnetometer\">Fingerprinting with Magnetometer<\/a><\/li>\n<li><a href=\"#wrapping-of-magnetometer-readings\">Wrapping of Magnetometer readings<\/a><\/li>\n<\/ul>\n<\/li>\n<li><a href=\"#device-orientation-sensors\">Device Orientation Sensors<\/a><ul>\n<li><a href=\"#fingerprinting-with-device-orientation-sensors\">Fingerprinting with Device Orientation Sensors<\/a><\/li>\n<li><a href=\"#wrapping-of-device-orientation-readings\">Wrapping of Device Orientation Readings<\/a><\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<\/div>\n<h3 id=\"generic-sensor-api\"><a class=\"toclink\" href=\"#generic-sensor-api\">Generic Sensor API<\/a><\/h3>\n<p>JavaScript's <a href=\"https:\/\/www.w3.org\/TR\/generic-sensor\/\">Generic sensor API<\/a> provides a unified way for accessing these sensors and reading data. The physical (hardware) sensor instances are called <strong>device sensors<\/strong>, while <strong>platform sensors<\/strong> represent interfaces over which the user agent can interact with the device sensors and read data. JavaScript represents sensors by a class hierarchy. The base class <code>Sensor<\/code> cannot be used directly but provides essential properties, event handlers, and methods for its subclasses. These represent concrete sensors like Accelerometer, Magnetometer, or Gyroscope.<\/p>\n<h4 id=\"browser-support\"><a class=\"toclink\" href=\"#browser-support\">Browser Support<\/a><\/h4>\n<p>The API is currently implemented, or partially implemented, in Chrome, Edge, and Opera browsers. For Android devices, the support exists in Chrome for Android, Opera for Android, and various Chromium-based browsers like Samsung Mobile or Kiwi Browser. The concrete support for individual classes depends on the browser type and version. Some features are considered experimental and, for now, only work when browser flags like <code>#enable-experimental-web-platform-features<\/code> or <code>#enable-generic-sensor-extra-classes<\/code> are enabled.<\/p>\n<h4 id=\"sensor-types\"><a class=\"toclink\" href=\"#sensor-types\">Sensor Types<\/a><\/h4>\n<p>Some sensors are characterized by their implementation, e.g. a <code>Gyroscope<\/code> or <code>Magnetometer<\/code>. Those are called <strong>low-level<\/strong> sensors. Sensors that are named after their readings, not their implementation, are called <strong>high-level<\/strong> sensors. For instance, the <code>GeolocationSensor<\/code> may read data from the GPS chip, Wifi networks, cellular network triangulation, or their combination. Using a combination of low-level sensor readings is called <strong>sensor fusion<\/strong>. An example is the <code>AbsoluteOrientaionSensor<\/code> that uses data from the Accelerometer, Gyroscope, and Magnetometer low-level sensors.<\/p>\n<h4 id=\"threats\"><a class=\"toclink\" href=\"#threats\">Threats<\/a><\/h4>\n<p>The risk of using Generic Sensor API calls for device fingerprinting is\nmentioned within the <a href=\"(https:\/\/www.w3.org\/TR\/2021\/CRD-generic-sensor-20210729\/#device-fingerprinting)\">W3C Candidate Recommendation Draft, 29 July 2021<\/a>.\nDocumented threats include manufacturing imperfections and differences\nthat are unique to the concrete model of the device and can be used\nfor fingerprinting. Concrete examples are discussed in the following sections dedicated\nto individual sensor classes.<\/p>\n<h3 id=\"timestamps\"><a class=\"toclink\" href=\"#timestamps\">Timestamps<\/a><\/h3>\n<p>We discovered a loophole in the <code>Sensor.timestamp<\/code> attribute. The value\ncontains the time when the last <code>Sensor.onreading<\/code> event occurred, in millisecond precision.\nWe observed that the time is not relative to the time of page context creation (like <code>performance.now<\/code>)\nbut the last boot time of the device. Exposing such information is dangerous\nas it allows to fingerprint the user easily. Not many\ndevices boot at the same time. The longer a device is running, the less likely that another device booted at the same time, and both are still running.<\/p>\n<p>The behaviour was with the Magnetometer sensor on the following devices:<\/p>\n<ul>\n<li>Samsung Galaxy S21 Ultra; Android 11, kernel 5.4.6-215566388-abG99BXXU3AUE1, Build\/RP1A.200720.012.G998BXXU3AUE1, Chrome 94.0.4606.71 and Kiwi (Chromium) 94.0.4606.56<\/li>\n<li>Xiaomi Redmi Note 5; Android 9, kernel 4.4.156-perf+, Build\/9 PKQ1.180901.001, Chrome 94.0.4606.71<\/li>\n<\/ul>\n<p>Our wrapper thus protects the device by changing the time origin to the page context\ncreation time, the timestamp should still uniquely identify the reading, i.e. two readings by the same page have a different timestamp.<\/p>\n<h3 id=\"global-orientation-settings\"><a class=\"toclink\" href=\"#global-orientation-settings\">Global Orientation Settings<\/a><\/h3>\n<p>Many sensor classes need access to the device's orientation to calculate its values\naccordingly. Readings from different sensors are thus not independent of\neach other, and relations between the sensor classes exist. We wanted even the faked\nreadings to look real and believable, and therefore JShelter uses a model of\nthe orientation that is shared between the individual wrappers.<\/p>\n<p>Let us consider a cell phone as a use case device. For all devices we examined:<\/p>\n<ul>\n<li>The <code>x<\/code> axis is oriented from the user's left to the right.<\/li>\n<li>The <code>y<\/code> axis from the bottom side of the display towards the top side.<\/li>\n<li>The <code>z<\/code> axis is perpendicular to the display; it leads from the phone's display towards the user's face.<\/li>\n<\/ul>\n<p>Similar to <a href=\"https:\/\/www.grc.nasa.gov\/www\/k-12\/airplane\/rotations.html\">Aircaft principal axes<\/a>, the rotation\nof the device is defined by three values: <code>yaw<\/code>, <code>pitch<\/code>, and <code>roll<\/code>:<\/p>\n<ul>\n<li><code>Yaw<\/code> defines rotation around the <code>z<\/code> axis. Assume a phone lying display up on a flat surface - a table, for instance. If you rotate the phone without taking any part up from the surface, only the yaw changes.<\/li>\n<li><code>Pitch<\/code> defines rotation around the <code>x<\/code> axis. Assume you want to have better visibility of the display. You may put something under the top (camera) part of the phone to lift it up a bit. This is where the pitch changes. For the phone, the surface is not horizontal anymore.<\/li>\n<li><code>Roll<\/code> defines rotation around the <code>y<\/code> axis. This is done by rotating the phone to the left and right. Assume you are holding the phone in your hand, looking at the display. If you decide to look at the buttons on the side instead, you rotate the phone, which applies the roll.<\/li>\n<\/ul>\n<p>As we observed, the yaw, pitch, and roll define the rotation of the phone on the Earth\nreference coordinate system:<\/p>\n<ul>\n<li>The <code>x<\/code> axis is oriented towards the <strong>East<\/strong><\/li>\n<li>The <code>y<\/code> axis is oriented towards the (Earth's magnetic) <strong>North<\/strong><\/li>\n<li>The <code>-z<\/code> axis is oriented toward the <strong>centre of the Earth<\/strong><\/li>\n<\/ul>\n<p>In our solution, the three values (<code>yaw<\/code>, <code>pitch<\/code>, and <code>roll<\/code>) are pseudorandomly drawn\nusing the <a href=\"https:\/\/gist.github.com\/tommyettinger\/46a874533244883189143505d203312c\">Mulberry32<\/a> PRNG\nthat is seeded with a value generated from the <code>domainHash<\/code> which ensures deterministic behaviour\nfor the given website, e.g. producing same values on different tabs.<\/p>\n<p>Future\nimprovements could also introduce movements where JShelter would change the orientation over time.<\/p>\n<p>A rotation matrix is calculated and stored within\nthe global <code>orient<\/code> variable from the values obtained. All our wrappers that need access to the device's orientation\nload it from this variable.<\/p>\n<h3 id=\"ambientlightsensor\"><a class=\"toclink\" href=\"#ambientlightsensor\">AmbientLightSensor<\/a><\/h3>\n<p>An ambient light sensor is a photodetector that senses the amount of ambient light present.\nThe motivation for integrating electronic devices is mostly to dim the screen accordingly\nand protect users' eyes.\nIn the Generic Sensor API, the sensor is implemented using the\n<code>AmbientLightSensor<\/code> class provides readings of the illuminance of the device's environment.\nThe unit of the illuminance values is <code>lux<\/code>.<\/p>\n<h4 id=\"fingerprinting-with-ambientlightsensor\"><a class=\"toclink\" href=\"#fingerprinting-with-ambientlightsensor\">Fingerprinting with AmbientLightSensor<\/a><\/h4>\n<p>While the light sensors in devices may protect users' eyes, they do not protect their privacy.\nAs the illuminance value describes the light conditions of the nearby physical surrounding of the device,\nan observer can use the illuminance together with other sensors' readings to create a unique fingerprint.\nUsing readings from the <code>AmbientLightSensor<\/code>, it is possible to\n<a href=\"https:\/\/blog.lukaszolejnik.com\/additional-security-and-privacy-risks-of-light-sensors\/\">scan the nearby environment<\/a>.\nFor instance, <a href=\"https:\/\/blog.lukaszolejnik.com\/privacy-of-ambient-light-sensors\/\">behavioral analysis<\/a>\ncan reveal information about the time of day that the user usually works,\npreferred lighting conditions, frequency of movement around different places, etc.\nBy applying the <a href=\"http:\/\/hyperphysics.phy-astr.gsu.edu\/hbase\/Forces\/isq.html\">inverse square law<\/a>,\none can also compute the distance between the device and another light-emitting object:\n<code>d = sqrt(L \/ 4 * \u03c0 * B)<\/code> where <code>L<\/code> is luminosity that is roughly constant for a light source, and\n<code>B<\/code> is brightness obtained from the sensor readings.<\/p>\n<p>It is also possible to detect the position of the user's fingers by analyzing the shadows\nthe cast.\nAn example that presents a serious danger is\n<a href=\"https:\/\/www.researchgate.net\/publication\/262380810_PIN_Skimming_Exploiting_the_Ambient-Light_Sensor_in_Mobile_Devices\">PIN Skimming<\/a>.\nIn this case, a malicious website or application exploits the sensor readings to\ndetect PINs for applications that make bank transactions, etc.\nSpreitzer et al. Using a linear discriminant analysis with a training set of 15 PINs,\neach repeated eight times, it was possible to classify more than 90 % of device PINs\ncorrectly. The chance of correctly guessing the right PIN from the set of 15 is only 6.7 %.\nIf the user watches TV and a light sensor-equipped smartphone or smartwatch is nearby,\nit can\n<a href=\"https:\/\/www.sciencedirect.com\/science\/article\/pii\/S1574119216302085\">identify concrete TV channels and on-demand videos<\/a>.<\/p>\n<p>Multiple devices allow conducting <a href=\"https:\/\/blog.lukaszolejnik.com\/privacy-of-ambient-light-sensors\/\">cross-device tracking<\/a>.\nFor instance, when someone uses a phone and a tablet in the same room, a website with access to their light sensors\ncan compare the values to distinguish whether the same person uses two separate devices.\nThe sensor can also be used for <a href=\"https:\/\/blog.lukaszolejnik.com\/privacy-of-ambient-light-sensors\/\">cross-device communication<\/a>\nwhere one device emits light by displaying content on its screen and the other\nreads the message through the sensor-measured illuminance values.<\/p>\n<h4 id=\"wrapping-the-ambientlightsensor-readings\"><a class=\"toclink\" href=\"#wrapping-the-ambientlightsensor-readings\">Wrapping the AmbientLightSensor readings<\/a><\/h4>\n<p>To eliminate possible exploitation of sensor readings, we decided\nto generate fake readings instead of modifying existing ones.\nOn examined stationary devices inside an office, the illuminance measured\nwas between 500 and 900, depending on the concrete position's light conditions.\nAll measured values were rounded to the nearest 50 illuminance value.\nThe wrapper simulates the same behaviour under non-changing light conditions.\nIn the beginning, a pseudorandom illuminance\nvalue is drawn using PRNG seeded with the domain hash - which should guarantee\nto produce the same values on multiple browser tabs on the same domain.\nAs we simulate a stationary device under constant light conditions,\nthis value remains the same for all readings.\nThe faked value is returned using a wrapped <code>illuminance<\/code> getter\nof the <code>AmbientLightSensor.prototype<\/code>.<\/p>\n<h3 id=\"accelerometer\"><a class=\"toclink\" href=\"#accelerometer\">Accelerometer<\/a><\/h3>\n<p>Accelerometers provide information about the device's acceleration\n- i.e., the rate of change of its velocity.\nThe Generic Sensor API provides access to the readings using three classes:\nthe <code>Accelerometer<\/code> sensor, the <code>LinearAccelerationSensor<\/code>, and the <code>GravitySensor<\/code>.\nAll use data from the underlying <code>accelerometer<\/code> device sensor.\nThe difference between them is whether and how the gravity acceleration is applied.\nThe <code>Accelerometer<\/code> sensor provides information about the total\nacceleration that is applied to the device.\nThe remaining two isolate the sources. The <code>LinearAccelerationSensor<\/code> ignores the influence of gravity.\nThe <code>GravitySensor<\/code> returns just the gravity acceleration.<\/p>\n<h4 id=\"fingerprinting-with-accelerometer\"><a class=\"toclink\" href=\"#fingerprinting-with-accelerometer\">Fingerprinting with Accelerometer<\/a><\/h4>\n<p>Readings from the accelerometer sensor classes represent a potential risk and need to be protected.\nA unique fingerprint <a href=\"https:\/\/link.springer.com\/chapter\/10.1007\/978-3-319-30806-7_7\">can be obtained by describing the device's vibrations<\/a>.\nUsing <a href=\"https:\/\/www.researchgate.net\/publication\/220990763_ACComplice_Location_inference_using_accelerometers_on_smartphones\">trajectory inference<\/a>\nand matching the model to map data, one may\nuse the readings from the Accelerometer to determine the device's position<\/p>\n<p>From the accelerometer readings, <a href=\"https:\/\/www.mysk.blog\/2021\/10\/24\/accelerometer-ios\/\">it is possible to infer<\/a>\nwhether the device user is lying, sitting, walking, or cycling. For walking and running,\nthe data allow calculating steps.\nAccelerometer readings can also be used to determine\n<a href=\"https:\/\/www.researchgate.net\/publication\/322835708_Classifying_Human_Walking_Patterns_using_Accelerometer_Data_from_Smartphone\">human walking patterns<\/a>.<\/p>\n<p>Similar to <code>Gyroscope<\/code>, the <code>Accelerometer<\/code> sensor is also influenced by\nvibrations from speech. Using the <a href=\"https:\/\/arxiv.org\/abs\/1907.05972\">Spearphone attack<\/a>,\nit is possible\nto perform gender classification (with accuracy over 90%) and speaker identification\n(with accuracy over 80%). Speech recognition and classification can also be done\nfrom the reading of this sensor.<\/p>\n<h4 id=\"wrapping-the-accelerometer-readings\"><a class=\"toclink\" href=\"#wrapping-the-accelerometer-readings\">Wrapping the Accelerometer readings<\/a><\/h4>\n<p>Our wrapper replaces the acceleration getters of these sensors. The goal is to\nsimulate a stationary device, possibly rotated. A rotation matrix represents the orientation\nof the device. Its values are drawn pseudorandomly\nfrom the domain hash, and are shared between all sensor wrappers to simulate\nthe same behaviour.<\/p>\n<p>The <code>GravitySensor<\/code> provides readings of gravity acceleration applied\nto the device. This is represented by a vector made of <code>x<\/code>, <code>y<\/code>, <code>z<\/code> portions.\nTo get this faked gravity vector for the device, the reference vector\n<code>[0, 0, 9.8]<\/code> is multiplied with the rotation matrix. Wrappers for the\nGravitySensor's getters return the individual portions of the fake gravity vector.<\/p>\n<p>Next, the <code>LinearAccelerationSensor<\/code> should return acceleration values without\nthe contribution of gravity. For a stationary device, it should be all zeroes.\nYet, vibrations may change values a little bit, e.g.,\nspin around <code>-0.1<\/code> to <code>+0.1<\/code>, as seen on the examined devices. Such vibrations usually do\nnot happen with every reading but only in intervals of seconds. And thus,\nJShelter pseudorandomly changes these values after a few seconds.<\/p>\n<p>Finally, the <code>Accelerometer<\/code> sensor combines the previous two. Our wrappers thus\nreturn the values from the LinearAccelerationSensor with the fake gravity\nvector portions added.<\/p>\n<p>For all three classes, we return the faked orientation values\nusing the wrapped  <code>x<\/code>, <code>y<\/code>, <code>z<\/code> component getters\nof the <code>Accelerometer.prototype<\/code>. Based on the constructor name\nthe wrapper detects which concrete class is used and thus\nwhat behaviour to simulate.<\/p>\n<h3 id=\"gyroscope\"><a class=\"toclink\" href=\"#gyroscope\">Gyroscope<\/a><\/h3>\n<p>The Gyroscope sensor provides readings of the angular velocity of the device along the <code>x<\/code>, <code>y<\/code>, <code>z<\/code> axes.\nThe class uses the underlying <code>gyroscope<\/code> device sensor.\nPhysically, classic gyroscopes used a spinning wheel or a disc with free axes of rotation.\nIn modern electronic devices,\ngyroscopes use piezoelectric or silicon transducers and <a href=\"https:\/\/www5.epsondevice.com\/en\/information\/technical_info\/gyro\/\">various mechanical structures<\/a>.<\/p>\n<h4 id=\"fingerprinting-with-gyroscope\"><a class=\"toclink\" href=\"#fingerprinting-with-gyroscope\">Fingerprinting with Gyroscope<\/a><\/h4>\n<p>Gyroscope readings can be used for <a href=\"https:\/\/crypto.stanford.edu\/gyrophone\/\">speech recognition<\/a>\nand various fingerprinting operations.\nFor stationary devices, the resonance of the unique internal or\nexternal sounds affect angular velocities affect the Gyroscope, and <a href=\"https:\/\/www.researchgate.net\/publication\/356678825_Mobile_Device_Fingerprint_Identification_Using_Gyroscope_Resonance\">allow to create a fingerprint<\/a>.\nFor moving devices, one of the options is using the Gyroscope to analyze <a href=\"https:\/\/www.ncbi.nlm.nih.gov\/pmc\/articles\/PMC7071017\/\">human walking patterns<\/a>.<\/p>\n<h4 id=\"wrapping-the-gyroscope-readings\"><a class=\"toclink\" href=\"#wrapping-the-gyroscope-readings\">Wrapping the Gyroscope Readings<\/a><\/h4>\n<p>All velocities should be zero in an ideal state for a stationary device. As we observed on the\nexamined devices, various device sensor imperfections and tiny vibrations cause the values\noscillate between <code>-0.002<\/code> and <code>0.002<\/code> on the examined devices. Our wrapper thus simulates the same behaviour.<\/p>\n<p>The changes are applied in pseudorandom intervals between <code>500 ms<\/code> to <code>2 s<\/code>. These boundaries\nwere chosen with respect to our observations from the examined devices' real sensors.\nThe actual values of change are also calculated pseudorandomly from the domain hash\nto ensure deterministic and consistent behaviour within a given domain.\nThe faked values are then returned using wrapped <code>x<\/code>, <code>y<\/code>, <code>z<\/code> getters of the <code>Gyroscope.prototype<\/code>.<\/p>\n<h3 id=\"magnetometer\"><a class=\"toclink\" href=\"#magnetometer\">Magnetometer<\/a><\/h3>\n<p>A magnetometer measures the strength and direction of the magnetic\nfield around the device. The interface offers sensor readings using three properties:\n<code>x<\/code>, <code>y<\/code>, and <code>z<\/code>. Each returns a number that describes the magnetic field\naround the particular axis. The numbers have a double precision and can be positive or negative,\ndepending on the orientation of the field.\nThe total strength of the magnetic field (<code>M<\/code>) can be calculated as <code>M = sqrt(x^2 + z^2 + y^2)<\/code>.\nThe unit is in microtesla (&micro;T).<\/p>\n<h4 id=\"fingerprinting-with-magnetometer\"><a class=\"toclink\" href=\"#fingerprinting-with-magnetometer\">Fingerprinting with Magnetometer<\/a><\/h4>\n<p>The Earth's magnetic field <a href=\"https:\/\/doi.org\/10.1111%2Fj.1365-246X.2010.04804.x\">ranges<\/a> between approximately 25 and 65 &micro;T.\nConcrete values depend on location, altitude, weather, and other factors. Yet, there are characteristics of the field for different places on Earth. The common model used for their description is the\n<a href=\"https:\/\/www.ngdc.noaa.gov\/IAGA\/vmod\/igrf.html\">International Geomagnetic Reference Field (IGRF)<\/a>.\nWhile the magnetic field changes over time, the changes are slow: There is a decrease of 5% every 100 years. Therefore, for the given latitude, longitude, and altitude, the average strength of the field should be stable. Can one determine the device's location based on the data from the Magnetometer sensor? Not exactly. The measured values are influenced by the interference with other electrical devices, isolation (buildings, vehicles), the weather, and other factors. Moreover, the field is not unique - similar fields can be measured in different places on Earth.<\/p>\n<p>What, however, can be determined is the orientation of the device. In the case of a stationary (non-moving) device, the measured values can serve as a fingerprint. As we experimentally examined, it is also possible to distinguish whether the device is moving or when its environment changes. When a person with a cellphone enters a car or an elevator, the metal barrier serves as isolation, and the strength of the field gets lower rapidly (e.g., from 60&micro;T outside to 27&micro;T inside). A cellphone lying on a case of a running computer can produce values over 100&micro;T, especially if it is near the power supply unit. Either way, for a single device at the same location in the same environment, the average strength of the magnetic field should be stable.<\/p>\n<p>Magnetometer values can be used for fingerprinting. First, Magnetometer readings can tell the attacker whether the device is moving or not.\nIn the case of a stationary device, we can make a fingerprint from the device orientation.\nAnother fingerprintable value is the average total strength of the field, which\nshould remain stable if the device is at the same position and in the same environment.<\/p>\n<p>Yet, even moving devices can be exploited. One can misuse the sensor readings\nto make a <a href=\"https:\/\/www.ieee-security.org\/TC\/SP2019\/papers\/405.pdf\">calibration fingerprint attack<\/a>\nthat infers per device factory calibration data. The researchers showed the attack to be usable and efficient\non both Android and iOS devices. A device can be identified using Magnetometer reading\n<a href=\"https:\/\/seclab.bu.edu\/papers\/magnetometer-wisec2019.pdf\">through the analysis of the bias<\/a>.\nMoreover, the device itself also produces electromagnetic emissions and can thus be identified\nusing the <a href=\"https:\/\/seclab.bu.edu\/papers\/magnetometer-wisec2019.pdf\">physical proximity attack<\/a>\nis also possible to use an external device.\nAs the underlying device sensor is also disturbed by the device's CPU activity,\nMagnetometer measurements can also be <a href=\"https:\/\/arxiv.org\/pdf\/1906.11117.pdf\">used to identify running applications or web pages<\/a>.<\/p>\n<h4 id=\"wrapping-of-magnetometer-readings\"><a class=\"toclink\" href=\"#wrapping-of-magnetometer-readings\">Wrapping of Magnetometer readings<\/a><\/h4>\n<p>JShelter wraps the <code>x<\/code>, <code>y<\/code>, <code>z<\/code> getters of the <code>Magnetometer.prototype<\/code> object to protect the device.\nInstead of using the original data, JShelter returns artificially generated values that look like actual sensor readings.<\/p>\n<p>At every moment, our wrapper stores information about the previous reading. Each rewrapped getter first checks the\n<code>timestamp<\/code> value of the sensor object. If there is no difference of the prior reading's timestamp,\nthe wrapper returns the last measured value. Otherwise, it provides a new fake reading.<\/p>\n<p>We designed our fake field generator to fulfil the following properties:<\/p>\n<ul>\n<li>The randomness of the generator should be high enough to prevent attackers from deducing the sensor values.<\/li>\n<li>Multiple scripts from the same website that access readings with the same timestamp must get the same results. And thus:<\/li>\n<li>The readings are deterministic - e.g., for a given website and time, we must be able to say what values to return.<\/li>\n<\/ul>\n<p>For every \"random\" toss-up, we use the <a href=\"https:\/\/gist.github.com\/tommyettinger\/46a874533244883189143505d203312c\">Mulberry32<\/a> PRNG\nthat is seeded with a value generated from the <code>domainHash<\/code>, which ensures deterministic\nbehaviour for the given website.\nFirst, we choose the desired total strength <code>M<\/code> of the magnetic field at our simulated location.\nThis is a pseudorandom number from <code>25<\/code> to <code>60<\/code> &micro;T, like on the Earth.<\/p>\n<p>First, we need to set the initial orientation of the axes. Our wrappers support\ntwo methods:<\/p>\n<ul>\n<li>The original implementation where the orientation of axes is drawn pseudorandomly.<\/li>\n<li>An improved version where we use the faked device rotation shared by other wrappers.\nIn this case, we start with the reference magnetic field vector that is oriented towards\nthe Earth's magnetic North and towards the centre of the Earth.\nThe vector is then multiplied with the shared faked rotation matrix. The elements\nof the resulting vector then represents the axes orientation.<\/li>\n<\/ul>\n<p>For both methods, the orientation is defined by a number from -1 to 1 for each axis:\nJShelter simulates a stationary device with a pseudorandomly drawn orientation in the current implementation.\nTherefore, we choose the orientation of the device by generating a number from <code>-1<\/code> to <code>1<\/code> for each axis.\nThose values we call <code>baseX<\/code>, <code>baseY<\/code>, and <code>baseZ<\/code>.\nBy modifying the above-shown formula, we calculate the <code>multiplier<\/code> that needs to be\napplied to the base values to get the desired field.\nThe calculation is done as follows: <code>mult = (M * sqrt(baseX^2 + baseY^2 + baseZ^2) \/ (baseX^2 + baseY^2 + baseZ^2))<\/code>\nFor axis <code>x<\/code>, the value should fluctuate around <code>baseX * mult<\/code>, etc.<\/p>\n<p>How much the field changes over time is specified by the <strong>fluctuation factor<\/strong>\nfrom <code>(0;1]<\/code> that can also be configured. For instance, <code>0.2<\/code> means that the\nmagnetic field on the axis may change from the base value by <code>20%<\/code> in both positive\nand negative ways.<\/p>\n<p>The fluctuation is simulated by using a series of <strong>sine<\/strong> functions for each axis.\nEach sine has a unique amplitude, phase shift, and period.\nThe number of sines per axis is chosen pseudorandomly based on the wrapper settings.\nFor initial experiments, we used around <code>20<\/code> to <code>30<\/code> sines for each axis.\nThe optimal configuration is in question.\nMore sines give less predictable results but also increase the computing complexity\nthat could have a negative impact on the browser's performance.<\/p>\n<p>For the given timestamp <code>t<\/code>, we make the sum of all sine values at the point <code>x=t<\/code>.\nThe result is then shifted over the y-axis by adding <code>base[X|Y|Z] * multiplier<\/code> to the sum.\nThe initial configuration of the fake field generator was chosen intuitively to resemble the\nresults of the real measurements. Currently, the generator uses <strong>at least one<\/strong> sine\nwith the period around <code>100<\/code> &micro;s (with <code>10%<\/code> tolerance), which seems to be the minimum sampling rate\nobtainable using the API on mobile devices. Then, at least one sine around <code>1 s<\/code>,\naround <code>10 s<\/code>, <code>1 minute<\/code> and <code>1 hour<\/code>. When more than <code>5<\/code> sines are used, the cycle repeats using\n<code>modulo 5<\/code> and creates a new sine with the period around <code>100<\/code> &micro;s, but this time the tolerance is <code>20%<\/code>.\nThe same follows for seconds, tens of seconds, minutes, and hours. The tolerance grows every five sines.\nFor 11+ sines, the tolerance is <code>30%<\/code> up to the maximum (currently <code>50%<\/code>).\nThe amplitude of each sine is chosen pseudorandomly based on the <strong>fluctuation factor<\/strong> described above.\nThe phase shift of each sine is also a pseudorandom number from [0;2&#960;).<\/p>\n<p>Based on the results, this heuristic returns believable values that look like actual sensor readings.\nNevertheless, the generator uses a series of constants whose optimal values\nshould be a subject of future research and improvements. Perhaps, a correlation analysis\nwith real measurements could help in the future. Figures below show the values of <code>x<\/code>, <code>y<\/code>, <code>z<\/code>, and\nthe total strength <code>M<\/code> measured within 10 minutes on a: 1) Stationary device, 2) Moving device, and\n3) Device with the fake wrapped magnetometer.<\/p>\n<p><img alt=\"Stationary device\" src=\"https:\/\/jshelter.org\/sensorapi\/device_stationary.svg\">\n<img alt=\"Moving device\" src=\"https:\/\/jshelter.org\/sensorapi\/device_moving.svg\">\n<img alt=\"Device with fake magnetometer\" src=\"https:\/\/jshelter.org\/sensorapi\/device_artificial.svg\"><\/p>\n<h3 id=\"device-orientation-sensors\"><a class=\"toclink\" href=\"#device-orientation-sensors\">Device Orientation Sensors<\/a><\/h3>\n<p>This group includes two sensor classes: <code>AbsoluteOrientationSensor<\/code> and <code>RelativeOrientationSensor<\/code>.\nBoth describe the physical orientation of the device, and both require access\nto the <code>accelerometer<\/code> and <code>gyroscope<\/code> device sensors.\nThe difference between the two classes is what they consider as a reference coordinate system\n- i.e., what is the real orientation of a non-rotated device.\nFor this purpose, the <code>AbsoluteOrientationSensor<\/code> uses the Earth's reference coordinate\nsystem. And thus, it also requires access to the <code>magnetometer<\/code> device sensor, simply\nto know where the North is.\nThe <code>RelativeOrientationSensor<\/code> does not require this information as the cardinal directions\nare not used. Yet, the sensor still needs to some coordinate system to calculate with -\ni.e., physical orientation of the device that is considered as reference.\nFor this purpose, it may use the orientation from the moment the sensor instance is created.\nWhen the sensor is initialized, it creates a new coordinate system, and the device can be\nconsidered non-rotated. Any rotations from this point are done in relation to this newly created\ncoordinate system.\nReadings are represented by the <code>OrientationSensor.quaternion<\/code> made of <code>x<\/code>, <code>y<\/code>, <code>z<\/code>, and <code>w<\/code>\ncomponents that describe the orientation of the device.<\/p>\n<h4 id=\"fingerprinting-with-device-orientation-sensors\"><a class=\"toclink\" href=\"#fingerprinting-with-device-orientation-sensors\">Fingerprinting with Device Orientation Sensors<\/a><\/h4>\n<p>Device orientation sensors can be easily used for fingerprinting. As it highly\nunlikely that two devices visiting the same site will be oriented exactly\nthe same, the orientation itself can serve as a fingerprint.<\/p>\n<p>As the device orientation sensor use data from both the <code>accelerometer<\/code> and the <code>gyroscope<\/code>\ndevice sensors, determining the location using <a href=\"https:\/\/www.researchgate.net\/publication\/220990763_ACComplice_Location_inference_using_accelerometers_on_smartphones\">trajectory inference<\/a>\nor determining <a href=\"https:\/\/www.researchgate.net\/publication\/322835708_Classifying_Human_Walking_Patterns_using_Accelerometer_Data_from_Smartphone\">human walking patterns<\/a>.\ncould be even easier than with bare <code>Accelerometer<\/code> class.<\/p>\n<h4 id=\"wrapping-of-device-orientation-readings\"><a class=\"toclink\" href=\"#wrapping-of-device-orientation-readings\">Wrapping of Device Orientation Readings<\/a><\/h4>\n<p>The <code>AbsoluteOrientationSensor<\/code> returns a quaternion describing the physical\norientation of the device in relation to the Earth's reference coordinate\nsystem. As discussed above, the faked orientation of the device is drawn pseudorandomly using\ndomain hash as a seed and sored inside a global variable called <code>orient<\/code>\nthat is accessible to all wrappers.\nWith each reading, it loads the \"orient\"  's contents,\nconverts the rotation matrix to a quaternion that is returned by the wrapped\ngetter.\nThis design makes the outputs realistic and in accordance with other sensors'\nreading. The implemenation also supports possible changes of orientation.<\/p>\n<p>The <code>RelativeOrientationSensor<\/code> also describes the orientation, but without\nregard to the Earth's reference coordinate system. We suppose the coordinate\nsystem is chosen at the beginning of the sensor instance creation.\nAs we observed, no matter how the device is oriented, there is always a slight\ndifference from the AbsoluteOrientationSensor's in at least one axis.\nWhen the device moves, both sensors' readings change. But their difference\nshould always be constant. And thus, we pseudorandomly generate rotation deviations\nfrom the Earth's reference coordinate system. The deviations are between\n<code>0<\/code> and <code>\u03c0\/2<\/code>. For each reading, we\ntake the values from the fake AbsoluteOrientationSensor and modify them\nby the constant deviation.<\/p>\n<p>For both sensor classes, we return the faked orientation values\nusing the wrapped  <code>x<\/code>, <code>y<\/code>, <code>z<\/code>, and <code>w<\/code> quaternion component getters\nof the <code>OrientationSensor.prototype<\/code>. Based on the constructor name\nthe wrapper detects whether it should simulate the absolute or\nrelative orientation sensor's behaviour.<\/p>","category":{"@attributes":{"term":"posts"}}},{"title":"Protection against fingerprinting with Generic Sensor API","link":{"@attributes":{"href":"https:\/\/jshelter.org\/pt\/sensorapi\/","rel":"alternate"}},"published":"2022-05-06T15:34:00+02:00","updated":"2025-07-10T07:01:03+02:00","author":{"name":"The JShelter team"},"id":"tag:jshelter.org,2022-05-06:\/pt\/sensorapi\/","summary":"<p>Today devices contain <a href=\"https:\/\/www.researchgate.net\/publication\/224170986_A_survey_of_mobile_phone_sensing_IEEE_Commun_Mag\">various\nsensors<\/a>\nfor reading information about the device's position, state, and environment.\nSuch equipment is typical for mobile devices like cellphones, tablets, or\nlaptops that often include sensors for obtaining geolocation or device\norientation data. Another example is a smartwatch that could monitor the\nheartbeat rate of \u2026<\/p>","content":"<p>Today devices contain <a href=\"https:\/\/www.researchgate.net\/publication\/224170986_A_survey_of_mobile_phone_sensing_IEEE_Commun_Mag\">various\nsensors<\/a>\nfor reading information about the device's position, state, and environment.\nSuch equipment is typical for mobile devices like cellphones, tablets, or\nlaptops that often include sensors for obtaining geolocation or device\norientation data. Another example is a smartwatch that could monitor the\nheartbeat rate of the wearer, or a car with a tire pressure sensor, etc. While\nthe benefits of having sensors are undisputed, allowing websites to access their\nreadings represents a considerable danger.<\/p>\n<p>This post contains:<\/p>\n<div class=\"toc\">\n<ul>\n<li><a href=\"#generic-sensor-api\">Generic Sensor API<\/a><ul>\n<li><a href=\"#browser-support\">Browser Support<\/a><\/li>\n<li><a href=\"#sensor-types\">Sensor Types<\/a><\/li>\n<li><a href=\"#threats\">Threats<\/a><\/li>\n<\/ul>\n<\/li>\n<li><a href=\"#timestamps\">Timestamps<\/a><\/li>\n<li><a href=\"#global-orientation-settings\">Global Orientation Settings<\/a><\/li>\n<li><a href=\"#ambientlightsensor\">AmbientLightSensor<\/a><ul>\n<li><a href=\"#fingerprinting-with-ambientlightsensor\">Fingerprinting with AmbientLightSensor<\/a><\/li>\n<li><a href=\"#wrapping-the-ambientlightsensor-readings\">Wrapping the AmbientLightSensor readings<\/a><\/li>\n<\/ul>\n<\/li>\n<li><a href=\"#accelerometer\">Accelerometer<\/a><ul>\n<li><a href=\"#fingerprinting-with-accelerometer\">Fingerprinting with Accelerometer<\/a><\/li>\n<li><a href=\"#wrapping-the-accelerometer-readings\">Wrapping the Accelerometer readings<\/a><\/li>\n<\/ul>\n<\/li>\n<li><a href=\"#gyroscope\">Gyroscope<\/a><ul>\n<li><a href=\"#fingerprinting-with-gyroscope\">Fingerprinting with Gyroscope<\/a><\/li>\n<li><a href=\"#wrapping-the-gyroscope-readings\">Wrapping the Gyroscope Readings<\/a><\/li>\n<\/ul>\n<\/li>\n<li><a href=\"#magnetometer\">Magnetometer<\/a><ul>\n<li><a href=\"#fingerprinting-with-magnetometer\">Fingerprinting with Magnetometer<\/a><\/li>\n<li><a href=\"#wrapping-of-magnetometer-readings\">Wrapping of Magnetometer readings<\/a><\/li>\n<\/ul>\n<\/li>\n<li><a href=\"#device-orientation-sensors\">Device Orientation Sensors<\/a><ul>\n<li><a href=\"#fingerprinting-with-device-orientation-sensors\">Fingerprinting with Device Orientation Sensors<\/a><\/li>\n<li><a href=\"#wrapping-of-device-orientation-readings\">Wrapping of Device Orientation Readings<\/a><\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<\/div>\n<h3 id=\"generic-sensor-api\"><a class=\"toclink\" href=\"#generic-sensor-api\">Generic Sensor API<\/a><\/h3>\n<p>JavaScript's <a href=\"https:\/\/www.w3.org\/TR\/generic-sensor\/\">Generic sensor API<\/a> provides\na unified way for accessing these sensors and reading data. The physical\n(hardware) sensor instances are called <strong>device sensors<\/strong>, while <strong>platform\nsensors<\/strong> represent interfaces over which the user agent can interact with the\ndevice sensors and read data. JavaScript represents sensors by a class\nhierarchy. The base class <code>Sensor<\/code> cannot be used directly but provides\nessential properties, event handlers, and methods for its subclasses. These\nrepresent concrete sensors like Accelerometer, Magnetometer, or Gyroscope.<\/p>\n<h4 id=\"browser-support\"><a class=\"toclink\" href=\"#browser-support\">Browser Support<\/a><\/h4>\n<p>The API is currently implemented, or partially implemented, in Chrome, Edge, and\nOpera browsers. For Android devices, the support exists in Chrome for Android,\nOpera for Android, and various Chromium-based browsers like Samsung Mobile or\nKiwi Browser. The concrete support for individual classes depends on the browser\ntype and version. Some features are considered experimental and, for now, only\nwork when browser flags like <code>#enable-experimental-web-platform-features<\/code> or\n<code>#enable-generic-sensor-extra-classes<\/code> are enabled.<\/p>\n<h4 id=\"sensor-types\"><a class=\"toclink\" href=\"#sensor-types\">Sensor Types<\/a><\/h4>\n<p>Some sensors are characterized by their implementation, e.g. a <code>Gyroscope<\/code> or\n<code>Magnetometer<\/code>. Those are called <strong>low-level<\/strong> sensors. Sensors that are named\nafter their readings, not their implementation, are called <strong>high-level<\/strong>\nsensors. For instance, the <code>GeolocationSensor<\/code> may read data from the GPS chip,\nWifi networks, cellular network triangulation, or their combination. Using a\ncombination of low-level sensor readings is called <strong>sensor fusion<\/strong>. An example\nis the <code>AbsoluteOrientaionSensor<\/code> that uses data from the Accelerometer,\nGyroscope, and Magnetometer low-level sensors.<\/p>\n<h4 id=\"threats\"><a class=\"toclink\" href=\"#threats\">Threats<\/a><\/h4>\n<p>The risk of using Generic Sensor API calls for device fingerprinting is mentioned\nwithin the <a href=\"(https:\/\/www.w3.org\/TR\/2021\/CRD-generic-sensor-20210729\/#device-fingerprinting)\">W3C Candidate Recommendation Draft, 29 July\n2021<\/a>.\nDocumented threats include manufacturing imperfections and differences that are\nunique to the concrete model of the device and can be used for fingerprinting.\nConcrete examples are discussed in the following sections dedicated to\nindividual sensor classes.<\/p>\n<h3 id=\"timestamps\"><a class=\"toclink\" href=\"#timestamps\">Timestamps<\/a><\/h3>\n<p>We discovered a loophole in the <code>Sensor.timestamp<\/code> attribute. The value contains\nthe time when the last <code>Sensor.onreading<\/code> event occurred, in millisecond\nprecision. We observed that the time is not relative to the time of page context\ncreation (like <code>performance.now<\/code>) but the last boot time of the device. Exposing\nsuch information is dangerous as it allows to fingerprint the user easily. Not\nmany devices boot at the same time. The longer a device is running, the less\nlikely that another device booted at the same time, and both are still running.<\/p>\n<p>The behaviour was with the Magnetometer sensor on the following devices:<\/p>\n<ul>\n<li>Samsung Galaxy S21 Ultra; Android 11, kernel 5.4.6-215566388-abG99BXXU3AUE1,\nBuild\/RP1A.200720.012.G998BXXU3AUE1, Chrome 94.0.4606.71 and Kiwi (Chromium)\n94.0.4606.56<\/li>\n<li>Xiaomi Redmi Note 5; Android 9, kernel 4.4.156-perf+, Build\/9 PKQ1.180901.001,\nChrome 94.0.4606.71<\/li>\n<\/ul>\n<p>Our wrapper thus protects the device by changing the time origin to the page\ncontext creation time, the timestamp should still uniquely identify the reading,\ni.e. two readings by the same page have a different timestamp.<\/p>\n<h3 id=\"global-orientation-settings\"><a class=\"toclink\" href=\"#global-orientation-settings\">Global Orientation Settings<\/a><\/h3>\n<p>Many sensor classes need access to the device's orientation to calculate its\nvalues accordingly. Readings from different sensors are thus not independent of\neach other, and relations between the sensor classes exist. We wanted even the\nfaked readings to look real and believable, and therefore JShelter uses a model\nof the orientation that is shared between the individual wrappers.<\/p>\n<p>Let us consider a cell phone as a use case device. For all devices we examined:<\/p>\n<ul>\n<li>The <code>x<\/code> axis is oriented from the user's left to the right.<\/li>\n<li>The <code>y<\/code> axis from the bottom side of the display towards the top side.<\/li>\n<li>The <code>z<\/code> axis is perpendicular to the display; it leads from the phone's display\ntowards the user's face.<\/li>\n<\/ul>\n<p>Similar to <a href=\"https:\/\/www.grc.nasa.gov\/www\/k-12\/airplane\/rotations.html\">Aircaft principal\naxes<\/a>, the rotation\nof the device is defined by three values: <code>yaw<\/code>, <code>pitch<\/code>, and <code>roll<\/code>:<\/p>\n<ul>\n<li><code>Yaw<\/code> defines rotation around the <code>z<\/code> axis. Assume a phone lying display up on\na flat surface - a table, for instance. If you rotate the phone without taking\nany part up from the surface, only the yaw changes.<\/li>\n<li><code>Pitch<\/code> defines rotation around the <code>x<\/code> axis. Assume you want to have better\nvisibility of the display. You may put something under the top (camera) part\nof the phone to lift it up a bit. This is where the pitch changes. For the\nphone, the surface is not horizontal anymore.<\/li>\n<li><code>Roll<\/code> defines rotation around the <code>y<\/code> axis. This is done by rotating the phone\nto the left and right. Assume you are holding the phone in your hand, looking\nat the display. If you decide to look at the buttons on the side instead, you\nrotate the phone, which applies the roll.<\/li>\n<\/ul>\n<p>As we observed, the yaw, pitch, and roll define the rotation of the phone on the\nEarth reference coordinate system:<\/p>\n<ul>\n<li>The <code>x<\/code> axis is oriented towards the <strong>East<\/strong><\/li>\n<li>The <code>y<\/code> axis is oriented towards the (Earth's magnetic) <strong>North<\/strong><\/li>\n<li>The <code>-z<\/code> axis is oriented toward the <strong>centre of the Earth<\/strong><\/li>\n<\/ul>\n<p>In our solution, the three values (<code>yaw<\/code>, <code>pitch<\/code>, and <code>roll<\/code>) are pseudorandomly\ndrawn using the\n<a href=\"https:\/\/gist.github.com\/tommyettinger\/46a874533244883189143505d203312c\">Mulberry32<\/a>\nPRNG that is seeded with a value generated from the <code>domainHash<\/code> which ensures\ndeterministic behaviour for the given website, e.g. producing same values on\ndifferent tabs.<\/p>\n<p>Future improvements could also introduce movements where JShelter would change\nthe orientation over time.<\/p>\n<p>A rotation matrix is calculated and stored within the global <code>orient<\/code> variable\nfrom the values obtained. All our wrappers that need access to the device's\norientation load it from this variable.<\/p>\n<h3 id=\"ambientlightsensor\"><a class=\"toclink\" href=\"#ambientlightsensor\">AmbientLightSensor<\/a><\/h3>\n<p>An ambient light sensor is a photodetector that senses the amount of ambient\nlight present. The motivation for integrating electronic devices is mostly to\ndim the screen accordingly and protect users' eyes. In the Generic Sensor API,\nthe sensor is implemented using the <code>AmbientLightSensor<\/code> class provides readings\nof the illuminance of the device's environment. The unit of the illuminance\nvalues is <code>lux<\/code>.<\/p>\n<h4 id=\"fingerprinting-with-ambientlightsensor\"><a class=\"toclink\" href=\"#fingerprinting-with-ambientlightsensor\">Fingerprinting with AmbientLightSensor<\/a><\/h4>\n<p>While the light sensors in devices may protect users' eyes, they do not protect\ntheir privacy. As the illuminance value describes the light conditions of the\nnearby physical surrounding of the device, an observer can use the illuminance\ntogether with other sensors' readings to create a unique fingerprint. Using\nreadings from the <code>AmbientLightSensor<\/code>, it is possible to <a href=\"https:\/\/blog.lukaszolejnik.com\/additional-security-and-privacy-risks-of-light-sensors\/\">scan the nearby\nenvironment<\/a>.\nFor instance, <a href=\"https:\/\/blog.lukaszolejnik.com\/privacy-of-ambient-light-sensors\/\">behavioral\nanalysis<\/a> can\nreveal information about the time of day that the user usually works, preferred\nlighting conditions, frequency of movement around different places, etc. By\napplying the <a href=\"http:\/\/hyperphysics.phy-astr.gsu.edu\/hbase\/Forces\/isq.html\">inverse square\nlaw<\/a>, one can also\ncompute the distance between the device and another light-emitting object:\n<code>d = sqrt(L \/ 4 * \u03c0 * B)<\/code> where <code>L<\/code> is luminosity that is roughly constant for a\nlight source, and <code>B<\/code> is brightness obtained from the sensor readings.<\/p>\n<p>It is also possible to detect the position of the user's fingers by analyzing the\nshadows the cast. An example that presents a serious danger is <a href=\"https:\/\/www.researchgate.net\/publication\/262380810_PIN_Skimming_Exploiting_the_Ambient-Light_Sensor_in_Mobile_Devices\">PIN\nSkimming<\/a>.\nIn this case, a malicious website or application exploits the sensor readings to\ndetect PINs for applications that make bank transactions, etc. Spreitzer et al.\nUsing a linear discriminant analysis with a training set of 15 PINs, each\nrepeated eight times, it was possible to classify more than 90 % of device PINs\ncorrectly. The chance of correctly guessing the right PIN from the set of 15 is\nonly 6.7 %. If the user watches TV and a light sensor-equipped smartphone or\nsmartwatch is nearby, it can <a href=\"https:\/\/www.sciencedirect.com\/science\/article\/pii\/S1574119216302085\">identify concrete TV channels and on-demand\nvideos<\/a>.<\/p>\n<p>Multiple devices allow conducting <a href=\"https:\/\/blog.lukaszolejnik.com\/privacy-of-ambient-light-sensors\/\">cross-device\ntracking<\/a>. For\ninstance, when someone uses a phone and a tablet in the same room, a website\nwith access to their light sensors can compare the values to distinguish whether\nthe same person uses two separate devices. The sensor can also be used for\n<a href=\"https:\/\/blog.lukaszolejnik.com\/privacy-of-ambient-light-sensors\/\">cross-device\ncommunication<\/a>\nwhere one device emits light by displaying content on its screen and the other\nreads the message through the sensor-measured illuminance values.<\/p>\n<h4 id=\"wrapping-the-ambientlightsensor-readings\"><a class=\"toclink\" href=\"#wrapping-the-ambientlightsensor-readings\">Wrapping the AmbientLightSensor readings<\/a><\/h4>\n<p>To eliminate possible exploitation of sensor readings, we decided to generate\nfake readings instead of modifying existing ones. On examined stationary devices\ninside an office, the illuminance measured was between 500 and 900, depending on\nthe concrete position's light conditions. All measured values were rounded to\nthe nearest 50 illuminance value. The wrapper simulates the same behaviour under\nnon-changing light conditions. In the beginning, a pseudorandom illuminance\nvalue is drawn using PRNG seeded with the domain hash - which should guarantee\nto produce the same values on multiple browser tabs on the same domain. As we\nsimulate a stationary device under constant light conditions, this value remains\nthe same for all readings. The faked value is returned using a wrapped\n<code>illuminance<\/code> getter of the <code>AmbientLightSensor.prototype<\/code>.<\/p>\n<h3 id=\"accelerometer\"><a class=\"toclink\" href=\"#accelerometer\">Accelerometer<\/a><\/h3>\n<p>Accelerometers provide information about the device's acceleration<\/p>\n<ul>\n<li>i.e., the rate of change of its velocity. The Generic Sensor API provides\naccess to the readings using three classes: the <code>Accelerometer<\/code> sensor, the\n<code>LinearAccelerationSensor<\/code>, and the <code>GravitySensor<\/code>. All use data from the\nunderlying <code>accelerometer<\/code> device sensor. The difference between them is whether\nand how the gravity acceleration is applied. The <code>Accelerometer<\/code> sensor provides\ninformation about the total acceleration that is applied to the device. The\nremaining two isolate the sources. The <code>LinearAccelerationSensor<\/code> ignores the\ninfluence of gravity. The <code>GravitySensor<\/code> returns just the gravity acceleration.<\/li>\n<\/ul>\n<h4 id=\"fingerprinting-with-accelerometer\"><a class=\"toclink\" href=\"#fingerprinting-with-accelerometer\">Fingerprinting with Accelerometer<\/a><\/h4>\n<p>Readings from the accelerometer sensor classes represent a potential risk and\nneed to be protected. A unique fingerprint <a href=\"https:\/\/link.springer.com\/chapter\/10.1007\/978-3-319-30806-7_7\">can be obtained by describing the\ndevice's\nvibrations<\/a>.\nUsing <a href=\"https:\/\/www.researchgate.net\/publication\/220990763_ACComplice_Location_inference_using_accelerometers_on_smartphones\">trajectory\ninference<\/a>\nand matching the model to map data, one may use the readings from the\nAccelerometer to determine the device's position<\/p>\n<p>From the accelerometer readings, <a href=\"https:\/\/www.mysk.blog\/2021\/10\/24\/accelerometer-ios\/\">it is possible to\ninfer<\/a> whether the device\nuser is lying, sitting, walking, or cycling. For walking and running, the data\nallow calculating steps. Accelerometer readings can also be used to determine\n<a href=\"https:\/\/www.researchgate.net\/publication\/322835708_Classifying_Human_Walking_Patterns_using_Accelerometer_Data_from_Smartphone\">human walking\npatterns<\/a>.<\/p>\n<p>Similar to <code>Gyroscope<\/code>, the <code>Accelerometer<\/code> sensor is also influenced by\nvibrations from speech. Using the <a href=\"https:\/\/arxiv.org\/abs\/1907.05972\">Spearphone\nattack<\/a>, it is possible to perform gender\nclassification (with accuracy over 90%) and speaker identification (with\naccuracy over 80%). Speech recognition and classification can also be done from\nthe reading of this sensor.<\/p>\n<h4 id=\"wrapping-the-accelerometer-readings\"><a class=\"toclink\" href=\"#wrapping-the-accelerometer-readings\">Wrapping the Accelerometer readings<\/a><\/h4>\n<p>Our wrapper replaces the acceleration getters of these sensors. The goal is to\nsimulate a stationary device, possibly rotated. A rotation matrix represents the\norientation of the device. Its values are drawn pseudorandomly from the domain\nhash, and are shared between all sensor wrappers to simulate the same behaviour.<\/p>\n<p>The <code>GravitySensor<\/code> provides readings of gravity acceleration applied to the\ndevice. This is represented by a vector made of <code>x<\/code>, <code>y<\/code>, <code>z<\/code> portions. To get\nthis faked gravity vector for the device, the reference vector <code>[0, 0, 9.8]<\/code> is\nmultiplied with the rotation matrix. Wrappers for the GravitySensor's getters\nreturn the individual portions of the fake gravity vector.<\/p>\n<p>Next, the <code>LinearAccelerationSensor<\/code> should return acceleration values without\nthe contribution of gravity. For a stationary device, it should be all zeroes.\nYet, vibrations may change values a little bit, e.g., spin around <code>-0.1<\/code> to\n<code>+0.1<\/code>, as seen on the examined devices. Such vibrations usually do not happen\nwith every reading but only in intervals of seconds. And thus, JShelter\npseudorandomly changes these values after a few seconds.<\/p>\n<p>Finally, the <code>Accelerometer<\/code> sensor combines the previous two. Our wrappers thus\nreturn the values from the LinearAccelerationSensor with the fake gravity vector\nportions added.<\/p>\n<p>For all three classes, we return the faked orientation values using the wrapped\n<code>x<\/code>, <code>y<\/code>, <code>z<\/code> component getters of the <code>Accelerometer.prototype<\/code>. Based on the\nconstructor name the wrapper detects which concrete class is used and thus what\nbehaviour to simulate.<\/p>\n<h3 id=\"gyroscope\"><a class=\"toclink\" href=\"#gyroscope\">Gyroscope<\/a><\/h3>\n<p>The Gyroscope sensor provides readings of the angular velocity of the device\nalong the <code>x<\/code>, <code>y<\/code>, <code>z<\/code> axes. The class uses the underlying <code>gyroscope<\/code> device\nsensor. Physically, classic gyroscopes used a spinning wheel or a disc with free\naxes of rotation. In modern electronic devices, gyroscopes use piezoelectric or\nsilicon transducers and <a href=\"https:\/\/www5.epsondevice.com\/en\/information\/technical_info\/gyro\/\">various mechanical\nstructures<\/a>.<\/p>\n<h4 id=\"fingerprinting-with-gyroscope\"><a class=\"toclink\" href=\"#fingerprinting-with-gyroscope\">Fingerprinting with Gyroscope<\/a><\/h4>\n<p>Gyroscope readings can be used for <a href=\"https:\/\/crypto.stanford.edu\/gyrophone\/\">speech\nrecognition<\/a> and various fingerprinting\noperations. For stationary devices, the resonance of the unique internal or\nexternal sounds affect angular velocities affect the Gyroscope, and <a href=\"https:\/\/www.researchgate.net\/publication\/356678825_Mobile_Device_Fingerprint_Identification_Using_Gyroscope_Resonance\">allow to\ncreate a\nfingerprint<\/a>.\nFor moving devices, one of the options is using the Gyroscope to analyze <a href=\"https:\/\/www.ncbi.nlm.nih.gov\/pmc\/articles\/PMC7071017\/\">human\nwalking patterns<\/a>.<\/p>\n<h4 id=\"wrapping-the-gyroscope-readings\"><a class=\"toclink\" href=\"#wrapping-the-gyroscope-readings\">Wrapping the Gyroscope Readings<\/a><\/h4>\n<p>All velocities should be zero in an ideal state for a stationary device. As we\nobserved on the examined devices, various device sensor imperfections and tiny\nvibrations cause the values oscillate between <code>-0.002<\/code> and <code>0.002<\/code> on the\nexamined devices. Our wrapper thus simulates the same behaviour.<\/p>\n<p>The changes are applied in pseudorandom intervals between <code>500 ms<\/code> to <code>2 s<\/code>.\nThese boundaries were chosen with respect to our observations from the examined\ndevices' real sensors. The actual values of change are also calculated\npseudorandomly from the domain hash to ensure deterministic and consistent\nbehaviour within a given domain. The faked values are then returned using\nwrapped <code>x<\/code>, <code>y<\/code>, <code>z<\/code> getters of the <code>Gyroscope.prototype<\/code>.<\/p>\n<h3 id=\"magnetometer\"><a class=\"toclink\" href=\"#magnetometer\">Magnetometer<\/a><\/h3>\n<p>A magnetometer measures the strength and direction of the magnetic field around\nthe device. The interface offers sensor readings using three properties: <code>x<\/code>,\n<code>y<\/code>, and <code>z<\/code>. Each returns a number that describes the magnetic field around the\nparticular axis. The numbers have a double precision and can be positive or\nnegative, depending on the orientation of the field. The total strength of the\nmagnetic field (<code>M<\/code>) can be calculated as <code>M = sqrt(x^2 + z^2 + y^2)<\/code>. The unit\nis in microtesla (&micro;T).<\/p>\n<h4 id=\"fingerprinting-with-magnetometer\"><a class=\"toclink\" href=\"#fingerprinting-with-magnetometer\">Fingerprinting with Magnetometer<\/a><\/h4>\n<p>The Earth's magnetic field\n<a href=\"https:\/\/doi.org\/10.1111%2Fj.1365-246X.2010.04804.x\">ranges<\/a> between\napproximately 25 and 65 &micro;T. Concrete values depend on location, altitude,\nweather, and other factors. Yet, there are characteristics of the field for\ndifferent places on Earth. The common model used for their description is the\n<a href=\"https:\/\/www.ngdc.noaa.gov\/IAGA\/vmod\/igrf.html\">International Geomagnetic Reference Field\n(IGRF)<\/a>. While the magnetic field\nchanges over time, the changes are slow: There is a decrease of 5% every 100\nyears. Therefore, for the given latitude, longitude, and altitude, the average\nstrength of the field should be stable. Can one determine the device's location\nbased on the data from the Magnetometer sensor? Not exactly. The measured values\nare influenced by the interference with other electrical devices, isolation\n(buildings, vehicles), the weather, and other factors. Moreover, the field is\nnot unique - similar fields can be measured in different places on Earth.<\/p>\n<p>What, however, can be determined is the orientation of the device. In the case of\na stationary (non-moving) device, the measured values can serve as a\nfingerprint. As we experimentally examined, it is also possible to distinguish\nwhether the device is moving or when its environment changes. When a person with\na cellphone enters a car or an elevator, the metal barrier serves as isolation,\nand the strength of the field gets lower rapidly (e.g., from 60&micro;T outside\nto 27&micro;T inside). A cellphone lying on a case of a running computer can\nproduce values over 100&micro;T, especially if it is near the power supply unit.\nEither way, for a single device at the same location in the same environment,\nthe average strength of the magnetic field should be stable.<\/p>\n<p>Magnetometer values can be used for fingerprinting. First, Magnetometer readings\ncan tell the attacker whether the device is moving or not. In the case of a\nstationary device, we can make a fingerprint from the device orientation.\nAnother fingerprintable value is the average total strength of the field, which\nshould remain stable if the device is at the same position and in the same\nenvironment.<\/p>\n<p>Yet, even moving devices can be exploited. One can misuse the sensor readings to\nmake a <a href=\"https:\/\/www.ieee-security.org\/TC\/SP2019\/papers\/405.pdf\">calibration fingerprint\nattack<\/a> that infers per\ndevice factory calibration data. The researchers showed the attack to be usable\nand efficient on both Android and iOS devices. A device can be identified using\nMagnetometer reading <a href=\"https:\/\/seclab.bu.edu\/papers\/magnetometer-wisec2019.pdf\">through the analysis of the\nbias<\/a>. Moreover, the\ndevice itself also produces electromagnetic emissions and can thus be identified\nusing the <a href=\"https:\/\/seclab.bu.edu\/papers\/magnetometer-wisec2019.pdf\">physical proximity\nattack<\/a> is also\npossible to use an external device. As the underlying device sensor is also\ndisturbed by the device's CPU activity, Magnetometer measurements can also be\n<a href=\"https:\/\/arxiv.org\/pdf\/1906.11117.pdf\">used to identify running applications or web\npages<\/a>.<\/p>\n<h4 id=\"wrapping-of-magnetometer-readings\"><a class=\"toclink\" href=\"#wrapping-of-magnetometer-readings\">Wrapping of Magnetometer readings<\/a><\/h4>\n<p>JShelter wraps the <code>x<\/code>, <code>y<\/code>, <code>z<\/code> getters of the <code>Magnetometer.prototype<\/code> object\nto protect the device. Instead of using the original data, JShelter returns\nartificially generated values that look like actual sensor readings.<\/p>\n<p>At every moment, our wrapper stores information about the previous reading. Each\nrewrapped getter first checks the <code>timestamp<\/code> value of the sensor object. If\nthere is no difference of the prior reading's timestamp, the wrapper returns the\nlast measured value. Otherwise, it provides a new fake reading.<\/p>\n<p>We designed our fake field generator to fulfil the following properties:<\/p>\n<ul>\n<li>The randomness of the generator should be high enough to prevent attackers from\ndeducing the sensor values.<\/li>\n<li>Multiple scripts from the same website that access readings with the same\ntimestamp must get the same results. And thus:<\/li>\n<li>The readings are deterministic - e.g., for a given website and time, we must be\nable to say what values to return.<\/li>\n<\/ul>\n<p>For every \"random\" toss-up, we use the\n<a href=\"https:\/\/gist.github.com\/tommyettinger\/46a874533244883189143505d203312c\">Mulberry32<\/a>\nPRNG that is seeded with a value generated from the <code>domainHash<\/code>, which ensures\ndeterministic behaviour for the given website. First, we choose the desired\ntotal strength <code>M<\/code> of the magnetic field at our simulated location. This is a\npseudorandom number from <code>25<\/code> to <code>60<\/code> &micro;T, like on the Earth.<\/p>\n<p>First, we need to set the initial orientation of the axes. Our wrappers support\ntwo methods:<\/p>\n<ul>\n<li>The original implementation where the orientation of axes is drawn\npseudorandomly.<\/li>\n<li>An improved version where we use the faked device rotation shared by other\nwrappers. In this case, we start with the reference magnetic field vector that\nis oriented towards the Earth's magnetic North and towards the centre of the\nEarth. The vector is then multiplied with the shared faked rotation matrix. The\nelements of the resulting vector then represents the axes orientation.<\/li>\n<\/ul>\n<p>For both methods, the orientation is defined by a number from -1 to 1 for each\naxis: JShelter simulates a stationary device with a pseudorandomly drawn\norientation in the current implementation. Therefore, we choose the orientation\nof the device by generating a number from <code>-1<\/code> to <code>1<\/code> for each axis. Those\nvalues we call <code>baseX<\/code>, <code>baseY<\/code>, and <code>baseZ<\/code>. By modifying the above-shown\nformula, we calculate the <code>multiplier<\/code> that needs to be applied to the base\nvalues to get the desired field. The calculation is done as follows:\n<code>mult = (M * sqrt(baseX^2 + baseY^2 + baseZ^2) \/ (baseX^2 + baseY^2 + baseZ^2))<\/code>\nFor axis <code>x<\/code>, the value should fluctuate around <code>baseX * mult<\/code>, etc.<\/p>\n<p>How much the field changes over time is specified by the <strong>fluctuation factor<\/strong>\nfrom <code>(0;1]<\/code> that can also be configured. For instance, <code>0.2<\/code> means that the\nmagnetic field on the axis may change from the base value by <code>20%<\/code> in both\npositive and negative ways.<\/p>\n<p>The fluctuation is simulated by using a series of <strong>sine<\/strong> functions for each\naxis. Each sine has a unique amplitude, phase shift, and period. The number of\nsines per axis is chosen pseudorandomly based on the wrapper settings. For\ninitial experiments, we used around <code>20<\/code> to <code>30<\/code> sines for each axis. The\noptimal configuration is in question. More sines give less predictable results\nbut also increase the computing complexity that could have a negative impact on\nthe browser's performance.<\/p>\n<p>For the given timestamp <code>t<\/code>, we make the sum of all sine values at the point\n<code>x=t<\/code>. The result is then shifted over the y-axis by adding\n<code>base[X|Y|Z] * multiplier<\/code> to the sum. The initial configuration of the fake\nfield generator was chosen intuitively to resemble the results of the real\nmeasurements. Currently, the generator uses <strong>at least one<\/strong> sine with the\nperiod around <code>100<\/code> &micro;s (with <code>10%<\/code> tolerance), which seems to be the\nminimum sampling rate obtainable using the API on mobile devices. Then, at least\none sine around <code>1 s<\/code>, around <code>10 s<\/code>, <code>1 minute<\/code> and <code>1 hour<\/code>. When more than\n<code>5<\/code> sines are used, the cycle repeats using <code>modulo 5<\/code> and creates a new sine\nwith the period around <code>100<\/code> &micro;s, but this time the tolerance is <code>20%<\/code>. The\nsame follows for seconds, tens of seconds, minutes, and hours. The tolerance\ngrows every five sines. For 11+ sines, the tolerance is <code>30%<\/code> up to the maximum\n(currently <code>50%<\/code>). The amplitude of each sine is chosen pseudorandomly based on\nthe <strong>fluctuation factor<\/strong> described above. The phase shift of each sine is also\na pseudorandom number from [0;2&#960;).<\/p>\n<p>Based on the results, this heuristic returns believable values that look like\nactual sensor readings. Nevertheless, the generator uses a series of constants\nwhose optimal values should be a subject of future research and improvements.\nPerhaps, a correlation analysis with real measurements could help in the future.\nFigures below show the values of <code>x<\/code>, <code>y<\/code>, <code>z<\/code>, and the total strength <code>M<\/code>\nmeasured within 10 minutes on a: 1) Stationary device, 2) Moving device, and 3)\nDevice with the fake wrapped magnetometer.<\/p>\n<p><img alt=\"Stationary device\" src=\"https:\/\/jshelter.org\/sensorapi\/device_stationary.svg\"> <img alt=\"Moving\ndevice\" src=\"https:\/\/jshelter.org\/sensorapi\/device_moving.svg\"> <img alt=\"Device with fake\nmagnetometer\" src=\"https:\/\/jshelter.org\/sensorapi\/device_artificial.svg\"><\/p>\n<h3 id=\"device-orientation-sensors\"><a class=\"toclink\" href=\"#device-orientation-sensors\">Device Orientation Sensors<\/a><\/h3>\n<p>This group includes two sensor classes: <code>AbsoluteOrientationSensor<\/code> and\n<code>RelativeOrientationSensor<\/code>. Both describe the physical orientation of the\ndevice, and both require access to the <code>accelerometer<\/code> and <code>gyroscope<\/code> device\nsensors. The difference between the two classes is what they consider as a\nreference coordinate system<\/p>\n<ul>\n<li>i.e., what is the real orientation of a non-rotated device. For this purpose,\nthe <code>AbsoluteOrientationSensor<\/code> uses the Earth's reference coordinate system.\nAnd thus, it also requires access to the <code>magnetometer<\/code> device sensor, simply to\nknow where the North is. The <code>RelativeOrientationSensor<\/code> does not require this\ninformation as the cardinal directions are not used. Yet, the sensor still needs\nto some coordinate system to calculate with - i.e., physical orientation of the\ndevice that is considered as reference. For this purpose, it may use the\norientation from the moment the sensor instance is created. When the sensor is\ninitialized, it creates a new coordinate system, and the device can be\nconsidered non-rotated. Any rotations from this point are done in relation to\nthis newly created coordinate system. Readings are represented by the\n<code>OrientationSensor.quaternion<\/code> made of <code>x<\/code>, <code>y<\/code>, <code>z<\/code>, and <code>w<\/code> components that\ndescribe the orientation of the device.<\/li>\n<\/ul>\n<h4 id=\"fingerprinting-with-device-orientation-sensors\"><a class=\"toclink\" href=\"#fingerprinting-with-device-orientation-sensors\">Fingerprinting with Device Orientation Sensors<\/a><\/h4>\n<p>Device orientation sensors can be easily used for fingerprinting. As it highly\nunlikely that two devices visiting the same site will be oriented exactly the\nsame, the orientation itself can serve as a fingerprint.<\/p>\n<p>As the device orientation sensor use data from both the <code>accelerometer<\/code> and the\n<code>gyroscope<\/code> device sensors, determining the location using <a href=\"https:\/\/www.researchgate.net\/publication\/220990763_ACComplice_Location_inference_using_accelerometers_on_smartphones\">trajectory\ninference<\/a>\nor determining <a href=\"https:\/\/www.researchgate.net\/publication\/322835708_Classifying_Human_Walking_Patterns_using_Accelerometer_Data_from_Smartphone\">human walking\npatterns<\/a>.\ncould be even easier than with bare <code>Accelerometer<\/code> class.<\/p>\n<h4 id=\"wrapping-of-device-orientation-readings\"><a class=\"toclink\" href=\"#wrapping-of-device-orientation-readings\">Wrapping of Device Orientation Readings<\/a><\/h4>\n<p>The <code>AbsoluteOrientationSensor<\/code> returns a quaternion describing the physical\norientation of the device in relation to the Earth's reference coordinate\nsystem. As discussed above, the faked orientation of the device is drawn\npseudorandomly using domain hash as a seed and sored inside a global variable\ncalled <code>orient<\/code> that is accessible to all wrappers. With each reading, it loads\nthe \"orient\" 's contents, converts the rotation matrix to a quaternion that is\nreturned by the wrapped getter. This design makes the outputs realistic and in\naccordance with other sensors' reading. The implemenation also supports possible\nchanges of orientation.<\/p>\n<p>The <code>RelativeOrientationSensor<\/code> also describes the orientation, but without\nregard to the Earth's reference coordinate system. We suppose the coordinate\nsystem is chosen at the beginning of the sensor instance creation. As we\nobserved, no matter how the device is oriented, there is always a slight\ndifference from the AbsoluteOrientationSensor's in at least one axis. When the\ndevice moves, both sensors' readings change. But their difference should always\nbe constant. And thus, we pseudorandomly generate rotation deviations from the\nEarth's reference coordinate system. The deviations are between <code>0<\/code> and <code>\u03c0\/2<\/code>.\nFor each reading, we take the values from the fake AbsoluteOrientationSensor and\nmodify them by the constant deviation.<\/p>\n<p>For both sensor classes, we return the faked orientation values using the wrapped\n<code>x<\/code>, <code>y<\/code>, <code>z<\/code>, and <code>w<\/code> quaternion component getters of the\n<code>OrientationSensor.prototype<\/code>. Based on the constructor name the wrapper detects\nwhether it should simulate the absolute or relative orientation sensor's\nbehaviour.<\/p>","category":{"@attributes":{"term":"pt"}}},{"title":"Paper about JShelter","link":{"@attributes":{"href":"https:\/\/jshelter.org\/paper2022\/","rel":"alternate"}},"published":"2022-04-05T00:00:00+02:00","updated":"2024-03-26T12:54:03+01:00","author":{"name":"The JShelter team"},"id":"tag:jshelter.org,2022-04-05:\/paper2022\/","summary":"<p>We recently submitted a position paper about JShelter for <a href=\"https:\/\/www.cnil.fr\/en\/privacy-research-day-2022\">CNIL Privacy Research Day 2022<\/a> and posted the paper on <a href=\"https:\/\/arxiv.org\/abs\/2204.01392\">arXive<\/a>. If you are interested in the project or if you are already using JShelter, we recommend reading the paper as it explains the project, its historical decisions, the thread model \u2026<\/p>","content":"<p>We recently submitted a position paper about JShelter for <a href=\"https:\/\/www.cnil.fr\/en\/privacy-research-day-2022\">CNIL Privacy Research Day 2022<\/a> and posted the paper on <a href=\"https:\/\/arxiv.org\/abs\/2204.01392\">arXive<\/a>. If you are interested in the project or if you are already using JShelter, we recommend reading the paper as it explains the project, its historical decisions, the thread model, design decision, experiments, and provides more information that you should know if you want to use JShelter correctly, or, you might decide that JShelter is not the correct tool for you and you might pick one of the alternative tools referenced in the paper.<\/p>\n<p>The Web is used daily by billions. Even so, users are not protected from many\nthreats by default. Jshelter builds on previous web privacy and\nsecurity research and fights to return\nthe browser to users. The paper introduces <a href=\"https:\/\/noscript.net\/commons-library\">NSCL<\/a>,\na library helping with common webextension development tasks and fixing\nloopholes misused by previous research. JShelter\nfocuses on fingerprinting prevention, limitations of rich web APIs,\nprevention of attacks connected to timing, and learning information about\nthe computer, the browser, the user, and surrounding physical environment and\nlocation. We discovered a loophole in the sensor timestamps that lets any\npage observe the device boot time if sensor APIs are enabled in Chromium-based\nbrowsers. JShelter provides a fingerprinting report and other feedback\nthat can be used by future security research and data protection\nauthorities. Thousands of users around the world use the webextension every day.<\/p>\n<p>Previous research established that browser security, privacy, and customizability are\nimportant\ntopics.\nThe imminent danger of third-party cookies' removal forces\ntrackers to employ even more privacy-invading techniques. Real-time bidding\nleaves users easy targets for various attacks, including gaining information\nabout other applications running on the local computer.\nMoreover, continuous additions of new JavaScript APIs open new ways for fingerprinting\nthe browsers and gaining additional knowledge about the browser or user preferences\nand physical environment.\nOne of the\nmajor concerns is a lack of effective tools that everyday user wants to use. Current\nmethods to tackle web threats are list-based blockers that might be evaded with\na change of URL, specialised browsers, or research-only projects that are\nquickly abandoned.<\/p>\n<p>In contrast, JShelter is a webextension that can be installed on major\nbrowsers and consequently does not require the user to change the browser and\nroutines. We integrate several previous research projects like <a href=\"https:\/\/github.com\/IAIK\/ChromeZero\">Chrome\nZero<\/a>, <a href=\"https:\/\/brave.com\/privacy-updates\/3-fingerprint-randomization\/\">little-lies-based fingerprinting prevention<\/a>, and ideas of limiting APIs brought by <a href=\"https:\/\/github.com\/pes10k\/web-api-manager\">Web API\nManager<\/a>. JShelter comes with <a href=\"\/fpdetection\/\">a heuristic-based fingerprint detector<\/a>\nand prevents webpages from <a href=\"\/localportscanning\/\">misusing the browser as a proxy to access the local network\nand computer<\/a>.\nWe needed to solve issues with reliable environment modifications that stem\nfrom webextension API that opens many loopholes that previous research\nexploited. In addition to JShelter, we introduce <a href=\"https:\/\/noscript.net\/commons-library\">NSCL<\/a>.\nBoth NoScript Security Suite and JShelter include NSCL. Moreover, NSCL is available for other privacy-\nand security-related webextensions.<\/p>\n<p>In cooperation with the Free Software Foundation, we\naim for long-term JShelter development; thus, users' privacy and security\nshould be improved in the future. We explain fingerprinting vectors introduced\nby Sensor API in mobile browsers.\nData protection\nspecialists should detect browser fingerprinting and other information leaks\nwith JShelter. We\nintegrated <a href=\"\/cooperation\/\">fingerprint report<\/a> and notifications to facilitate the task. We discussed considerations and issues connected with\ndeployment. The webextension is under development. Future work will include\nfixing problems breaking pages, improved heuristics of FPD, and research\nfingerprinting on login pages. We want to revisit\nand evaluate the little-lies-based anti-fingerprinting technique; are the little changes\nenough to stop a determined fingerprinter that can, for example, approximate\ncolour\nvalues of several pixels or repeat an effect multiple times?\nJShelter should not be considered a single bullet-proof solution.\nWe anticipate that everyday users will install JShelter together with other\nwebextensions like\nlist-based blockers or JavaScript blockers.<\/p>","category":{"@attributes":{"term":"posts"}}},{"title":"Paper about JShelter","link":{"@attributes":{"href":"https:\/\/jshelter.org\/pt\/paper2022\/","rel":"alternate"}},"published":"2022-04-05T00:00:00+02:00","updated":"2025-07-10T07:01:03+02:00","author":{"name":"The JShelter team"},"id":"tag:jshelter.org,2022-04-05:\/pt\/paper2022\/","summary":"<p>We recently submitted a position paper about JShelter for <a href=\"https:\/\/www.cnil.fr\/en\/privacy-research-day-2022\">CNIL Privacy Research\nDay 2022<\/a> and posted the paper\non <a href=\"https:\/\/arxiv.org\/abs\/2204.01392\">arXive<\/a>. If you are interested in the\nproject or if you are already using JShelter, we recommend reading the paper as\nit explains the project, its historical decisions, the thread model \u2026<\/p>","content":"<p>We recently submitted a position paper about JShelter for <a href=\"https:\/\/www.cnil.fr\/en\/privacy-research-day-2022\">CNIL Privacy Research\nDay 2022<\/a> and posted the paper\non <a href=\"https:\/\/arxiv.org\/abs\/2204.01392\">arXive<\/a>. If you are interested in the\nproject or if you are already using JShelter, we recommend reading the paper as\nit explains the project, its historical decisions, the thread model, design\ndecision, experiments, and provides more information that you should know if you\nwant to use JShelter correctly, or, you might decide that JShelter is not the\ncorrect tool for you and you might pick one of the alternative tools referenced\nin the paper.<\/p>\n<p>The Web is used daily by billions. Even so, users are not protected from many\nthreats by default. Jshelter builds on previous web privacy and security\nresearch and fights to return the browser to users. The paper introduces\n<a href=\"https:\/\/noscript.net\/commons-library\">NSCL<\/a>, a library helping with common\nwebextension development tasks and fixing loopholes misused by previous\nresearch. JShelter focuses on fingerprinting prevention, limitations of rich web\nAPIs, prevention of attacks connected to timing, and learning information about\nthe computer, the browser, the user, and surrounding physical environment and\nlocation. We discovered a loophole in the sensor timestamps that lets any page\nobserve the device boot time if sensor APIs are enabled in Chromium-based\nbrowsers. JShelter provides a fingerprinting report and other feedback that can\nbe used by future security research and data protection authorities. Thousands\nof users around the world use the webextension every day.<\/p>\n<p>Previous research established that browser security, privacy, and customizability\nare important topics. The imminent danger of third-party cookies' removal forces\ntrackers to employ even more privacy-invading techniques. Real-time bidding\nleaves users easy targets for various attacks, including gaining information\nabout other applications running on the local computer. Moreover, continuous\nadditions of new JavaScript APIs open new ways for fingerprinting the browsers\nand gaining additional knowledge about the browser or user preferences and\nphysical environment. One of the major concerns is a lack of effective tools\nthat everyday user wants to use. Current methods to tackle web threats are\nlist-based blockers that might be evaded with a change of URL, specialised\nbrowsers, or research-only projects that are quickly abandoned.<\/p>\n<p>In contrast, JShelter is a webextension that can be installed on major browsers\nand consequently does not require the user to change the browser and routines.\nWe integrate several previous research projects like <a href=\"https:\/\/github.com\/IAIK\/ChromeZero\">Chrome\nZero<\/a>, <a href=\"https:\/\/brave.com\/privacy-updates\/3-fingerprint-randomization\/\">little-lies-based fingerprinting\nprevention<\/a>, and\nideas of limiting APIs brought by <a href=\"https:\/\/github.com\/pes10k\/web-api-manager\">Web API\nManager<\/a>. JShelter comes with <a href=\"\/fpdetection\/\">a\nheuristic-based fingerprint detector<\/a> and prevents webpages from\n<a href=\"\/localportscanning\/\">misusing the browser as a proxy to access the local network\nand\ncomputer<\/a>. We needed to solve issues with reliable\nenvironment modifications that stem from webextension API that opens many\nloopholes that previous research exploited. In addition to JShelter, we\nintroduce <a href=\"https:\/\/noscript.net\/commons-library\">NSCL<\/a>. Both NoScript Security\nSuite and JShelter include NSCL. Moreover, NSCL is available for other privacy-\nand security-related webextensions.<\/p>\n<p>In cooperation with the Free Software Foundation, we aim for long-term JShelter\ndevelopment; thus, users' privacy and security should be improved in the future.\nWe explain fingerprinting vectors introduced by Sensor API in mobile browsers.\nData protection specialists should detect browser fingerprinting and other\ninformation leaks with JShelter. We integrated <a href=\"\/cooperation\/\">fingerprint\nreport<\/a> and notifications to facilitate the task. We discussed\nconsiderations and issues connected with deployment. The webextension is under\ndevelopment. Future work will include fixing problems breaking pages, improved\nheuristics of FPD, and research fingerprinting on login pages. We want to\nrevisit and evaluate the little-lies-based anti-fingerprinting technique; are\nthe little changes enough to stop a determined fingerprinter that can, for\nexample, approximate colour values of several pixels or repeat an effect\nmultiple times? JShelter should not be considered a single bullet-proof\nsolution. We anticipate that everyday users will install JShelter together with\nother webextensions like list-based blockers or JavaScript blockers.<\/p>","category":{"@attributes":{"term":"pt"}}},{"title":"Cooperation with the JShelter project, rebranding, and latest features","link":{"@attributes":{"href":"https:\/\/jshelter.org\/cooperation\/","rel":"alternate"}},"published":"2022-03-17T15:00:00+01:00","updated":"2024-03-26T12:54:03+01:00","author":{"name":"The JShelter team"},"id":"tag:jshelter.org,2022-03-17:\/cooperation\/","summary":"<p>This project started as JavaScript Restrictor at <a href=\"https:\/\/www.fit.vut.cz\">Brno University of\nTechnology<\/a>. As announced in <a href=\"\/support\">a previous post<\/a>, we got funding\nfrom NLNet <a href=\"https:\/\/nlnet.nl\/project\/JSRestrictor\/\">NGI0 PET Fund<\/a>. Furthermore, NLNet allowed us to cooperate with another NGI0 PET Fund project - <a href=\"https:\/\/nlnet.nl\/project\/JavascriptShield\/\">JShelter project (formerly known as\nJavaScript Shield project) run by Free Software\nFoundation \u2026<\/a><\/p>","content":"<p>This project started as JavaScript Restrictor at <a href=\"https:\/\/www.fit.vut.cz\">Brno University of\nTechnology<\/a>. As announced in <a href=\"\/support\">a previous post<\/a>, we got funding\nfrom NLNet <a href=\"https:\/\/nlnet.nl\/project\/JSRestrictor\/\">NGI0 PET Fund<\/a>. Furthermore, NLNet allowed us to cooperate with another NGI0 PET Fund project - <a href=\"https:\/\/nlnet.nl\/project\/JavascriptShield\/\">JShelter project (formerly known as\nJavaScript Shield project) run by Free Software\nFoundation<\/a>.<\/p>\n<p>What did the cooperation bring to the users? More experience, more developers, new ideas. Let us go\nthrough the list.<\/p>\n<h2 id=\"1-open-development\"><a class=\"toclink\" href=\"#1-open-development\">1. Open development<\/a><\/h2>\n<p>We created <a href=\"https:\/\/lists.nongnu.org\/archive\/html\/js-shield\/\">mailing list<\/a> where we discussed the\ncore issues of the development of the extension. We held weekly meetings with the minutes available\nin the mailing list archives.<\/p>\n<h2 id=\"2-integration-of-noscript-common-library\"><a class=\"toclink\" href=\"#2-integration-of-noscript-common-library\">2. Integration of NoScript Common Library<\/a><\/h2>\n<p>Giorgio Maone, the developer of NoScript Suite, is a part of the JShelter project. His\nrecent efforts were in developing NoScript Common Library (NSCL). The library aims at\nsimplifying the development of privacy and security-oriented web extensions with cross-browser\nsupport.<\/p>\n<p>Giorgio refactored the defence mechanism code injection and cross-browser support. Both now depend\non NSCL.<\/p>\n<p>The improved code injection solved several long-term issues of the previous mechanism like (1) the\nuncertainty that the defences are inserted in time before page scripts have access to the\nenvironment and (2) the vulnerability to <a href=\"https:\/\/bugzilla.mozilla.org\/show_bug.cgi?id=1267027\">Firefox\nbug<\/a> that did not allow to insert (full)\ndefenses for pages relying on <a href=\"https:\/\/content-security-policy.com\/\">CSP headers<\/a>.<\/p>\n<p>Improved cross-browser support should make the future lives of developers easier. Should there be a\nchange like the switch to Manifest3, we will benefit from the effort put into NSCL.<\/p>\n<h2 id=\"3-development-of-a-new-site\"><a class=\"toclink\" href=\"#3-development-of-a-new-site\">3. Development of a new site<\/a><\/h2>\n<p><a href=\"https:\/\/manufacturaindependente.org\/\">Manufactura Independente<\/a>, another participant of JShelter,\ncreated a new website for the extension, see <a href=\"https:\/\/jshelter.org\/\">https:\/\/jshelter.org\/<\/a>. Besides the polished structure\nand look, the new website allows generating documentation for the Javascript Shield wrappers directly from the\ncomments in the source code. We do not have automatic updates, yet. But we are working on that.<\/p>\n<h2 id=\"4-improved-user-interface\"><a class=\"toclink\" href=\"#4-improved-user-interface\">4. Improved user interface<\/a><\/h2>\n<p>The original method of assigning JavaScript Shield levels of protection to each domain is nice to explain and should\nbe easy to understand for users. However, as the number of protected APIs grew, it became clear that we needed to fine-tune the protection for each page for power users. For example, allow Geolocation API\nfor maps or allow Media Devices on a video call application.<\/p>\n<p>The user often had to create a new level for each misbehaving domain. With time, the amount of\nlevels becomes overwhelming, and the user needs to keep them in sync with the addition of the new\nAPIs. Each version of the extension that adds new wrappers tries to guess how to\nmodify user-defined levels to ease this task. But these modifications are not perfect.<\/p>\n<p>0.7 brought a redesigned UI and backend that allows fine-tuning JavaScript Shield protection for\neach domain. The new UI is a result of the effort of most of the developers of the extension.<\/p>\n<p><img alt=\"You can see the number of calls for each wrapping group and fine-tune the protection for the\nvisited page\" src=\"https:\/\/jshelter.org\/cooperation\/fine-tuning.png\"><\/p>\n<p>0.6 introduced Fingerprint Detector that was improved in 0.8. The pop up shows the number of potentially suspicious calls and the likelihood of fingerprinting behaviour. The fingerprint report provides an explanation about the fingerprinting activities. Marek Salo\u0148 is the main author of the Fingerprint Detector.<\/p>\n<p><img alt=\"FPD report shows the reasoning to claim that a page is fingerprinting the browser\" src=\"https:\/\/jshelter.org\/cooperation\/fpd-report.png\"><\/p>\n<p>Marek also improved notifications so that the user cannot be overwhelmed by many similar\nnotifications during a very short period. Starting from 0.8, it is also possible to use the\nextension for passive monitoring of page activities without active modifications to the JavaScript\nenvironment. Network Boundary Shield cannot be turned into notify mode, but it is very rare to find a\npage accessing a local network. This configuration should not break any page, the user is not\nprotected from fingerprinting, but the extension notifies about suspicious activities.<\/p>\n<p><img alt=\"Almost passive configuration of the extension\" src=\"https:\/\/jshelter.org\/cooperation\/almost-passive.png\"><\/p>\n<h2 id=\"5-publicity\"><a class=\"toclink\" href=\"#5-publicity\">5. Publicity<\/a><\/h2>\n<p>At the beginning of 2021, we only had a handful of users. When FSF <a href=\"https:\/\/www.fsf.org\/news\/fsf-announces-jshelter-browser-add-on-to-combat-threats-from-nonfree-javascript\">announced the\nproject<\/a>\nthe number of users raised. We managed to retain about 75% of the peek of Firefox and Chrome users. See graphs below.<\/p>\n<p>The number of daily users with Firefox browser:<\/p>\n<p><img alt=\"You can see a steep rise of Firefox daily users after the announcement on September\n30th, some users left afterwards\" src=\"https:\/\/jshelter.org\/cooperation\/firefox-daily-2022-03-17.png\"><\/p>\n<p>The number of weekly users with Chrome browser:<\/p>\n<p><img alt=\"We managed to keep most of the Chrome users\" src=\"https:\/\/jshelter.org\/cooperation\/chrome-weekly-2022-03-17.png\"><\/p>\n<p>The more users we have, the more bugs are found and the more likely we receive\ncontributions from other parties. Also, keep in mind that some defences against fingerprinting\nwork best when many users employ the\ndefences. So it is beneficial for everyone to have as many users as possible.<\/p>\n<h2 id=\"6-new-name-rebranding\"><a class=\"toclink\" href=\"#6-new-name-rebranding\">6. New name, rebranding<\/a><\/h2>\n<p>The cooperation initially started between JavaScript Restrictor and JavaScript Shield project.\nWe thought about several rebranding options for the project to give\nusers a warm feeling of safety. Starting from 0.7 the extension is called JShelter,\nuses a more privacy-friendly repository, and we already had a new crab icon in the late 0.5 releases.<\/p>\n<p>We intend to keep the current infrastructure like the Github repository intact. All Github pages links\nshould work and are redirected to the new website.<\/p>","category":{"@attributes":{"term":"posts"}}},{"title":"Cooperation with the JShelter project, rebranding, and latest features","link":{"@attributes":{"href":"https:\/\/jshelter.org\/pt\/cooperation\/","rel":"alternate"}},"published":"2022-03-17T15:00:00+01:00","updated":"2025-07-10T07:01:03+02:00","author":{"name":"The JShelter team"},"id":"tag:jshelter.org,2022-03-17:\/pt\/cooperation\/","summary":"<p>This project started as JavaScript Restrictor at <a href=\"https:\/\/www.fit.vut.cz\">Brno University of\nTechnology<\/a>. As announced in <a href=\"\/support\">a previous\npost<\/a>, we got funding from NLNet <a href=\"https:\/\/nlnet.nl\/project\/JSRestrictor\/\">NGI0 PET\nFund<\/a>. Furthermore, NLNet allowed us to\ncooperate with another NGI0 PET Fund project - <a href=\"https:\/\/nlnet.nl\/project\/JavascriptShield\/\">JShelter project (formerly known\nas\nJavaScript Shield project) run by Free Software\nFoundation \u2026<\/a><\/p>","content":"<p>This project started as JavaScript Restrictor at <a href=\"https:\/\/www.fit.vut.cz\">Brno University of\nTechnology<\/a>. As announced in <a href=\"\/support\">a previous\npost<\/a>, we got funding from NLNet <a href=\"https:\/\/nlnet.nl\/project\/JSRestrictor\/\">NGI0 PET\nFund<\/a>. Furthermore, NLNet allowed us to\ncooperate with another NGI0 PET Fund project - <a href=\"https:\/\/nlnet.nl\/project\/JavascriptShield\/\">JShelter project (formerly known\nas\nJavaScript Shield project) run by Free Software\nFoundation<\/a>.<\/p>\n<p>What did the cooperation bring to the users? More experience, more developers,\nnew ideas. Let us go through the list.<\/p>\n<h2 id=\"1-open-development\"><a class=\"toclink\" href=\"#1-open-development\">1. Open development<\/a><\/h2>\n<p>We created <a href=\"https:\/\/lists.nongnu.org\/archive\/html\/js-shield\/\">mailing list<\/a> where\nwe discussed the core issues of the development of the extension. We held weekly\nmeetings with the minutes available in the mailing list archives.<\/p>\n<h2 id=\"2-integration-of-noscript-common-library\"><a class=\"toclink\" href=\"#2-integration-of-noscript-common-library\">2. Integration of NoScript Common Library<\/a><\/h2>\n<p>Giorgio Maone, the developer of NoScript Suite, is a part of the JShelter\nproject. His recent efforts were in developing NoScript Common Library (NSCL).\nThe library aims at simplifying the development of privacy and security-oriented\nweb extensions with cross-browser support.<\/p>\n<p>Giorgio refactored the defence mechanism code injection and cross-browser\nsupport. Both now depend on NSCL.<\/p>\n<p>The improved code injection solved several long-term issues of the previous\nmechanism like (1) the uncertainty that the defences are inserted in time before\npage scripts have access to the environment and (2) the vulnerability to\n<a href=\"https:\/\/bugzilla.mozilla.org\/show_bug.cgi?id=1267027\">Firefox\nbug<\/a> that did not allow to\ninsert (full) defenses for pages relying on <a href=\"https:\/\/content-security-policy.com\/\">CSP\nheaders<\/a>.<\/p>\n<p>Improved cross-browser support should make the future lives of developers easier.\nShould there be a change like the switch to Manifest3, we will benefit from the\neffort put into NSCL.<\/p>\n<h2 id=\"3-development-of-a-new-site\"><a class=\"toclink\" href=\"#3-development-of-a-new-site\">3. Development of a new site<\/a><\/h2>\n<p><a href=\"https:\/\/manufacturaindependente.org\/\">Manufactura Independente<\/a>, another\nparticipant of JShelter, created a new website for the extension, see\n<a href=\"https:\/\/jshelter.org\/\">https:\/\/jshelter.org\/<\/a>. Besides the polished structure and look, the new\nwebsite allows generating documentation for the Javascript Shield wrappers\ndirectly from the comments in the source code. We do not have automatic updates,\nyet. But we are working on that.<\/p>\n<h2 id=\"4-improved-user-interface\"><a class=\"toclink\" href=\"#4-improved-user-interface\">4. Improved user interface<\/a><\/h2>\n<p>The original method of assigning JavaScript Shield levels of protection to each\ndomain is nice to explain and should be easy to understand for users. However,\nas the number of protected APIs grew, it became clear that we needed to\nfine-tune the protection for each page for power users. For example, allow\nGeolocation API for maps or allow Media Devices on a video call application.<\/p>\n<p>The user often had to create a new level for each misbehaving domain. With time,\nthe amount of levels becomes overwhelming, and the user needs to keep them in\nsync with the addition of the new APIs. Each version of the extension that adds\nnew wrappers tries to guess how to modify user-defined levels to ease this task.\nBut these modifications are not perfect.<\/p>\n<p>0.7 brought a redesigned UI and backend that allows fine-tuning JavaScript Shield\nprotection for each domain. The new UI is a result of the effort of most of the\ndevelopers of the extension.<\/p>\n<p><img alt=\"visited page\" src=\"https:\/\/jshelter.org\/cooperation\/fine-tuning.png\"><\/p>\n<p>0.6 introduced Fingerprint Detector that was improved in 0.8. The pop up shows\nthe number of potentially suspicious calls and the likelihood of fingerprinting\nbehaviour. The fingerprint report provides an explanation about the\nfingerprinting activities. Marek Salo\u0148 is the main author of the Fingerprint\nDetector.<\/p>\n<p><img alt=\"FPD report shows the reasoning to claim that a page is fingerprinting the\nbrowser\" src=\"https:\/\/jshelter.org\/cooperation\/fpd-report.png\"><\/p>\n<p>Marek also improved notifications so that the user cannot be overwhelmed by many\nsimilar notifications during a very short period. Starting from 0.8, it is also\npossible to use the extension for passive monitoring of page activities without\nactive modifications to the JavaScript environment. Network Boundary Shield\ncannot be turned into notify mode, but it is very rare to find a page accessing\na local network. This configuration should not break any page, the user is not\nprotected from fingerprinting, but the extension notifies about suspicious\nactivities.<\/p>\n<p><img alt=\"Almost passive configuration of the\nextension\" src=\"https:\/\/jshelter.org\/cooperation\/almost-passive.png\"><\/p>\n<h2 id=\"5-publicity\"><a class=\"toclink\" href=\"#5-publicity\">5. Publicity<\/a><\/h2>\n<p>At the beginning of 2021, we only had a handful of users. When FSF <a href=\"https:\/\/www.fsf.org\/news\/fsf-announces-jshelter-browser-add-on-to-combat-threats-from-nonfree-javascript\">announced the\nproject<\/a>\nthe number of users raised. We managed to retain about 75% of the peek of\nFirefox and Chrome users. See graphs below.<\/p>\n<p>The number of daily users with Firefox browser:<\/p>\n<p><img alt=\"30th, some users left\nafterwards\" src=\"https:\/\/jshelter.org\/cooperation\/firefox-daily-2022-03-17.png\"><\/p>\n<p>The number of weekly users with Chrome browser:<\/p>\n<p><img alt=\"We managed to keep most of the Chrome\nusers\" src=\"https:\/\/jshelter.org\/cooperation\/chrome-weekly-2022-03-17.png\"><\/p>\n<p>The more users we have, the more bugs are found and the more likely we receive\ncontributions from other parties. Also, keep in mind that some defences against\nfingerprinting work best when many users employ the defences. So it is\nbeneficial for everyone to have as many users as possible.<\/p>\n<h2 id=\"6-new-name-rebranding\"><a class=\"toclink\" href=\"#6-new-name-rebranding\">6. New name, rebranding<\/a><\/h2>\n<p>The cooperation initially started between JavaScript Restrictor and JavaScript\nShield project. We thought about several rebranding options for the project to\ngive users a warm feeling of safety. Starting from 0.7 the extension is called\nJShelter, uses a more privacy-friendly repository, and we already had a new crab\nicon in the late 0.5 releases.<\/p>\n<p>We intend to keep the current infrastructure like the Github repository intact.\nAll Github pages links should work and are redirected to the new website.<\/p>","category":{"@attributes":{"term":"pt"}}},{"title":"Catch websites red-handed fingerprinting your browser","link":{"@attributes":{"href":"https:\/\/jshelter.org\/fpdetection\/","rel":"alternate"}},"published":"2022-01-14T14:00:00+01:00","updated":"2024-03-26T12:54:03+01:00","author":{"name":"The JShelter team"},"id":"tag:jshelter.org,2022-01-14:\/fpdetection\/","summary":"<p>We want to identify <a href=\"\/fingerprinting\/\">fingerprinting<\/a> attempts by counting the number of different APIs employed by a page, especially those not frequently used for benign purposes. This blog post introduces a new fingerprinting protection mechanism - FingerPrint Detector (FPD) available in JShelter 0.6. This tool allows users to gain more control \u2026<\/p>","content":"<p>We want to identify <a href=\"\/fingerprinting\/\">fingerprinting<\/a> attempts by counting the number of different APIs employed by a page, especially those not frequently used for benign purposes. This blog post introduces a new fingerprinting protection mechanism - FingerPrint Detector (FPD) available in JShelter 0.6. This tool allows users to gain more control over browser fingerprinting, which has become an invisible threat to our privacy.<\/p>\n<h2 id=\"heuristics-as-a-template-for-the-fingerprinting-detection\"><a class=\"toclink\" href=\"#heuristics-as-a-template-for-the-fingerprinting-detection\">Heuristics as a template for the fingerprinting detection<\/a><\/h2>\n<p><a href=\"\/fingerprinting\/\">Mitigation of browser fingerprinting is not straightforward<\/a>. FPD does not attempt to prevent a script from taking a fingerprint. Neither does FPD falsify a fingerprint. Instead, FPD monitors the APIs that a web page accesses and detects suspicious activities. FPD quickly reacts in case of fingerprint extraction and blocks further web page communication, including storing information.<\/p>\n<p>Several studies over the last decade detected fingerprinting attempts. Many of them used a simple heuristic approach to create a set of conditions. If these conditions are met, suspicious activity is detected. Studies like <a href=\"https:\/\/securehomes.esat.kuleuven.be\/~gacar\/persistent\/the_web_never_forgets.pdf\">The Web Never Forgets<\/a> and <a href=\"https:\/\/www.cs.princeton.edu\/~arvindn\/publications\/OpenWPM_1_million_site_tracking_measurement.pdf\">A 1-million-site Measurement and Analysis<\/a> used this approach to measure the real-world occurrence of web tracking. At the same time, they verified the usability of heuristic-based detection. They found the detection effective with a very low false-positive rate. The most challenging part of this approach is a careful selection of detection conditions. Later, studies began to experiment with more sophisticated methods. For example, <a href=\"https:\/\/web.cs.ucdavis.edu\/~zubair\/files\/fpinspector-sp2021.pdf\">Fingerprinting the Fingerprinters<\/a> used machine learning for fingerprinting detection. They managed to achieve even better precision, but at the cost of demanding model training.<\/p>\n<p>For JShelter, we settled down on using a simple heuristic approach, but with a little twist to it. Internet technology is constantly changing, so we want to make our heuristics as flexible as possible. Instead of hard-coding them, we propose a declarative way to describe the heuristics. This concept allows us to make changes with a new release and progressively <a href=\"\/crawling_results\/\">adapt to the latest changes in the field<\/a>. For this purpose, we defined JSON configuration files, which contain all the information required for fingerprinting detection. As these files make an input for our evaluation\/detection logic, their content directly reflects how JShelter should evaluate websites in terms of fingerprinting.<\/p>\n<p>On closer inspection, the configuration files contain two basic types of entries. Firstly, they define JavaScript endpoints, which are relevant for fingerprinting detection. Secondly, they group related endpoints. For example, we group endpoints according to their semantic properties. Imagine that there are two different endpoints. Both provide hardware information about the device. We can assign both endpoints to a group that covers access to hardware properties in this scenario. The configuration allows clustering groups to other groups and creating a hierarchy of groups. Ultimately, the configuration is a tree-like structure whose evaluation can detect browser fingerprinting.<\/p>\n<p>The whole evaluation process dynamically observes the API calls performed by a web page. Note that we analyse the calls themselves. Hence, the dynamic analysis overcomes any obfuscation of fingerprinting scripts.<\/p>\n<p>The current heuristics are based on many prior studies, <a href=\"\/crawling_results\/\">our own crawl<\/a>, and available tools focused on browser fingerprinting.<\/p>\n<ul>\n<li>\n<p>We extracted and modified detection rules from studies like:<\/p>\n<ul>\n<li><a href=\"https:\/\/web.cs.ucdavis.edu\/~zubair\/files\/fpinspector-sp2021.pdf\">Fingerprinting the Fingerprinters<\/a><\/li>\n<li><a href=\"https:\/\/www.cs.princeton.edu\/~arvindn\/publications\/OpenWPM_1_million_site_tracking_measurement.pdf\">A 1-million-site Measurement and Analysis<\/a><\/li>\n<li><a href=\"https:\/\/securehomes.esat.kuleuven.be\/~gacar\/persistent\/the_web_never_forgets.pdf\">The Web Never Forgets<\/a><\/li>\n<\/ul>\n<\/li>\n<li>\n<p>We reflected traits of known fingerprinting tools like:<\/p>\n<ul>\n<li><a href=\"https:\/\/github.com\/fingerprintjs\">FingerprintJS<\/a><\/li>\n<li><a href=\"https:\/\/amiunique.org\/\">Am I Unique<\/a><\/li>\n<li><a href=\"https:\/\/coveryourtracks.eff.org\/\">Cover Your Tracks<\/a><\/li>\n<\/ul>\n<\/li>\n<li>\n<p>We utilized knowledge of existing detection tools like:<\/p>\n<ul>\n<li><a href=\"https:\/\/fpmon.github.io\/fingerprinting-monitor\/\">A Fingerprinting Monitor For Chrome (FPMON)<\/a><\/li>\n<li><a href=\"https:\/\/github.com\/freethenation\/DFPM\">Don't FingerPrint Me (DFPM)<\/a><\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<h2 id=\"keep-your-fingerprint-for-yourself\"><a class=\"toclink\" href=\"#keep-your-fingerprint-for-yourself\">Keep your fingerprint for yourself<\/a><\/h2>\n<p>FPD works in three basic phases, <em>monitoring<\/em>, <em>evaluation<\/em> and <em>reactive<\/em>.<\/p>\n<h3 id=\"monitoring-phase\"><a class=\"toclink\" href=\"#monitoring-phase\">Monitoring phase<\/a><\/h3>\n<p>If the FPD module is active, it locally logs all accesses to crucial JavaScript API endpoints during the monitoring phase. FPD injects custom wrapping code of all suspicious APIs (properties or methods) into the browser when a user visits a page. Hence, the extension observes API calls initiated by the website.<\/p>\n<p>FPD stores the number of accessed calls in the context of a displayed page. It contains all the metadata needed for evaluation. This metadata includes the number of calls for each logged endpoint with a corresponding argument value. Consequently, FPD can tell how often the visited web page called a particular endpoint and the arguments for these calls.<\/p>\n<h3 id=\"evaluation-phase\"><a class=\"toclink\" href=\"#evaluation-phase\">Evaluation phase<\/a><\/h3>\n<p>The evaluation phase starts whenever a new HTTP request occurs. FPD counts the fingerprinting score from the observed calls in the past. If the fingerprinting score is above a specified threshold, FPD considers a web page to perform fingerprinting. In this case, FPD warns the user with a notification.<\/p>\n<p><img alt=\"Notification example for Chrome (left) and Firefox (right).\" src=\"https:\/\/jshelter.org\/fpdetection\/notifications.png\"><\/p>\n<h3 id=\"reactive-phase\"><a class=\"toclink\" href=\"#reactive-phase\">Reactive phase<\/a><\/h3>\n<p>FPD blocks all subsequent asynchronous HTTP requests initiated by the fingerprinting page in the reaction phase. After that, FPD cleans all supported browser storage mechanisms such as <code>cookies<\/code> and <code>localStorage<\/code>. This measure prevents web pages from caching already extracted fingerprints for later transmission. To be clear, blocking of subsequent requests <strong>may result in breakage<\/strong> of the visited page. The user can create an exception for the page and add it to a whitelist in this situation. FPD is not active on whitelisted domains for browser fingerprinting. Users can opt in to be fingerprinted on the pages they trust.<\/p>\n<p>For now, we only support whitelisting of the visited domains. However, as fingerprinting is often performed for security reasons and it is more prevalent on login pages, we plan to evaluate if adding a specific behaviour for a URL is a better choice.<\/p>\n<h2 id=\"testing-fingerprinting-detection-in-the-wild\"><a class=\"toclink\" href=\"#testing-fingerprinting-detection-in-the-wild\">Testing fingerprinting detection in the wild<\/a><\/h2>\n<p>The line between fingerprinting and non-fingerprinting behaviour is very thin. Defining the fixed threshold can easily result in very doubtful results. Hence, the heuristics approach needs careful fine-tuning.<\/p>\n<p>We have made significant efforts to tailor the heuristics in such a way as to target mainly excessive fingerprints that identify users. We also focused on achieving a very low number of false positives for a better user experience. Because of the radical step of blocking subsequent requests, we must ensure that this blocking occurs only in the necessary cases when there is a high probability of fingerprinting.<\/p>\n<p>Additionally, it is very hard to differentiate between benign and fingerprinting usage of a JavaScript endpoint. From the heuristic point of view, setting a higher threshold for fingerprinting behaviour helps FPD reduce false positives. We decided to verify all these assumptions in practice. We tested FPD on real-world web pages and refined heuristics accordingly.<\/p>\n<p>In terms of methodology, we manually visited homepages and login pages of the top 100 websites from <a href=\"https:\/\/tranco-list.eu\/list\/23W9\/1000000\">the Tranco list<\/a>. For inaccessible websites at the time of testing, we replaced them with random websites from the top 200 list.<\/p>\n<p>With each access to the tested page, we wiped browser settings to ensure determinism of initial access. As the erasure removed any previously-stored identifier, the visited pages may have deployed fingerprinting scripts more aggressively to identify the user and reinstall the identifier.<\/p>\n<p>To boost the probability of fingerprinting even more, we switched off all protection mechanisms offered by the browser. However, we blocked third-party cookies because our previous experience suggests that the missing possibility to store a permanent identifier tempts trackers to start fingerprinting. To see an impact of a browser on the detection process, we used both <a href=\"https:\/\/www.google.com\/chrome\/\">Google Chrome<\/a> and <a href=\"https:\/\/www.mozilla.org\/en-US\/firefox\/\">Mozzila Firefox<\/a>.<\/p>\n<p>We needed the ground truth for web pages employing fingerprinting. We used <a href=\"https:\/\/fpmon.github.io\/fingerprinting-monitor\/\">FPMON<\/a> and <a href=\"https:\/\/github.com\/freethenation\/DFPM\">DFPM<\/a> to create the ground truth. We selected these two extensions because they are the only ones capable of real-time fingerprinting detection. FPMON reports fingerprinting pages with colour. We assigned Yellow colour 1 point and red colour 3 points. DFPM reports danger warnings. If DFPM reports one danger warning, we assign 1 point to the page. For a higher number of danger warnings, we assign 3 points to the page. Therefore, each page gets a fingerprinting score from 0 to 6.<\/p>\n<p>The score of 6 means that both extensions detected excessive fingerprinting behaviour. Web pages with the score of 6 certainly deploy fingerprinting, and FPD must detect such pages. FPD does not detect fingerprinting on <em>Google login<\/em> pages since FPD heuristics evaluate <em>Google login<\/em> pages just below its threshold. FPMON and DFPM detect fingerprinting on <em>Google login<\/em> pages but just above their thresholds.\n<em>Google login<\/em> pages occurred six times in total during testing. According to the methodology, these are false negatives. Nevertheless, the final fingerprint is not aggressive enough to provide enough entropy to identify most users uniquely.<\/p>\n<p>The score of 4 means that one extension detected fingerprinting and the other suspects. We classify these web pages as deploying fingerprinting. FPD managed to detect all web pages with two exceptions, <em>Facebook login<\/em> page and <code>yandex.ru<\/code>. Both are border-line cases that do not obtain enough entropy, similarly to <em>Google login<\/em> pages.<\/p>\n<p>The score of 3 means that one extension detected fingerprinting and the other did not detect fingerprinting at all. FPMON and DFPM treat browser fingerprinting differently, so we observed a few web pages with this score. It is questionable how to classify these pages when the reference extensions conflict. FPD does not detect most of these pages. FPD detected fingerprinting only on one of these pages, <em>Paypal login<\/em> page. We consider this detection justified as we manually checked FPD logs and found clear tracks of <a href=\"https:\/\/fingerprintjs.com\/blog\/canvas-fingerprinting\/\">canvas fingerprinting<\/a>. In contrast, FPMON reported negligible fingerprinting signs and did not recognise the fingerprinting attempt.<\/p>\n<p>The score of 2 means that both extensions suspect fingerprinting. We assume that web pages with this score may or may not fingerprint. Nevertheless, the fingerprint is likely not extensive enough to serve as a unique identifier if they do. Moreover, these web pages are prone to misclassification because they may be close to the heuristic threshold. FPD detected two web pages with this score, namely <em>Cloudflare login<\/em> page and <em>Washingtonpost login<\/em> page. A closer analysis revealed that both pages use canvas fingerprinting in conjunction with other fingerprinting methods. Interestingly, the reference extensions could not detect such fingerprinting with enough certainty.<\/p>\n<p>The score of 1 means that only one extension suspects fingerprinting, and the other extension does not detect anything. Similarly, for the score of 0 where neither reference extension does detect fingerprinting. According to our testing methodology, FPD should not detect such web pages as fingerprinting. However, FPD detected fingerprinting on <code>ebay.com<\/code>. Manual inspection showed that <code>ebay.com<\/code> did fingerprint indeed using canvas fingerprinting, <a href=\"https:\/\/fingerprintjs.com\/blog\/audio-fingerprinting\/\">audio fingerprinting<\/a> and other techniques. Amusingly, the string used by canvas fingerprint says <code>\"@Browsers~%fingGPRint$&amp;,&lt;canvas&gt;\"<\/code>.<\/p>\n<p>We decided to classify web pages as follows. We considered a page to be fingerprinting when its score is above or equal to 4. We did not count pages with the score of 3 or 2 as fingerprinting because they may not be engaged in fingerprinting in reality. It means that FPD may or may not detect such pages; we do not count such classification as an error in both cases. As discussed above, we manually inspected FPD in these situations. Finally, we consider anything below the score of 2 as not fingerprinting.<\/p>\n<ul>\n<li>\n<p>Number of fingerprinting web pages identified by <em>our ground truth<\/em>.<\/p>\n<ul>\n<li>Homepages: <strong>20<\/strong><\/li>\n<li>Login pages: <strong>34<\/strong><\/li>\n<\/ul>\n<\/li>\n<li>\n<p>Number of fingerprinting web pages identified by <em>JShelter<\/em>.<\/p>\n<ul>\n<li>Homepages: <strong>20<\/strong><\/li>\n<li>Login pages: <strong>30<\/strong><\/li>\n<\/ul>\n<\/li>\n<li>\n<p>Number of wrong identifications by <em>JShelter<\/em>.<\/p>\n<ul>\n<li>Homepages: <strong>2<\/strong><ul>\n<li>False positives: <strong>1<\/strong><\/li>\n<li>False negatives: <strong>1<\/strong><\/li>\n<\/ul>\n<\/li>\n<li>Login pages: <strong>7<\/strong><ul>\n<li>False positives: <strong>0<\/strong><\/li>\n<li>False negatives: <strong>7<\/strong><\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<p>At first glance, the numbers are close but not the same. Different heuristic thresholds of the extensions caused the main difference. However, as we found out, the ground truth is far from being flawless. We encountered many exceptions during testing and examined them in detail. In many cases, FPD detects fingerprinting, but the reference extensions do not. For <code>ebay.com<\/code>, neither FPMON nor DPFM identified the ongoing fingerprinting. We got a very low false positive rate and an acceptable false negative rate in terms of methodology.<\/p>\n<p>We also observed other notable behaviour during the testing. The asymmetry between detection on different browsers was minor but did occur sometimes. However, the difference between browsers should be minimal. We had implemented a mechanism that automatically recalculates heuristics to compensate unsupported APIs. Finally, note that blocking tools like <em>adblockers<\/em> can significantly reduce the number of positive detections. These tools use filter lists to block tracking scripts before their execution. Using FPD with a filter-based blocking tool can significantly improve user experience and privacy.<\/p>\n<h2 id=\"tldr\"><a class=\"toclink\" href=\"#tldr\">TL;DR<\/a><\/h2>\n<p>We have developed a JShelter module dedicated to browser fingerprinting detection called <em>FingerPrint Detector (FPD)<\/em>. FPD applies a heuristic approach to detect fingerprinting behaviour in real-time. FPD counts calls to JavaScript APIs often employed by fingerprinting scripts. When FPD detects fingerprinting attempt, it will (1) inform the user, (2) prevent uploading of the fingerprint to the server, (3) prevent storing the fingerprint for later usage. We tested FPD on the top 100 homepages and login pages. The results show that FPD identifies excessive fingerprinting behaviour and takes the necessary measures against fingerprint leakage.<\/p>\n<p>Nevertheless, there is always more work to do. The detection heuristics still have room for improvement. The real-world testing yields stimulating research questions. How to define an excessive fingerprint? What fingerprints should be blocked by the extension, and what fingerprints should not? What is the best behaviour (threshold) so that the users find the extension helpful?<\/p>","category":{"@attributes":{"term":"posts"}}},{"title":"Catch websites red-handed fingerprinting your browser","link":{"@attributes":{"href":"https:\/\/jshelter.org\/pt\/fpdetection\/","rel":"alternate"}},"published":"2022-01-14T14:00:00+01:00","updated":"2025-07-10T07:01:03+02:00","author":{"name":"The JShelter team"},"id":"tag:jshelter.org,2022-01-14:\/pt\/fpdetection\/","summary":"<p>We want to identify <a href=\"\/fingerprinting\/\">fingerprinting<\/a> attempts by counting the\nnumber of different APIs employed by a page, especially those not frequently\nused for benign purposes. This blog post introduces a new fingerprinting\nprotection mechanism - FingerPrint Detector (FPD) available in JShelter 0.6.\nThis tool allows users to gain more control \u2026<\/p>","content":"<p>We want to identify <a href=\"\/fingerprinting\/\">fingerprinting<\/a> attempts by counting the\nnumber of different APIs employed by a page, especially those not frequently\nused for benign purposes. This blog post introduces a new fingerprinting\nprotection mechanism - FingerPrint Detector (FPD) available in JShelter 0.6.\nThis tool allows users to gain more control over browser fingerprinting, which\nhas become an invisible threat to our privacy.<\/p>\n<h2 id=\"heuristics-as-a-template-for-the-fingerprinting-detection\"><a class=\"toclink\" href=\"#heuristics-as-a-template-for-the-fingerprinting-detection\">Heuristics as a template for the fingerprinting detection<\/a><\/h2>\n<p><a href=\"\/fingerprinting\/\">Mitigation of browser fingerprinting is not straightforward<\/a>.\nFPD does not attempt to prevent a script from taking a fingerprint. Neither does\nFPD falsify a fingerprint. Instead, FPD monitors the APIs that a web page\naccesses and detects suspicious activities. FPD quickly reacts in case of\nfingerprint extraction and blocks further web page communication, including\nstoring information.<\/p>\n<p>Several studies over the last decade detected fingerprinting attempts. Many of\nthem used a simple heuristic approach to create a set of conditions. If these\nconditions are met, suspicious activity is detected. Studies like <a href=\"https:\/\/securehomes.esat.kuleuven.be\/~gacar\/persistent\/the_web_never_forgets.pdf\">The Web Never\nForgets<\/a>\nand <a href=\"https:\/\/www.cs.princeton.edu\/~arvindn\/publications\/OpenWPM_1_million_site_tracking_measurement.pdf\">A 1-million-site Measurement and\nAnalysis<\/a>\nused this approach to measure the real-world occurrence of web tracking. At the\nsame time, they verified the usability of heuristic-based detection. They found\nthe detection effective with a very low false-positive rate. The most\nchallenging part of this approach is a careful selection of detection\nconditions. Later, studies began to experiment with more sophisticated methods.\nFor example, <a href=\"https:\/\/web.cs.ucdavis.edu\/~zubair\/files\/fpinspector-sp2021.pdf\">Fingerprinting the\nFingerprinters<\/a>\nused machine learning for fingerprinting detection. They managed to achieve even\nbetter precision, but at the cost of demanding model training.<\/p>\n<p>For JShelter, we settled down on using a simple heuristic approach, but with a\nlittle twist to it. Internet technology is constantly changing, so we want to\nmake our heuristics as flexible as possible. Instead of hard-coding them, we\npropose a declarative way to describe the heuristics. This concept allows us to\nmake changes with a new release and progressively <a href=\"\/crawling_results\/\">adapt to the latest changes\nin the field<\/a>. For this purpose, we defined JSON\nconfiguration files, which contain all the information required for\nfingerprinting detection. As these files make an input for our\nevaluation\/detection logic, their content directly reflects how JShelter should\nevaluate websites in terms of fingerprinting.<\/p>\n<p>On closer inspection, the configuration files contain two basic types of entries.\nFirstly, they define JavaScript endpoints, which are relevant for fingerprinting\ndetection. Secondly, they group related endpoints. For example, we group\nendpoints according to their semantic properties. Imagine that there are two\ndifferent endpoints. Both provide hardware information about the device. We can\nassign both endpoints to a group that covers access to hardware properties in\nthis scenario. The configuration allows clustering groups to other groups and\ncreating a hierarchy of groups. Ultimately, the configuration is a tree-like\nstructure whose evaluation can detect browser fingerprinting.<\/p>\n<p>The whole evaluation process dynamically observes the API calls performed by a\nweb page. Note that we analyse the calls themselves. Hence, the dynamic analysis\novercomes any obfuscation of fingerprinting scripts.<\/p>\n<p>The current heuristics are based on many prior studies, <a href=\"\/crawling_results\/\">our own\ncrawl<\/a>, and available tools focused on browser\nfingerprinting.<\/p>\n<ul>\n<li>\n<p>We extracted and modified detection rules from studies like:<\/p>\n<\/li>\n<li>\n<p><a href=\"https:\/\/web.cs.ucdavis.edu\/~zubair\/files\/fpinspector-sp2021.pdf\">Fingerprinting the\nFingerprinters<\/a><\/p>\n<\/li>\n<li><a href=\"https:\/\/www.cs.princeton.edu\/~arvindn\/publications\/OpenWPM_1_million_site_tracking_measurement.pdf\">A 1-million-site Measurement and\nAnalysis<\/a><\/li>\n<li><a href=\"https:\/\/securehomes.esat.kuleuven.be\/~gacar\/persistent\/the_web_never_forgets.pdf\">The Web Never\nForgets<\/a><\/li>\n<li>\n<p>We reflected traits of known fingerprinting tools like:<\/p>\n<\/li>\n<li>\n<p><a href=\"https:\/\/github.com\/fingerprintjs\">FingerprintJS<\/a><\/p>\n<\/li>\n<li><a href=\"https:\/\/amiunique.org\/\">Am I Unique<\/a><\/li>\n<li><a href=\"https:\/\/coveryourtracks.eff.org\/\">Cover Your Tracks<\/a><\/li>\n<li>\n<p>We utilized knowledge of existing detection tools like:<\/p>\n<\/li>\n<li>\n<p><a href=\"https:\/\/fpmon.github.io\/fingerprinting-monitor\/\">A Fingerprinting Monitor For Chrome\n(FPMON)<\/a><\/p>\n<\/li>\n<li><a href=\"https:\/\/github.com\/freethenation\/DFPM\">Don't FingerPrint Me (DFPM)<\/a><\/li>\n<\/ul>\n<h2 id=\"keep-your-fingerprint-for-yourself\"><a class=\"toclink\" href=\"#keep-your-fingerprint-for-yourself\">Keep your fingerprint for yourself<\/a><\/h2>\n<p>FPD works in three basic phases, <em>monitoring<\/em>, <em>evaluation<\/em> and <em>reactive<\/em>.<\/p>\n<h3 id=\"monitoring-phase\"><a class=\"toclink\" href=\"#monitoring-phase\">Monitoring phase<\/a><\/h3>\n<p>If the FPD module is active, it locally logs all accesses to crucial JavaScript\nAPI endpoints during the monitoring phase. FPD injects custom wrapping code of\nall suspicious APIs (properties or methods) into the browser when a user visits\na page. Hence, the extension observes API calls initiated by the website.<\/p>\n<p>FPD stores the number of accessed calls in the context of a displayed page. It\ncontains all the metadata needed for evaluation. This metadata includes the\nnumber of calls for each logged endpoint with a corresponding argument value.\nConsequently, FPD can tell how often the visited web page called a particular\nendpoint and the arguments for these calls.<\/p>\n<h3 id=\"evaluation-phase\"><a class=\"toclink\" href=\"#evaluation-phase\">Evaluation phase<\/a><\/h3>\n<p>The evaluation phase starts whenever a new HTTP request occurs. FPD counts the\nfingerprinting score from the observed calls in the past. If the fingerprinting\nscore is above a specified threshold, FPD considers a web page to perform\nfingerprinting. In this case, FPD warns the user with a notification.<\/p>\n<p><img alt=\"Notification example for Chrome (left) and Firefox\n(right).\" src=\"https:\/\/jshelter.org\/fpdetection\/notifications.png\"><\/p>\n<h3 id=\"reactive-phase\"><a class=\"toclink\" href=\"#reactive-phase\">Reactive phase<\/a><\/h3>\n<p>FPD blocks all subsequent asynchronous HTTP requests initiated by the\nfingerprinting page in the reaction phase. After that, FPD cleans all supported\nbrowser storage mechanisms such as <code>cookies<\/code> and <code>localStorage<\/code>. This measure\nprevents web pages from caching already extracted fingerprints for later\ntransmission. To be clear, blocking of subsequent requests <strong>may result in\nbreakage<\/strong> of the visited page. The user can create an exception for the page\nand add it to a whitelist in this situation. FPD is not active on whitelisted\ndomains for browser fingerprinting. Users can opt in to be fingerprinted on the\npages they trust.<\/p>\n<p>For now, we only support whitelisting of the visited domains. However, as\nfingerprinting is often performed for security reasons and it is more prevalent\non login pages, we plan to evaluate if adding a specific behaviour for a URL is\na better choice.<\/p>\n<h2 id=\"testing-fingerprinting-detection-in-the-wild\"><a class=\"toclink\" href=\"#testing-fingerprinting-detection-in-the-wild\">Testing fingerprinting detection in the wild<\/a><\/h2>\n<p>The line between fingerprinting and non-fingerprinting behaviour is very thin.\nDefining the fixed threshold can easily result in very doubtful results. Hence,\nthe heuristics approach needs careful fine-tuning.<\/p>\n<p>We have made significant efforts to tailor the heuristics in such a way as to\ntarget mainly excessive fingerprints that identify users. We also focused on\nachieving a very low number of false positives for a better user experience.\nBecause of the radical step of blocking subsequent requests, we must ensure that\nthis blocking occurs only in the necessary cases when there is a high\nprobability of fingerprinting.<\/p>\n<p>Additionally, it is very hard to differentiate between benign and fingerprinting\nusage of a JavaScript endpoint. From the heuristic point of view, setting a\nhigher threshold for fingerprinting behaviour helps FPD reduce false positives.\nWe decided to verify all these assumptions in practice. We tested FPD on\nreal-world web pages and refined heuristics accordingly.<\/p>\n<p>In terms of methodology, we manually visited homepages and login pages of the top\n100 websites from <a href=\"https:\/\/tranco-list.eu\/list\/23W9\/1000000\">the Tranco list<\/a>.\nFor inaccessible websites at the time of testing, we replaced them with random\nwebsites from the top 200 list.<\/p>\n<p>With each access to the tested page, we wiped browser settings to ensure\ndeterminism of initial access. As the erasure removed any previously-stored\nidentifier, the visited pages may have deployed fingerprinting scripts more\naggressively to identify the user and reinstall the identifier.<\/p>\n<p>To boost the probability of fingerprinting even more, we switched off all\nprotection mechanisms offered by the browser. However, we blocked third-party\ncookies because our previous experience suggests that the missing possibility to\nstore a permanent identifier tempts trackers to start fingerprinting. To see an\nimpact of a browser on the detection process, we used both <a href=\"https:\/\/www.google.com\/chrome\/\">Google\nChrome<\/a> and <a href=\"https:\/\/www.mozilla.org\/en-US\/firefox\/\">Mozzila\nFirefox<\/a>.<\/p>\n<p>We needed the ground truth for web pages employing fingerprinting. We used\n<a href=\"https:\/\/fpmon.github.io\/fingerprinting-monitor\/\">FPMON<\/a> and\n<a href=\"https:\/\/github.com\/freethenation\/DFPM\">DFPM<\/a> to create the ground truth. We\nselected these two extensions because they are the only ones capable of\nreal-time fingerprinting detection. FPMON reports fingerprinting pages with\ncolour. We assigned Yellow colour 1 point and red colour 3 points. DFPM reports\ndanger warnings. If DFPM reports one danger warning, we assign 1 point to the\npage. For a higher number of danger warnings, we assign 3 points to the page.\nTherefore, each page gets a fingerprinting score from 0 to 6.<\/p>\n<p>The score of 6 means that both extensions detected excessive fingerprinting\nbehaviour. Web pages with the score of 6 certainly deploy fingerprinting, and\nFPD must detect such pages. FPD does not detect fingerprinting on <em>Google login<\/em>\npages since FPD heuristics evaluate <em>Google login<\/em> pages just below its\nthreshold. FPMON and DFPM detect fingerprinting on <em>Google login<\/em> pages but just\nabove their thresholds. <em>Google login<\/em> pages occurred six times in total during\ntesting. According to the methodology, these are false negatives. Nevertheless,\nthe final fingerprint is not aggressive enough to provide enough entropy to\nidentify most users uniquely.<\/p>\n<p>The score of 4 means that one extension detected fingerprinting and the other\nsuspects. We classify these web pages as deploying fingerprinting. FPD managed\nto detect all web pages with two exceptions, <em>Facebook login<\/em> page and\n<code>yandex.ru<\/code>. Both are border-line cases that do not obtain enough entropy,\nsimilarly to <em>Google login<\/em> pages.<\/p>\n<p>The score of 3 means that one extension detected fingerprinting and the other did\nnot detect fingerprinting at all. FPMON and DFPM treat browser fingerprinting\ndifferently, so we observed a few web pages with this score. It is questionable\nhow to classify these pages when the reference extensions conflict. FPD does not\ndetect most of these pages. FPD detected fingerprinting only on one of these\npages, <em>Paypal login<\/em> page. We consider this detection justified as we manually\nchecked FPD logs and found clear tracks of <a href=\"https:\/\/fingerprintjs.com\/blog\/canvas-fingerprinting\/\">canvas\nfingerprinting<\/a>. In\ncontrast, FPMON reported negligible fingerprinting signs and did not recognise\nthe fingerprinting attempt.<\/p>\n<p>The score of 2 means that both extensions suspect fingerprinting. We assume that\nweb pages with this score may or may not fingerprint. Nevertheless, the\nfingerprint is likely not extensive enough to serve as a unique identifier if\nthey do. Moreover, these web pages are prone to misclassification because they\nmay be close to the heuristic threshold. FPD detected two web pages with this\nscore, namely <em>Cloudflare login<\/em> page and <em>Washingtonpost login<\/em> page. A closer\nanalysis revealed that both pages use canvas fingerprinting in conjunction with\nother fingerprinting methods. Interestingly, the reference extensions could not\ndetect such fingerprinting with enough certainty.<\/p>\n<p>The score of 1 means that only one extension suspects fingerprinting, and the\nother extension does not detect anything. Similarly, for the score of 0 where\nneither reference extension does detect fingerprinting. According to our testing\nmethodology, FPD should not detect such web pages as fingerprinting. However,\nFPD detected fingerprinting on <code>ebay.com<\/code>. Manual inspection showed that\n<code>ebay.com<\/code> did fingerprint indeed using canvas fingerprinting, <a href=\"https:\/\/fingerprintjs.com\/blog\/audio-fingerprinting\/\">audio\nfingerprinting<\/a> and other\ntechniques. Amusingly, the string used by canvas fingerprint says\n<code>\"@Browsers~%fingGPRint$&amp;,&lt;canvas&gt;\"<\/code>.<\/p>\n<p>We decided to classify web pages as follows. We considered a page to be\nfingerprinting when its score is above or equal to 4. We did not count pages\nwith the score of 3 or 2 as fingerprinting because they may not be engaged in\nfingerprinting in reality. It means that FPD may or may not detect such pages;\nwe do not count such classification as an error in both cases. As discussed\nabove, we manually inspected FPD in these situations. Finally, we consider\nanything below the score of 2 as not fingerprinting.<\/p>\n<ul>\n<li>\n<p>Number of fingerprinting web pages identified by <em>our ground truth<\/em>.<\/p>\n<\/li>\n<li>\n<p>Homepages: <strong>20<\/strong><\/p>\n<\/li>\n<li>Login pages: <strong>34<\/strong><\/li>\n<li>\n<p>Number of fingerprinting web pages identified by <em>JShelter<\/em>.<\/p>\n<\/li>\n<li>\n<p>Homepages: <strong>20<\/strong><\/p>\n<\/li>\n<li>Login pages: <strong>30<\/strong><\/li>\n<li>\n<p>Number of wrong identifications by <em>JShelter<\/em>.<\/p>\n<\/li>\n<li>\n<p>Homepages: <strong>2<\/strong><\/p>\n<ul>\n<li>False positives: <strong>1<\/strong><\/li>\n<li>False negatives: <strong>1<\/strong><\/li>\n<\/ul>\n<\/li>\n<li>Login pages: <strong>7<\/strong><ul>\n<li>False positives: <strong>0<\/strong><\/li>\n<li>False negatives: <strong>7<\/strong><\/li>\n<\/ul>\n<\/li>\n<\/ul>\n<p>At first glance, the numbers are close but not the same. Different heuristic\nthresholds of the extensions caused the main difference. However, as we found\nout, the ground truth is far from being flawless. We encountered many exceptions\nduring testing and examined them in detail. In many cases, FPD detects\nfingerprinting, but the reference extensions do not. For <code>ebay.com<\/code>, neither\nFPMON nor DPFM identified the ongoing fingerprinting. We got a very low false\npositive rate and an acceptable false negative rate in terms of methodology.<\/p>\n<p>We also observed other notable behaviour during the testing. The asymmetry\nbetween detection on different browsers was minor but did occur sometimes.\nHowever, the difference between browsers should be minimal. We had implemented a\nmechanism that automatically recalculates heuristics to compensate unsupported\nAPIs. Finally, note that blocking tools like <em>adblockers<\/em> can significantly\nreduce the number of positive detections. These tools use filter lists to block\ntracking scripts before their execution. Using FPD with a filter-based blocking\ntool can significantly improve user experience and privacy.<\/p>\n<h2 id=\"tldr\"><a class=\"toclink\" href=\"#tldr\">TL;DR<\/a><\/h2>\n<p>We have developed a JShelter module dedicated to browser fingerprinting detection\ncalled <em>FingerPrint Detector (FPD)<\/em>. FPD applies a heuristic approach to detect\nfingerprinting behaviour in real-time. FPD counts calls to JavaScript APIs often\nemployed by fingerprinting scripts. When FPD detects fingerprinting attempt, it\nwill (1) inform the user, (2) prevent uploading of the fingerprint to the\nserver, (3) prevent storing the fingerprint for later usage. We tested FPD on\nthe top 100 homepages and login pages. The results show that FPD identifies\nexcessive fingerprinting behaviour and takes the necessary measures against\nfingerprint leakage.<\/p>\n<p>Nevertheless, there is always more work to do. The detection heuristics still\nhave room for improvement. The real-world testing yields stimulating research\nquestions. How to define an excessive fingerprint? What fingerprints should be\nblocked by the extension, and what fingerprints should not? What is the best\nbehaviour (threshold) so that the users find the extension helpful?<\/p>","category":{"@attributes":{"term":"pt"}}},{"title":"Browser fingerprinting and JShelter","link":{"@attributes":{"href":"https:\/\/jshelter.org\/fingerprinting\/","rel":"alternate"}},"published":"2022-01-14T13:00:00+01:00","updated":"2023-04-14T14:00:00+02:00","author":{"name":"The JShelter team"},"id":"tag:jshelter.org,2022-01-14:\/fingerprinting\/","summary":"<p>This post introduces <a href=\"https:\/\/arxiv.org\/pdf\/1905.01051.pdf\">browser fingerprinting<\/a> and anti-fingerprinting mechanisms. We explain what JShelter implements and the strengths and downsides of the anti-fingerprinting mechanisms.<\/p>\n<p>In short, browser fingerprinting is a stateless tracking method vastly prevalent on the web in recent years. Similarly to a human <em>fingerprint<\/em>, browser fingerprinting tries to find a \u2026<\/p>","content":"<p>This post introduces <a href=\"https:\/\/arxiv.org\/pdf\/1905.01051.pdf\">browser fingerprinting<\/a> and anti-fingerprinting mechanisms. We explain what JShelter implements and the strengths and downsides of the anti-fingerprinting mechanisms.<\/p>\n<p>In short, browser fingerprinting is a stateless tracking method vastly prevalent on the web in recent years. Similarly to a human <em>fingerprint<\/em>, browser fingerprinting tries to find a set of features that make (almost) every fingerprint unique and the browser uniquely identifiable. <\/p>\n<p>The fingerprinting features contain various sources, mostly accessible through browser application interfaces, APIs. The APIs provide essential functionality for modern websites; however, they leak sensitive information about the browser, operating system, or device itself. Many different devices are connected to the internet \u2014 most of them with a very specific configuration. Leaking hardware and software properties may result in a sufficiently identifiable browser fingerprint. Furthermore, do not forget about the possibility of leaking information that may uncover vulnerabilities of your system for the sake of potential attackers.<\/p>\n<p>At first glance, browser fingerprinting seems to be a great evil in the world full of privacy concerns. However, we should be aware of two distinct purposes for using browser fingerprinting.<\/p>\n<p>Firstly, there is <em>a negative or destructive use case<\/em> during which websites profile cross-domain user activities without user consent. Trackers use the fingerprint as a cross-domain identifier.<\/p>\n<p>Secondly, there is <em>a positive or constructive use case<\/em>, where websites tend to collect information about your system to improve the usability or security of their application. For example, applications can recommend installing critical security updates based on your system properties. Some websites collect browser fingerprints to verify known devices of their users to prevent fraud. The JShelter.org website offers the download link in the extension store based on the user agent.<\/p>\n<p>Many browser vendors have already introduced changes to the cookie policy. Browsers such as Firefox, Safari, and Brave block third-party cookies, often abused for stateful tracking. Chrome developers <a href=\"https:\/\/blog.google\/products\/chrome\/updated-timeline-privacy-sandbox-milestones\/\">recently announced<\/a> that they will jump on the bandwagon too. The counter-measures against stateful tracking techniques result in a substantial increase in the usage of browser fingerprinting. <\/p>\n<h2 id=\"counter-measures-to-browser-fingerprinting\"><a class=\"toclink\" href=\"#counter-measures-to-browser-fingerprinting\">Counter-measures to browser fingerprinting<\/a><\/h2>\n<p>Unlike cookies, browser fingerprinting does not require storing an identifier on the device. Instead, the identifier is recomputed with each visited page. Once the fingerprint is obtained, it can be sent to a tracking server in a subsequent request. Moreover, the whole process of fingerprint extraction is invisible to a user. There are many sources of potentially identifiable information from which a fingerprint can be constructed. A fingerprint is considered <em>passive<\/em> when it contains natively accessible information from HTTP headers or network traffic. On the other hand, <em>active<\/em> fingerprint runs JavaScript code to retrieve data from browser APIs. JShelter focuses on detecting and preventing active fingerprinting.<\/p>\n<p>Nowadays, there are many solutions to mitigate the effects of browser fingerprinting to improve internet privacy.<\/p>\n<p><a href=\"\/farbling\/\">Modifying the content of fingerprints<\/a> is a valid choice to resist a fingerprinting attempt. However, each modification may create an inconsistency that may improve the fingerprintability of the browser. Many protection tools use predefined or real fingerprints instead of the user's one to counter this issue.<\/p>\n<p>Another approach is to create homogeneous fingerprints. If every fingerprint is the same, there is no way to tell the users behind the browsers apart. The leading representative of this approach is the <a href=\"https:\/\/www.torproject.org\/\">Tor browser<\/a>. Level 3 of JShelter aims to create a homogenous fingerprint. Unfortunately, homogeneous fingerprints have an inherent downside of following specific rules to be effective. Most importantly, the effectiveness of the approach depends on the broad coverage of the blocked APIs and the size of the population employing the counter-measures. All browsers with the same fingerprint form an anonymity set. An observer cannot distinguish between browsers in the anonymity set. With every missed fingerprintable attribute, the anonymity set breaks into smaller sets. For example, the Tor browser strongly recommends using a specific window size. Suppose you use a window size different from all other Tor browser users. In that case, a fingerprinter can identify you solely by this attribute. Also, keep in mind that Tor browser hides your IP address, which JShelter does not do. Hence we see Level 3 protections more as leak prevention than an anti-fingerprinting measure. If you like the idea of homogeneous fingerprints, install Tor browser.<\/p>\n<p><a href=\"https:\/\/brave.com\/\">Brave browser<\/a> modifies the readings from APIs misused to create fingerprints. Nevertheless, the goal is to <a href=\"\/farbling\/\">create a unique fingerprint for each domain and session<\/a>. As the fingerprint changes for every visited domain, its use for linking cross-domain activities is smaller. JShelter already <a href=\"\/farbling\/\">implements the farbling protection of Brave<\/a> and uses them as the default anti-fingerprinting approach.<\/p>\n<p>Recently, we added <a href=\"\/fpdetection\/\">Fingerprint detector (FPD)<\/a> to JShelter. FPD does not modify the fingerprint. Instead, FPD monitors APIs that are often misused by fingerprinters. When FPD detects a fingerprinting attempt, it warns the user. You can also allow FPD to block all subsequent transmissions of the page and remove stored data to prevent storing the fingerprint and loading it after a page reload. Hence, FPD allows the page to compute the fingerprint but (if allowed) blocks the page from uploading the fingerprint away from your computer.<\/p>\n<p>Other approaches include blocking JavaScript code from suspicious sources or decreasing the surface of browser APIs with explicit permission control. Extensions like <a href=\"https:\/\/noscript.net\/\">NoScript Suite<\/a> are perfect complementary measures to JShelter.<\/p>\n<p>Despite all the efforts, there is no ultimate approach that can prevent fingerprinting while keeping a high level of usability in mind. Every approach has its strengths and weaknesses, so the challenge is to find a balance between privacy and usability.<\/p>","category":{"@attributes":{"term":"posts"}}},{"title":"Browser fingerprinting and JShelter","link":{"@attributes":{"href":"https:\/\/jshelter.org\/pt\/fingerprinting\/","rel":"alternate"}},"published":"2022-01-14T13:00:00+01:00","updated":"2023-04-14T14:00:00+02:00","author":{"name":"The JShelter team"},"id":"tag:jshelter.org,2022-01-14:\/pt\/fingerprinting\/","summary":"<p>This post introduces <a href=\"https:\/\/arxiv.org\/pdf\/1905.01051.pdf\">browser\nfingerprinting<\/a> and anti-fingerprinting\nmechanisms. We explain what JShelter implements and the strengths and downsides\nof the anti-fingerprinting mechanisms.<\/p>\n<p>In short, browser fingerprinting is a stateless tracking method vastly prevalent\non the web in recent years. Similarly to a human <em>fingerprint<\/em>, browser\nfingerprinting tries to find a \u2026<\/p>","content":"<p>This post introduces <a href=\"https:\/\/arxiv.org\/pdf\/1905.01051.pdf\">browser\nfingerprinting<\/a> and anti-fingerprinting\nmechanisms. We explain what JShelter implements and the strengths and downsides\nof the anti-fingerprinting mechanisms.<\/p>\n<p>In short, browser fingerprinting is a stateless tracking method vastly prevalent\non the web in recent years. Similarly to a human <em>fingerprint<\/em>, browser\nfingerprinting tries to find a set of features that make (almost) every\nfingerprint unique and the browser uniquely identifiable.<\/p>\n<p>The fingerprinting features contain various sources, mostly accessible through\nbrowser application interfaces, APIs. The APIs provide essential functionality\nfor modern websites; however, they leak sensitive information about the browser,\noperating system, or device itself. Many different devices are connected to the\ninternet \u2014 most of them with a very specific configuration. Leaking hardware and\nsoftware properties may result in a sufficiently identifiable browser\nfingerprint. Furthermore, do not forget about the possibility of leaking\ninformation that may uncover vulnerabilities of your system for the sake of\npotential attackers.<\/p>\n<p>At first glance, browser fingerprinting seems to be a great evil in the world\nfull of privacy concerns. However, we should be aware of two distinct purposes\nfor using browser fingerprinting.<\/p>\n<p>Firstly, there is <em>a negative or destructive use case<\/em> during which websites\nprofile cross-domain user activities without user consent. Trackers use the\nfingerprint as a cross-domain identifier.<\/p>\n<p>Secondly, there is <em>a positive or constructive use case<\/em>, where websites tend to\ncollect information about your system to improve the usability or security of\ntheir application. For example, applications can recommend installing critical\nsecurity updates based on your system properties. Some websites collect browser\nfingerprints to verify known devices of their users to prevent fraud. The\nJShelter.org website offers the download link in the extension store based on\nthe user agent.<\/p>\n<p>Many browser vendors have already introduced changes to the cookie policy.\nBrowsers such as Firefox, Safari, and Brave block third-party cookies, often\nabused for stateful tracking. Chrome developers <a href=\"https:\/\/blog.google\/products\/chrome\/updated-timeline-privacy-sandbox-milestones\/\">recently\nannounced<\/a>\nthat they will jump on the bandwagon too. The counter-measures against stateful\ntracking techniques result in a substantial increase in the usage of browser\nfingerprinting.<\/p>\n<h2 id=\"counter-measures-to-browser-fingerprinting\"><a class=\"toclink\" href=\"#counter-measures-to-browser-fingerprinting\">Counter-measures to browser fingerprinting<\/a><\/h2>\n<p>Unlike cookies, browser fingerprinting does not require storing an identifier on\nthe device. Instead, the identifier is recomputed with each visited page. Once\nthe fingerprint is obtained, it can be sent to a tracking server in a subsequent\nrequest. Moreover, the whole process of fingerprint extraction is invisible to a\nuser. There are many sources of potentially identifiable information from which\na fingerprint can be constructed. A fingerprint is considered <em>passive<\/em> when it\ncontains natively accessible information from HTTP headers or network traffic.\nOn the other hand, <em>active<\/em> fingerprint runs JavaScript code to retrieve data\nfrom browser APIs. JShelter focuses on detecting and preventing active\nfingerprinting.<\/p>\n<p>Nowadays, there are many solutions to mitigate the effects of browser\nfingerprinting to improve internet privacy.<\/p>\n<p><a href=\"\/farbling\/\">Modifying the content of fingerprints<\/a> is a valid choice to resist a\nfingerprinting attempt. However, each modification may create an inconsistency\nthat may improve the fingerprintability of the browser. Many protection tools\nuse predefined or real fingerprints instead of the user's one to counter this\nissue.<\/p>\n<p>Another approach is to create homogeneous fingerprints. If every fingerprint is\nthe same, there is no way to tell the users behind the browsers apart. The\nleading representative of this approach is the <a href=\"https:\/\/www.torproject.org\/\">Tor\nbrowser<\/a>. Level 3 of JShelter aims to create a\nhomogenous fingerprint. Unfortunately, homogeneous fingerprints have an inherent\ndownside of following specific rules to be effective. Most importantly, the\neffectiveness of the approach depends on the broad coverage of the blocked APIs\nand the size of the population employing the counter-measures. All browsers with\nthe same fingerprint form an anonymity set. An observer cannot distinguish\nbetween browsers in the anonymity set. With every missed fingerprintable\nattribute, the anonymity set breaks into smaller sets. For example, the Tor\nbrowser strongly recommends using a specific window size. Suppose you use a\nwindow size different from all other Tor browser users. In that case, a\nfingerprinter can identify you solely by this attribute. Also, keep in mind that\nTor browser hides your IP address, which JShelter does not do. Hence we see\nLevel 3 protections more as leak prevention than an anti-fingerprinting measure.\nIf you like the idea of homogeneous fingerprints, install Tor browser.<\/p>\n<p><a href=\"https:\/\/brave.com\/\">Brave browser<\/a> modifies the readings from APIs misused to\ncreate fingerprints. Nevertheless, the goal is to <a href=\"\/farbling\/\">create a unique fingerprint\nfor each domain and session<\/a>. As the fingerprint changes for every\nvisited domain, its use for linking cross-domain activities is smaller. JShelter\nalready <a href=\"\/farbling\/\">implements the farbling protection of Brave<\/a> and uses them\nas the default anti-fingerprinting approach.<\/p>\n<p>Recently, we added <a href=\"\/fpdetection\/\">Fingerprint detector (FPD)<\/a> to JShelter. FPD\ndoes not modify the fingerprint. Instead, FPD monitors APIs that are often\nmisused by fingerprinters. When FPD detects a fingerprinting attempt, it warns\nthe user. You can also allow FPD to block all subsequent transmissions of the\npage and remove stored data to prevent storing the fingerprint and loading it\nafter a page reload. Hence, FPD allows the page to compute the fingerprint but\n(if allowed) blocks the page from uploading the fingerprint away from your\ncomputer.<\/p>\n<p>Other approaches include blocking JavaScript code from suspicious sources or\ndecreasing the surface of browser APIs with explicit permission control.\nExtensions like <a href=\"https:\/\/noscript.net\/\">NoScript Suite<\/a> are perfect\ncomplementary measures to JShelter.<\/p>\n<p>Despite all the efforts, there is no ultimate approach that can prevent\nfingerprinting while keeping a high level of usability in mind. Every approach\nhas its strengths and weaknesses, so the challenge is to find a balance between\nprivacy and usability.<\/p>","category":{"@attributes":{"term":"pt"}}},{"title":"\"RESULTS: Measurement of JavaScript API usage on the web\"","link":{"@attributes":{"href":"https:\/\/jshelter.org\/crawling_results\/","rel":"alternate"}},"published":"2022-01-14T12:00:00+01:00","updated":"2024-03-26T12:54:03+01:00","author":{"name":"The JShelter team"},"id":"tag:jshelter.org,2022-01-14:\/crawling_results\/","summary":"<p>A <a href=\"\/crawling\/\">previous blogpost<\/a> introduced a methodology to measure the usage of JavaScript APIs on the web by crawling the web. \nThe starting point of our web crawling research is the <a href=\"https:\/\/www.peteresnyder.com\/static\/papers\/improving-web-privacy-and-security-thesis.pdf\">work of Peter Snyder<\/a>. Most importantly,  we wanted to compare the differences in JS API usage between browsers with and \u2026<\/p>","content":"<p>A <a href=\"\/crawling\/\">previous blogpost<\/a> introduced a methodology to measure the usage of JavaScript APIs on the web by crawling the web. \nThe starting point of our web crawling research is the <a href=\"https:\/\/www.peteresnyder.com\/static\/papers\/improving-web-privacy-and-security-thesis.pdf\">work of Peter Snyder<\/a>. Most importantly,  we wanted to compare the differences in JS API usage between browsers with and without privacy extension (e.g., uBlock Origin). We have finished the development of the <a href=\"https:\/\/github.com\/martinbednar\/web_crawler\">crawling tool<\/a>. We crawled thousands of websites and collected JavaScript calls on the visited web pages.<\/p>\n<p>This blog post introduces the methodology and results of the crawling.<\/p>\n<h2 id=\"introduction\"><a class=\"toclink\" href=\"#introduction\">Introduction<\/a><\/h2>\n<p>When a user opens a webpage in a JavaScript-enabled web browser, that webpage can access various APIs supported by the web browser.\nThe webpage can read, for example, the value of performance.now(), battery status or data from sensors (when available). All these values can be misused to create a <a href=\"https:\/\/arxiv.org\/pdf\/1905.01051.pdf\">device fingerprint<\/a> that can be used to identify the user.<\/p>\n<p>We aim to research how JS APIs are used on websites. The main research questions are:\n* What APIs do websites use?\n* How many APIs does a website use?\n* What endpoints do websites access most often?\n* How many JS calls are made?\n* What are the differences in results in all the previous questions with an active privacy web-browser extension (e.g., uBlock Origin)? For example, What JS calls are blocked by uBlock Origin?<\/p>\n<p>The answers to these questions should help us better understand websites' behaviour. Based on the obtained data, we want to define the suspicious behaviour indicating that the website is trying to fingerprint the user's device.<\/p>\n<p>We plan to keep the <a href=\"\/fpdetection\/\">anti-fingerprint mechanism<\/a> updated based on derived heuristics. The heuristics are derived from the following statistics:<\/p>\n<ul>\n<li>How many APIs and endpoints does the website access?<\/li>\n<li>How many JS calls did the website make?<\/li>\n<li>Is the number and combination of JS APIs calls suspicious?<\/li>\n<\/ul>\n<p>When our anti-fingerprint mechanism detects suspicious combinations or a high number of used APIs, it can block communication with the webpage.<\/p>\n<h2 id=\"crawler\"><a class=\"toclink\" href=\"#crawler\">Crawler<\/a><\/h2>\n<p>We have developed <a href=\"https:\/\/github.com\/martinbednar\/web_crawler\/\">Web crawler<\/a> - a tool for automatically visiting websites from a given list and collecting JavaScript calls made by the website.\nOur Web crawler is based on the <a href=\"https:\/\/github.com\/openwpm\/OpenWPM\">OpenWPM<\/a> platform. A modified web browser extension <a href=\"https:\/\/github.com\/pes10k\/web-api-manager\">Web API Manager<\/a> collects statistics on called JS APIs.<\/p>\n<p>The crawling process can be described in the following steps:<\/p>\n<ol>\n<li>The python script <code>start_docker_runs.py<\/code> launches the Docker image <a href=\"https:\/\/hub.docker.com\/repository\/docker\/martan305\/web_crawler\">martan305\/web_crawler<\/a>. The parameters set in the Python startup script are given to the Docker container as environmental variables.<\/li>\n<li>If a privacy extension is required (in the script parameter), uBlock Origin will be installed as soon as the web browser is started.<\/li>\n<li>Python script running in the container visits web pages from a given list one by one.<\/li>\n<li>The browser waits for 30 seconds on each page. The customised web browser extension Web API Manager intercepts JS calls and stores the stats into the SQLite database.<\/li>\n<li>When the script finishes the crawl of assigned pages, it checks if there is a batch of pages not yet visited. If there is an unprocessed batch, the crawling continues with step 3 and the new list of web pages to visit. If the crawler visited all sites, the crawling ends.<\/li>\n<\/ol>\n<p>For now, we only visited the homepages because we wanted to visit as many different websites as possible. In the future, we plan to launch long-term crawling, which will include subpages. In particular, we want to focus on visiting login pages, where we expect fingerprint scripts to be included. Then, we can compare API calls on login pages and other pages.<\/p>\n<p>Afterwards, we analyse the collected data with a standalone <a href=\"https:\/\/github.com\/martinbednar\/web_crawler_data_analysis\">analysis tool<\/a>.\nThe analysis process consists of the following steps:<\/p>\n<ol>\n<li>Databases created by browsing with and without the web-browser extension uBlock Origin are loaded separately.<\/li>\n<li>Aggregation SQL queries are executed in databases. The results are loaded into Python dictionaries.<\/li>\n<li>Data sets are analysed, compared and the results are exported to CSV files.<\/li>\n<\/ol>\n<h2 id=\"measurement-results\"><a class=\"toclink\" href=\"#measurement-results\">Measurement results<\/a><\/h2>\n<p>This section presents the results of the analysis.<\/p>\n<p>We tried to visit the first 250 000 websites from the <a href=\"https:\/\/tranco-list.eu\/list\/X79N\/1000000\">Tranco list X79N<\/a>.\n211 843 homepages of websites from the Tranco list were successfully visited in both modes - with and without uBlock Origin.\nMore than 4 000 000 000 JS calls were intercepted and stored into 5 000 <a href=\"https:\/\/nextcloud.fit.vutbr.cz\/s\/XKm3PCZnr2xkPH9\">SQLite databases<\/a> that have a total size of over 880 GB.<\/p>\n<p>Let us focus on the answers to the research questions on JavaScript API calls made by the websites. This blog post usually lists only 10 result lines for each experiment. Complete tables with all rows can be found in the <a href=\"https:\/\/nextcloud.fit.vutbr.cz\/s\/xDfSAe3Nx7iFSm4\">results stored on the server<\/a>.<\/p>\n<p>The meaning of the table columns:\n* Endpoint = a function or a property provided by a web browser.\n* API = a group of functions and properties that are thematically related.\n* Website = URL of the visited website.\n* Calls without uBlock Origin = the number of JavaScript calls intercepted without the uBlock Origin extension.\n* Calls with uBlock Origin = the number of JavaScript calls intercepted with the uBlock Origin extension.\n* Difference = the difference between the number of JavaScript calls intercepted with and without the uBlock Origin extension.\n* Difference [%] = the difference expressed as a percentage. Difference [%] = 100 * Difference\/Calls without uBlock Origin\n* Websites without uBlock [%] - what percentage of websites use the given API (measured without the uBlock Origin extension installed).\n* Websites with uBlock [%] - what percentage of websites use the given API (measured with the uBlock Origin extension installed).<\/p>\n<p>All result tables are sorted by the <code>Difference [%]<\/code> column decreasing.<\/p>\n<h3 id=\"the-most-blocked-api\"><a class=\"toclink\" href=\"#the-most-blocked-api\">The most blocked API<\/a><\/h3>\n<table>\n<thead>\n<tr>\n<th>API<\/th>\n<th>Calls   without uBlock<\/th>\n<th>Calls   with uBlock<\/th>\n<th>Difference<\/th>\n<th>Difference [%]<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>Non-Standard<\/td>\n<td>340 777<\/td>\n<td>18 180<\/td>\n<td>322 597<\/td>\n<td>94,67%<\/td>\n<\/tr>\n<tr>\n<td>Web Audio API<\/td>\n<td>14 390 507<\/td>\n<td>1 431 286<\/td>\n<td>12 959 221<\/td>\n<td>90,05%<\/td>\n<\/tr>\n<tr>\n<td>Web Speech API<\/td>\n<td>93 889<\/td>\n<td>10 712<\/td>\n<td>83 177<\/td>\n<td>88,59%<\/td>\n<\/tr>\n<tr>\n<td>Background Tasks API<\/td>\n<td>5 950 947<\/td>\n<td>954 740<\/td>\n<td>4 996 207<\/td>\n<td>83,96%<\/td>\n<\/tr>\n<tr>\n<td>High Resolution Time   (Level 2)<\/td>\n<td>367 400 369<\/td>\n<td>63 507 434<\/td>\n<td>303 892 935<\/td>\n<td>82,71%<\/td>\n<\/tr>\n<tr>\n<td>Navigation Timing<\/td>\n<td>8 160<\/td>\n<td>1 749<\/td>\n<td>6 411<\/td>\n<td>78,57%<\/td>\n<\/tr>\n<tr>\n<td>CSS Animations Level 1<\/td>\n<td>1 301<\/td>\n<td>317<\/td>\n<td>984<\/td>\n<td>75,63%<\/td>\n<\/tr>\n<tr>\n<td>Beacon<\/td>\n<td>223 209<\/td>\n<td>62 651<\/td>\n<td>160 558<\/td>\n<td>71,93%<\/td>\n<\/tr>\n<tr>\n<td>DOM Level 2: Traversal   and Range<\/td>\n<td>25 814 025<\/td>\n<td>7 996 500<\/td>\n<td>17 817 525<\/td>\n<td>69,02%<\/td>\n<\/tr>\n<tr>\n<td>Indexed Database API<\/td>\n<td>1 308 464<\/td>\n<td>408 983<\/td>\n<td>899 481<\/td>\n<td>68,74%<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<h3 id=\"the-most-blocked-api-endpoints\"><a class=\"toclink\" href=\"#the-most-blocked-api-endpoints\">The most blocked API endpoints<\/a><\/h3>\n<table>\n<thead>\n<tr>\n<th>Endpoint<\/th>\n<th>API<\/th>\n<th>Calls   without uBlock<\/th>\n<th>Calls   with uBlock<\/th>\n<th>Difference<\/th>\n<th>Difference [%]<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>Range.prototype.cloneContents<\/td>\n<td>DOM Level 2: Traversal and Range<\/td>\n<td>2 603<\/td>\n<td>16<\/td>\n<td>2 587<\/td>\n<td>99,39%<\/td>\n<\/tr>\n<tr>\n<td>Range.prototype.setStartAfter<\/td>\n<td>DOM Level 2: Traversal and Range<\/td>\n<td>1 234<\/td>\n<td>9<\/td>\n<td>1 225<\/td>\n<td>99,27%<\/td>\n<\/tr>\n<tr>\n<td>MessagePort.prototype.close<\/td>\n<td>HTML: Channel Messaging<\/td>\n<td>51 802<\/td>\n<td>416<\/td>\n<td>51 386<\/td>\n<td>99,20%<\/td>\n<\/tr>\n<tr>\n<td>SubtleCrypto.prototype.deriveBits<\/td>\n<td>Web Cryptography API<\/td>\n<td>208<\/td>\n<td>2<\/td>\n<td>206<\/td>\n<td>99,04%<\/td>\n<\/tr>\n<tr>\n<td>Range.prototype.selectNode<\/td>\n<td>DOM Level 2: Traversal and Range<\/td>\n<td>337 795<\/td>\n<td>7 384<\/td>\n<td>330 411<\/td>\n<td>97,81%<\/td>\n<\/tr>\n<tr>\n<td>Range.prototype.getBoundingClientRect<\/td>\n<td>DOM Level 2: Traversal and Range<\/td>\n<td>337 650<\/td>\n<td>7 565<\/td>\n<td>330 085<\/td>\n<td>97,76%<\/td>\n<\/tr>\n<tr>\n<td>Document.prototype.close<\/td>\n<td>HTML: Dynamic Markup Insertion<\/td>\n<td>21 666<\/td>\n<td>666<\/td>\n<td>21 000<\/td>\n<td>96,93%<\/td>\n<\/tr>\n<tr>\n<td>Navigator.prototype.javaEnabled<\/td>\n<td>Non-Standard<\/td>\n<td>334 580<\/td>\n<td>11 781<\/td>\n<td>322 799<\/td>\n<td>96,48%<\/td>\n<\/tr>\n<tr>\n<td>ServiceWorkerContainer.prototype.getRegistration<\/td>\n<td>Service Workers<\/td>\n<td>34 176<\/td>\n<td>1 375<\/td>\n<td>32 801<\/td>\n<td>95,98%<\/td>\n<\/tr>\n<tr>\n<td>FormData.prototype.append<\/td>\n<td>XMLHttpRequest<\/td>\n<td>670 137<\/td>\n<td>27 767<\/td>\n<td>642 370<\/td>\n<td>95,86%<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<h3 id=\"use-of-apis-on-websites\"><a class=\"toclink\" href=\"#use-of-apis-on-websites\">Use of APIs on websites<\/a><\/h3>\n<table>\n<thead>\n<tr>\n<th>API<\/th>\n<th>Websites   without uBlock [%]<\/th>\n<th>Websites   with uBlock [%]<\/th>\n<th>Difference   [%]<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>Non-Standard<\/td>\n<td>62,06%<\/td>\n<td>4,80%<\/td>\n<td>57,29%<\/td>\n<\/tr>\n<tr>\n<td>Web Cryptography API<\/td>\n<td>61,40%<\/td>\n<td>21,98%<\/td>\n<td>39,56%<\/td>\n<\/tr>\n<tr>\n<td>Beacon<\/td>\n<td>32,91%<\/td>\n<td>7,60%<\/td>\n<td>25,36%<\/td>\n<\/tr>\n<tr>\n<td>XMLHttpRequest<\/td>\n<td>71,53%<\/td>\n<td>47,76%<\/td>\n<td>24,08%<\/td>\n<\/tr>\n<tr>\n<td>HTML: Channel Messaging<\/td>\n<td>45,58%<\/td>\n<td>24,71%<\/td>\n<td>21,03%<\/td>\n<\/tr>\n<tr>\n<td>Performance Timeline<\/td>\n<td>41,15%<\/td>\n<td>21,22%<\/td>\n<td>20,06%<\/td>\n<\/tr>\n<tr>\n<td>High Resolution Time   (Level 2)<\/td>\n<td>57,37%<\/td>\n<td>39,24%<\/td>\n<td>18,39%<\/td>\n<\/tr>\n<tr>\n<td>HTML 5<\/td>\n<td>50,14%<\/td>\n<td>33,45%<\/td>\n<td>16,91%<\/td>\n<\/tr>\n<tr>\n<td>HTML: Web Storage<\/td>\n<td>65,69%<\/td>\n<td>49,48%<\/td>\n<td>16,53%<\/td>\n<\/tr>\n<tr>\n<td>URL<\/td>\n<td>29,73%<\/td>\n<td>15,79%<\/td>\n<td>14,05%<\/td>\n<\/tr>\n<tr>\n<td>Web Speech API<\/td>\n<td>14,41%<\/td>\n<td>0,81%<\/td>\n<td>13,61%<\/td>\n<\/tr>\n<tr>\n<td>Background Tasks API<\/td>\n<td>22,23%<\/td>\n<td>9,40%<\/td>\n<td>12,89%<\/td>\n<\/tr>\n<tr>\n<td>HTML: Canvas Element<\/td>\n<td>42,54%<\/td>\n<td>29,89%<\/td>\n<td>12,85%<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<h3 id=\"the-number-of-apis-used-by-the-website\"><a class=\"toclink\" href=\"#the-number-of-apis-used-by-the-website\">The number of APIs used by the website<\/a><\/h3>\n<p><strong>Average value<\/strong> - Number of APIs used without uBlock Origin: <strong>18.78<\/strong><\/p>\n<p><img alt=\"image\" src=\"https:\/\/jshelter.org\/crawling_results\/APIs_without_uBlock.png\"><\/p>\n<p><strong>Average value<\/strong> - Number of APIs used with uBlock Origin: <strong>14.27<\/strong><\/p>\n<p><img alt=\"image\" src=\"https:\/\/jshelter.org\/crawling_results\/APIs_with_uBlock.png\"><\/p>\n<h3 id=\"the-number-of-javascript-calls-on-websites\"><a class=\"toclink\" href=\"#the-number-of-javascript-calls-on-websites\">The number of JavaScript calls on websites<\/a><\/h3>\n<p><strong>Average value<\/strong> - Number of JavaScript calls made without uBlock Origin: <strong>10 629.92<\/strong><\/p>\n<p><img alt=\"image\" src=\"https:\/\/jshelter.org\/crawling_results\/JScalls_without_uBlock.png\"><\/p>\n<p><strong>Average value<\/strong> - Number of JavaScript calls made with uBlock Origin: <strong>7 176.2<\/strong><\/p>\n<p><img alt=\"image\" src=\"https:\/\/jshelter.org\/crawling_results\/JScalls_with_uBlock.png\"><\/p>\n<h3 id=\"the-number-of-endpoint-calls-on-websites\"><a class=\"toclink\" href=\"#the-number-of-endpoint-calls-on-websites\">The number of endpoint calls on websites<\/a><\/h3>\n<table>\n<thead>\n<tr>\n<th>Website<\/th>\n<th>Endpoint<\/th>\n<th>Calls   without uBlock<\/th>\n<th>Calls   with uBlock<\/th>\n<th>Difference<\/th>\n<th>Difference [%]<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>https:\/\/www.woolworths.co.za\/<\/td>\n<td>window.getComputedStyle<\/td>\n<td>202 013<\/td>\n<td>1<\/td>\n<td>202 012<\/td>\n<td>100,00%<\/td>\n<\/tr>\n<tr>\n<td>https:\/\/www.superkopilka.com\/<\/td>\n<td>Performance.prototype.now<\/td>\n<td>158 034<\/td>\n<td>1<\/td>\n<td>158 033<\/td>\n<td>100,00%<\/td>\n<\/tr>\n<tr>\n<td>https:\/\/www.jarir.com\/<\/td>\n<td>DOMTokenList.prototype.contains<\/td>\n<td>156 917<\/td>\n<td>1<\/td>\n<td>156 916<\/td>\n<td>100,00%<\/td>\n<\/tr>\n<tr>\n<td>https:\/\/shm.ru\/#<\/td>\n<td>Performance.prototype.now<\/td>\n<td>153 653<\/td>\n<td>1<\/td>\n<td>153 652<\/td>\n<td>100,00%<\/td>\n<\/tr>\n<tr>\n<td>https:\/\/znanium.com\/<\/td>\n<td>Performance.prototype.now<\/td>\n<td>138 465<\/td>\n<td>1<\/td>\n<td>138 464<\/td>\n<td>100,00%<\/td>\n<\/tr>\n<tr>\n<td>https:\/\/www.soliton.az\/<\/td>\n<td>Performance.prototype.now<\/td>\n<td>137 467<\/td>\n<td>1<\/td>\n<td>137 466<\/td>\n<td>100,00%<\/td>\n<\/tr>\n<tr>\n<td>https:\/\/www.lasenza.com\/<\/td>\n<td>DOMTokenList.prototype.contains<\/td>\n<td>118 945<\/td>\n<td>1<\/td>\n<td>118 944<\/td>\n<td>100,00%<\/td>\n<\/tr>\n<tr>\n<td>https:\/\/nethouse.ru\/<\/td>\n<td>Performance.prototype.now<\/td>\n<td>113 580<\/td>\n<td>1<\/td>\n<td>113 579<\/td>\n<td>100,00%<\/td>\n<\/tr>\n<tr>\n<td>https:\/\/boodmo.com\/<\/td>\n<td>Performance.prototype.now<\/td>\n<td>108 376<\/td>\n<td>1<\/td>\n<td>108 375<\/td>\n<td>100,00%<\/td>\n<\/tr>\n<tr>\n<td>https:\/\/fipi.ru\/<\/td>\n<td>Performance.prototype.now<\/td>\n<td>108 255<\/td>\n<td>1<\/td>\n<td>108 254<\/td>\n<td>100,00%<\/td>\n<\/tr>\n<tr>\n<td>https:\/\/belregion.ru\/<\/td>\n<td>Performance.prototype.now<\/td>\n<td>105 227<\/td>\n<td>1<\/td>\n<td>105 226<\/td>\n<td>100,00%<\/td>\n<\/tr>\n<tr>\n<td>https:\/\/tsargrad.tv\/<\/td>\n<td>Performance.prototype.now<\/td>\n<td>99 445<\/td>\n<td>1<\/td>\n<td>99 444<\/td>\n<td>100,00%<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<h2 id=\"measurement-results-for-opensource-websites-only\"><a class=\"toclink\" href=\"#measurement-results-for-opensource-websites-only\">Measurement results for opensource websites only<\/a><\/h2>\n<p>As this project focuses on free and open source software, we compared the general results with a list of pages connected to free software or open source.<\/p>\n<p>We collected the list of the home pages from <a href=\"https:\/\/gitweb.gentoo.org\/repo\/gentoo.git\/tree\/\">Gentoo repository<\/a>.<\/p>\n<p>We tried to visit all 5 271 collected websites. 4 528 homepages were successfully visited in both modes, with and without uBlock Origin.\nMore than 11 000 000 JS calls were intercepted and stored into 2 <a href=\"https:\/\/nextcloud.fit.vutbr.cz\/s\/yoLa5rcGzkgbSka\">SQLite databases<\/a> that have a total size of over 3 GB.<\/p>\n<p>Only the first 10 lines of the analysis results are usually listed below. Complete tables with all rows can be found in the <a href=\"https:\/\/nextcloud.fit.vutbr.cz\/s\/LWANmRxoXc5YYzy\">results stored on the server<\/a>.<\/p>\n<h3 id=\"the-most-blocked-api_1\"><a class=\"toclink\" href=\"#the-most-blocked-api_1\">The most blocked API<\/a><\/h3>\n<table>\n<thead>\n<tr>\n<th>API<\/th>\n<th>Calls   without uBlock<\/th>\n<th>Calls   with uBlock<\/th>\n<th>Difference<\/th>\n<th>Difference [%]<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>Resource Timing Level 2<\/td>\n<td>348<\/td>\n<td>8<\/td>\n<td>340<\/td>\n<td>97,70%<\/td>\n<\/tr>\n<tr>\n<td>Non-Standard<\/td>\n<td>1 711<\/td>\n<td>66<\/td>\n<td>1 645<\/td>\n<td>96,14%<\/td>\n<\/tr>\n<tr>\n<td>Background Tasks API<\/td>\n<td>18 763<\/td>\n<td>1 037<\/td>\n<td>17 726<\/td>\n<td>94,47%<\/td>\n<\/tr>\n<tr>\n<td>Web Speech API<\/td>\n<td>306<\/td>\n<td>18<\/td>\n<td>288<\/td>\n<td>94,12%<\/td>\n<\/tr>\n<tr>\n<td>HTML: Plugins<\/td>\n<td>13<\/td>\n<td>2<\/td>\n<td>11<\/td>\n<td>84,62%<\/td>\n<\/tr>\n<tr>\n<td>Permissions API<\/td>\n<td>705<\/td>\n<td>186<\/td>\n<td>519<\/td>\n<td>73,62%<\/td>\n<\/tr>\n<tr>\n<td>Performance Timeline<\/td>\n<td>3 981<\/td>\n<td>1 088<\/td>\n<td>2 893<\/td>\n<td>72,67%<\/td>\n<\/tr>\n<tr>\n<td>High Resolution Time   (Level 2)<\/td>\n<td>849 390<\/td>\n<td>232 806<\/td>\n<td>616 584<\/td>\n<td>72,59%<\/td>\n<\/tr>\n<tr>\n<td>Navigation Timing<\/td>\n<td>21<\/td>\n<td>6<\/td>\n<td>15<\/td>\n<td>71,43%<\/td>\n<\/tr>\n<tr>\n<td>Performance Timeline   (Level 2)<\/td>\n<td>2 548<\/td>\n<td>745<\/td>\n<td>1 803<\/td>\n<td>70,76%<\/td>\n<\/tr>\n<tr>\n<td>Storage API<\/td>\n<td>3<\/td>\n<td>1<\/td>\n<td>2<\/td>\n<td>66,67%<\/td>\n<\/tr>\n<tr>\n<td>Beacon<\/td>\n<td>727<\/td>\n<td>250<\/td>\n<td>477<\/td>\n<td>65,61%<\/td>\n<\/tr>\n<tr>\n<td>Web Cryptography API<\/td>\n<td>7 632<\/td>\n<td>2 928<\/td>\n<td>4 704<\/td>\n<td>61,64%<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<h3 id=\"the-most-blocked-api-endpoints_1\"><a class=\"toclink\" href=\"#the-most-blocked-api-endpoints_1\">The most blocked API endpoints<\/a><\/h3>\n<table>\n<thead>\n<tr>\n<th>Endpoint<\/th>\n<th>API<\/th>\n<th>Calls   without uBlock<\/th>\n<th>Calls   with uBlock<\/th>\n<th>Difference<\/th>\n<th>Difference [%]<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>IdleDeadline.prototype.timeRemaining<\/td>\n<td>Background Tasks API<\/td>\n<td>2 258<\/td>\n<td>2<\/td>\n<td>2 256<\/td>\n<td>99,91%<\/td>\n<\/tr>\n<tr>\n<td>Element.prototype.getAttributeNames<\/td>\n<td>DOM<\/td>\n<td>1 648<\/td>\n<td>12<\/td>\n<td>1 636<\/td>\n<td>99,27%<\/td>\n<\/tr>\n<tr>\n<td>MessagePort.prototype.close<\/td>\n<td>HTML: Channel Messaging<\/td>\n<td>268<\/td>\n<td>2<\/td>\n<td>266<\/td>\n<td>99,25%<\/td>\n<\/tr>\n<tr>\n<td>Document.prototype.elementFromPoint<\/td>\n<td>CSS Object Model (CSSOM)<\/td>\n<td>2 554<\/td>\n<td>22<\/td>\n<td>2 532<\/td>\n<td>99,14%<\/td>\n<\/tr>\n<tr>\n<td>Range.prototype.selectNode<\/td>\n<td>DOM Level 2: Traversal and Range<\/td>\n<td>106<\/td>\n<td>1<\/td>\n<td>105<\/td>\n<td>99,06%<\/td>\n<\/tr>\n<tr>\n<td>CanvasRenderingContext2D.prototype.createLinearGradient<\/td>\n<td>HTML: Canvas Element<\/td>\n<td>4 634<\/td>\n<td>56<\/td>\n<td>4 578<\/td>\n<td>98,79%<\/td>\n<\/tr>\n<tr>\n<td>CanvasRenderingContext2D.prototype.transform<\/td>\n<td>HTML: Canvas Element<\/td>\n<td>37 792<\/td>\n<td>845<\/td>\n<td>36 947<\/td>\n<td>97,76%<\/td>\n<\/tr>\n<tr>\n<td>PerformanceResourceTiming.prototype.toJSON<\/td>\n<td>Resource Timing Level 2<\/td>\n<td>348<\/td>\n<td>8<\/td>\n<td>340<\/td>\n<td>97,70%<\/td>\n<\/tr>\n<tr>\n<td>Navigator.prototype.javaEnabled<\/td>\n<td>Non-Standard<\/td>\n<td>1 691<\/td>\n<td>47<\/td>\n<td>1 644<\/td>\n<td>97,22%<\/td>\n<\/tr>\n<tr>\n<td>PerformanceObserverEntryList.prototype.getEntriesByName<\/td>\n<td>Performance Timeline (Level 2)<\/td>\n<td>52<\/td>\n<td>2<\/td>\n<td>50<\/td>\n<td>96,15%<\/td>\n<\/tr>\n<tr>\n<td>CanvasGradient.prototype.addColorStop<\/td>\n<td>HTML: Canvas Element<\/td>\n<td>9 623<\/td>\n<td>453<\/td>\n<td>9 170<\/td>\n<td>95,29%<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<h3 id=\"use-of-apis-on-websites_1\"><a class=\"toclink\" href=\"#use-of-apis-on-websites_1\">Use of APIs on websites<\/a><\/h3>\n<table>\n<thead>\n<tr>\n<th>API<\/th>\n<th>Websites   without uBlock [%]<\/th>\n<th>Websites   with uBlock [%]<\/th>\n<th>Difference   [%]<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>Non-Standard<\/td>\n<td>26,47%<\/td>\n<td>1,13%<\/td>\n<td>25,26%<\/td>\n<\/tr>\n<tr>\n<td>Web Cryptography API<\/td>\n<td>21,94%<\/td>\n<td>7,24%<\/td>\n<td>14,64%<\/td>\n<\/tr>\n<tr>\n<td>XMLHttpRequest<\/td>\n<td>26,42%<\/td>\n<td>14,94%<\/td>\n<td>11,41%<\/td>\n<\/tr>\n<tr>\n<td>HTML 5<\/td>\n<td>13,70%<\/td>\n<td>9,08%<\/td>\n<td>4,58%<\/td>\n<\/tr>\n<tr>\n<td>Beacon<\/td>\n<td>6,35%<\/td>\n<td>1,76%<\/td>\n<td>4,58%<\/td>\n<\/tr>\n<tr>\n<td>Performance Timeline<\/td>\n<td>10,07%<\/td>\n<td>5,68%<\/td>\n<td>4,36%<\/td>\n<\/tr>\n<tr>\n<td>High Resolution Time   (Level 2)<\/td>\n<td>15,46%<\/td>\n<td>11,12%<\/td>\n<td>4,29%<\/td>\n<\/tr>\n<tr>\n<td>HTML: Channel Messaging<\/td>\n<td>10,79%<\/td>\n<td>6,46%<\/td>\n<td>4,29%<\/td>\n<\/tr>\n<tr>\n<td>HTML: Web Storage<\/td>\n<td>20,60%<\/td>\n<td>16,26%<\/td>\n<td>4,27%<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<h3 id=\"the-number-of-apis-used-by-the-website_1\"><a class=\"toclink\" href=\"#the-number-of-apis-used-by-the-website_1\">The number of APIs used by the website<\/a><\/h3>\n<p><strong>Average value<\/strong> - Number of APIs used without uBlock Origin: <strong>7.4<\/strong><\/p>\n<p><img alt=\"image\" src=\"crawling_results\/APIs_without_uBlock_opensource.png\"><\/p>\n<p><strong>Average value<\/strong> - Number of APIs used with uBlock Origin: <strong>6.2<\/strong><\/p>\n<p><img alt=\"image\" src=\"crawling_results\/APIs_with_uBlock_opensource.png\"><\/p>\n<h3 id=\"the-number-of-javascript-calls-on-websites_1\"><a class=\"toclink\" href=\"#the-number-of-javascript-calls-on-websites_1\">The number of JavaScript calls on websites<\/a><\/h3>\n<p><strong>Average value<\/strong> - Number of JavaScript calls made without uBlock Origin: <strong>1598.18<\/strong><\/p>\n<p><img alt=\"image\" src=\"crawling_results\/JScalls_without_uBlock_opensource.png\"><\/p>\n<p><strong>Average value<\/strong> - Number of JavaScript calls made with uBlock Origin: <strong>1304.06<\/strong><\/p>\n<p><img alt=\"image\" src=\"crawling_results\/JScalls_with_uBlock_opensource.png\"><\/p>\n<h3 id=\"the-number-of-endpoint-calls-on-websites_1\"><a class=\"toclink\" href=\"#the-number-of-endpoint-calls-on-websites_1\">The number of endpoint calls on websites<\/a><\/h3>\n<table>\n<thead>\n<tr>\n<th>Website<\/th>\n<th>Endpoint<\/th>\n<th>Calls   without uBlock<\/th>\n<th>Calls   with uBlock<\/th>\n<th>Difference<\/th>\n<th>Difference [%]<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>https:\/\/wci.llnl.gov\/<\/td>\n<td>Element.prototype.hasAttribute<\/td>\n<td>34 860<\/td>\n<td>1<\/td>\n<td>34 859<\/td>\n<td>100,00%<\/td>\n<\/tr>\n<tr>\n<td>https:\/\/wiibrew.org\/wiki\/Main_Page<\/td>\n<td>Performance.prototype.now<\/td>\n<td>6 512<\/td>\n<td>1<\/td>\n<td>6 511<\/td>\n<td>99,98%<\/td>\n<\/tr>\n<tr>\n<td>http:\/\/www.vsound.org\/<\/td>\n<td>Document.prototype.getElementById<\/td>\n<td>5 797<\/td>\n<td>1<\/td>\n<td>5 796<\/td>\n<td>99,98%<\/td>\n<\/tr>\n<tr>\n<td>http:\/\/wormsofprey.org\/<\/td>\n<td>Document.prototype.getElementById<\/td>\n<td>5 793<\/td>\n<td>1<\/td>\n<td>5 792<\/td>\n<td>99,98%<\/td>\n<\/tr>\n<tr>\n<td>http:\/\/konst.org.ua\/<\/td>\n<td>Element.prototype.setAttribute<\/td>\n<td>4 248<\/td>\n<td>1<\/td>\n<td>4 247<\/td>\n<td>99,98%<\/td>\n<\/tr>\n<tr>\n<td>https:\/\/www.union.edu\/mathematics<\/td>\n<td>Performance.prototype.now<\/td>\n<td>5 317<\/td>\n<td>2<\/td>\n<td>5 315<\/td>\n<td>99,96%<\/td>\n<\/tr>\n<tr>\n<td>https:\/\/www.mongodb.com\/<\/td>\n<td>window.getComputedStyle<\/td>\n<td>30 516<\/td>\n<td>14<\/td>\n<td>30 502<\/td>\n<td>99,95%<\/td>\n<\/tr>\n<tr>\n<td>https:\/\/www.monotype.com\/<\/td>\n<td>Performance.prototype.now<\/td>\n<td>4 091<\/td>\n<td>2<\/td>\n<td>4 089<\/td>\n<td>99,95%<\/td>\n<\/tr>\n<tr>\n<td>https:\/\/sylabs.io\/<\/td>\n<td>Performance.prototype.now<\/td>\n<td>1 839<\/td>\n<td>1<\/td>\n<td>1 838<\/td>\n<td>99,95%<\/td>\n<\/tr>\n<tr>\n<td>http:\/\/vlgothic.dicey.org\/<\/td>\n<td>Performance.prototype.now<\/td>\n<td>3 427<\/td>\n<td>2<\/td>\n<td>3 425<\/td>\n<td>99,94%<\/td>\n<\/tr>\n<tr>\n<td>https:\/\/www.ppsspp.org\/<\/td>\n<td>Performance.prototype.now<\/td>\n<td>3 397<\/td>\n<td>2<\/td>\n<td>3 395<\/td>\n<td>99,94%<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<h2 id=\"measurement-results-for-fingerprint-detector\"><a class=\"toclink\" href=\"#measurement-results-for-fingerprint-detector\">Measurement results for FingerPrint Detector<\/a><\/h2>\n<p>We designed the web crawling primarily to retrieve data to create or evaluate heuristics for the <a href=\"\/fpdetection\/\">FingerPrint Detector (FPD)<\/a>.\nThe above results are helpful in understanding the JavaScript APIs usage on the web. For FPD, only the name of the endpoint and the weight of this endpoint are important. The weight expresses how often a given endpoint is used to create a fingerprint.<\/p>\n<p>The resulting table below was created from crawled data from websites of the Tranco list . Two data sets were combined to obtain more accurate results. The first dataset obtained while crawling with uBlock Origin, the second one obtained while crawling with uMatrix.\nThe resulting endpoint weight (marked as <code>average_weight<\/code>) was calculated in Python as follows:<\/p>\n<div class=\"highlight\"><pre><span><\/span><code><span class=\"nv\">weight_uMatrix<\/span> <span class=\"o\">=<\/span> <span class=\"nv\">max<\/span><span class=\"ss\">(<\/span><span class=\"mi\">0<\/span>, <span class=\"mi\">10<\/span> <span class=\"o\">+<\/span> <span class=\"ss\">((<\/span><span class=\"mi\">10<\/span><span class=\"o\">-<\/span><span class=\"nv\">len<\/span><span class=\"ss\">(<\/span><span class=\"nv\">str<\/span><span class=\"ss\">(<\/span><span class=\"nv\">calls<\/span><span class=\"ss\">)))<\/span><span class=\"o\">\/<\/span><span class=\"mi\">2<\/span><span class=\"ss\">)<\/span> <span class=\"o\">-<\/span> <span class=\"ss\">((<\/span><span class=\"mi\">100<\/span><span class=\"o\">-<\/span><span class=\"ss\">(<\/span><span class=\"mi\">100<\/span><span class=\"o\">*<\/span><span class=\"nv\">difference_uMatrix_percent<\/span><span class=\"ss\">))<\/span><span class=\"o\">\/<\/span><span class=\"mi\">3<\/span><span class=\"ss\">)<\/span> <span class=\"ss\">)<\/span>\n<span class=\"nv\">weight_uBlock<\/span> <span class=\"o\">=<\/span> <span class=\"nv\">max<\/span><span class=\"ss\">(<\/span><span class=\"mi\">0<\/span>, <span class=\"mi\">10<\/span> <span class=\"o\">+<\/span> <span class=\"ss\">((<\/span><span class=\"mi\">10<\/span><span class=\"o\">-<\/span><span class=\"nv\">len<\/span><span class=\"ss\">(<\/span><span class=\"nv\">str<\/span><span class=\"ss\">(<\/span><span class=\"nv\">calls<\/span><span class=\"ss\">)))<\/span><span class=\"o\">\/<\/span><span class=\"mi\">2<\/span><span class=\"ss\">)<\/span> <span class=\"o\">-<\/span> <span class=\"ss\">((<\/span><span class=\"mi\">100<\/span><span class=\"o\">-<\/span><span class=\"ss\">(<\/span><span class=\"mi\">100<\/span><span class=\"o\">*<\/span><span class=\"nv\">difference_uBlock_percent<\/span><span class=\"ss\">))<\/span><span class=\"o\">\/<\/span><span class=\"mi\">3<\/span><span class=\"ss\">)<\/span> <span class=\"ss\">)<\/span>\n\n<span class=\"k\">if<\/span> <span class=\"nv\">weight_uMatrix<\/span> <span class=\"o\">&gt;<\/span> <span class=\"mi\">0<\/span> <span class=\"nv\">and<\/span> <span class=\"nv\">weight_uBlock<\/span> <span class=\"o\">&gt;<\/span> <span class=\"mi\">0<\/span>:\n    <span class=\"nv\">average_weight<\/span> <span class=\"o\">=<\/span> <span class=\"nv\">round<\/span><span class=\"ss\">((<\/span><span class=\"nv\">weight_uMatrix<\/span> <span class=\"o\">+<\/span> <span class=\"nv\">weight_uBlock<\/span><span class=\"ss\">)<\/span><span class=\"o\">\/<\/span><span class=\"mi\">2<\/span><span class=\"ss\">)<\/span>\n<span class=\"k\">else<\/span>:\n    <span class=\"nv\">average_weight<\/span> <span class=\"o\">=<\/span> <span class=\"mi\">0<\/span>\n<\/code><\/pre><\/div>\n\n<p>Again, only the first 10 lines of the FPD analysis result are listed below. Complete table with all rows can be found in the <a href=\"https:\/\/nextcloud.fit.vutbr.cz\/s\/GjkTJzweccgxw6n\">results stored on the server<\/a>.<\/p>\n<table>\n<thead>\n<tr>\n<th>Endpoint<\/th>\n<th>Average weight<\/th>\n<th>uMatrix weight<\/th>\n<th>uBlock weight<\/th>\n<th>Calls   without extension<\/th>\n<th>Calls   with uMatrix<\/th>\n<th>Calls   with uBlock<\/th>\n<th>Difference uMatrix<\/th>\n<th>Difference uBlock<\/th>\n<th>Difference uMatrix   [%]<\/th>\n<th>Difference uBlock [%]<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>Range.prototype.setStartAfter<\/td>\n<td>13<\/td>\n<td>13,427<\/td>\n<td>13,060<\/td>\n<td>455<\/td>\n<td>1<\/td>\n<td>6<\/td>\n<td>454<\/td>\n<td>449<\/td>\n<td>0,998<\/td>\n<td>0,987<\/td>\n<\/tr>\n<tr>\n<td>Range.prototype.insertNode<\/td>\n<td>13<\/td>\n<td>13,138<\/td>\n<td>12,920<\/td>\n<td>460<\/td>\n<td>5<\/td>\n<td>8<\/td>\n<td>455<\/td>\n<td>452<\/td>\n<td>0,989<\/td>\n<td>0,983<\/td>\n<\/tr>\n<tr>\n<td>HTMLFormControlsCollection.prototype.namedItem<\/td>\n<td>13<\/td>\n<td>12,714<\/td>\n<td>12,623<\/td>\n<td>2 211<\/td>\n<td>19<\/td>\n<td>25<\/td>\n<td>2 192<\/td>\n<td>2 186<\/td>\n<td>0,991<\/td>\n<td>0,989<\/td>\n<\/tr>\n<tr>\n<td>Document.prototype.open<\/td>\n<td>12<\/td>\n<td>12,475<\/td>\n<td>11,274<\/td>\n<td>8 190<\/td>\n<td>129<\/td>\n<td>424<\/td>\n<td>8 061<\/td>\n<td>7 766<\/td>\n<td>0,984<\/td>\n<td>0,948<\/td>\n<\/tr>\n<tr>\n<td>MessagePort.prototype.close<\/td>\n<td>12<\/td>\n<td>12,438<\/td>\n<td>12,424<\/td>\n<td>16 208<\/td>\n<td>30<\/td>\n<td>37<\/td>\n<td>16 178<\/td>\n<td>16 171<\/td>\n<td>0,998<\/td>\n<td>0,998<\/td>\n<\/tr>\n<tr>\n<td>Document.prototype.getElementsByTagNameNS<\/td>\n<td>12<\/td>\n<td>12,374<\/td>\n<td>10,811<\/td>\n<td>533<\/td>\n<td>18<\/td>\n<td>43<\/td>\n<td>515<\/td>\n<td>490<\/td>\n<td>0,966<\/td>\n<td>0,919<\/td>\n<\/tr>\n<tr>\n<td>Document.prototype.close<\/td>\n<td>12<\/td>\n<td>12,086<\/td>\n<td>11,944<\/td>\n<td>7 731<\/td>\n<td>212<\/td>\n<td>245<\/td>\n<td>7 519<\/td>\n<td>7 486<\/td>\n<td>0,973<\/td>\n<td>0,968<\/td>\n<\/tr>\n<tr>\n<td>SubtleCrypto.prototype.generateKey<\/td>\n<td>12<\/td>\n<td>11,876<\/td>\n<td>11,503<\/td>\n<td>985<\/td>\n<td>48<\/td>\n<td>59<\/td>\n<td>937<\/td>\n<td>926<\/td>\n<td>0,951<\/td>\n<td>0,940<\/td>\n<\/tr>\n<tr>\n<td>Document.prototype.elementsFromPoint<\/td>\n<td>12<\/td>\n<td>12,469<\/td>\n<td>11,365<\/td>\n<td>46 938<\/td>\n<td>44<\/td>\n<td>1 598<\/td>\n<td>46 894<\/td>\n<td>45 340<\/td>\n<td>0,999<\/td>\n<td>0,966<\/td>\n<\/tr>\n<tr>\n<td>Navigator.prototype.javaEnabled<\/td>\n<td>11<\/td>\n<td>10,892<\/td>\n<td>10,892<\/td>\n<td>110 665<\/td>\n<td>3 677<\/td>\n<td>3 679<\/td>\n<td>106 988<\/td>\n<td>106 986<\/td>\n<td>0,967<\/td>\n<td>0,967<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>More about FingerPrint Detector is written in the <a href=\"\/fpdetection\/\">following blogpost<\/a>.<\/p>\n<h2 id=\"conclusion\"><a class=\"toclink\" href=\"#conclusion\">Conclusion<\/a><\/h2>\n<p>The crawl identified JavaScript endpoints, often used to create a browser fingerprint. The observed data allow assigning weights for each endpoint.\n<a href=\"\/fpdetection\/\">FingerPrint Detector<\/a> configuration file uses the crawl results.<\/p>","category":{"@attributes":{"term":"posts"}}},{"title":"\"RESULTS: Measurement of JavaScript API usage on the web\"","link":{"@attributes":{"href":"https:\/\/jshelter.org\/pt\/crawling_results\/","rel":"alternate"}},"published":"2022-01-14T12:00:00+01:00","updated":"2025-07-10T07:01:03+02:00","author":{"name":"The JShelter team"},"id":"tag:jshelter.org,2022-01-14:\/pt\/crawling_results\/","summary":"<p>A <a href=\"\/crawling\/\">previous blogpost<\/a> introduced a methodology to measure the usage\nof JavaScript APIs on the web by crawling the web. The starting point of our web\ncrawling research is the <a href=\"https:\/\/www.peteresnyder.com\/static\/papers\/improving-web-privacy-and-security-thesis.pdf\">work of Peter\nSnyder<\/a>.\nMost importantly, we wanted to compare the differences in JS API usage between\nbrowsers with and \u2026<\/p>","content":"<p>A <a href=\"\/crawling\/\">previous blogpost<\/a> introduced a methodology to measure the usage\nof JavaScript APIs on the web by crawling the web. The starting point of our web\ncrawling research is the <a href=\"https:\/\/www.peteresnyder.com\/static\/papers\/improving-web-privacy-and-security-thesis.pdf\">work of Peter\nSnyder<\/a>.\nMost importantly, we wanted to compare the differences in JS API usage between\nbrowsers with and without privacy extension (e.g., uBlock Origin). We have\nfinished the development of the <a href=\"https:\/\/github.com\/martinbednar\/web_crawler\">crawling\ntool<\/a>. We crawled thousands of\nwebsites and collected JavaScript calls on the visited web pages.<\/p>\n<p>This blog post introduces the methodology and results of the crawling.<\/p>\n<h2 id=\"introduction\"><a class=\"toclink\" href=\"#introduction\">Introduction<\/a><\/h2>\n<p>When a user opens a webpage in a JavaScript-enabled web browser, that webpage can\naccess various APIs supported by the web browser. The webpage can read, for\nexample, the value of performance.now(), battery status or data from sensors\n(when available). All these values can be misused to create a <a href=\"https:\/\/arxiv.org\/pdf\/1905.01051.pdf\">device\nfingerprint<\/a> that can be used to identify\nthe user.<\/p>\n<p>We aim to research how JS APIs are used on websites. The main research questions\nare:<\/p>\n<ul>\n<li>What APIs do websites use?<\/li>\n<li>How many APIs does a website use?<\/li>\n<li>What endpoints do websites access most often?<\/li>\n<li>How many JS calls are made?<\/li>\n<li>What are the differences in results in all the previous questions with an\nactive privacy web-browser extension (e.g., uBlock Origin)? For example, What\nJS calls are blocked by uBlock Origin?<\/li>\n<\/ul>\n<p>The answers to these questions should help us better understand websites'\nbehaviour. Based on the obtained data, we want to define the suspicious\nbehaviour indicating that the website is trying to fingerprint the user's device.<\/p>\n<p>We plan to keep the <a href=\"\/fpdetection\/\">anti-fingerprint mechanism<\/a> updated based on\nderived heuristics. The heuristics are derived from the following statistics:<\/p>\n<ul>\n<li>How many APIs and endpoints does the website access?<\/li>\n<li>How many JS calls did the website make?<\/li>\n<li>Is the number and combination of JS APIs calls suspicious?<\/li>\n<\/ul>\n<p>When our anti-fingerprint mechanism detects suspicious combinations or a high\nnumber of used APIs, it can block communication with the webpage.<\/p>\n<h2 id=\"crawler\"><a class=\"toclink\" href=\"#crawler\">Crawler<\/a><\/h2>\n<p>We have developed <a href=\"https:\/\/github.com\/martinbednar\/web_crawler\/\">Web crawler<\/a> - a\ntool for automatically visiting websites from a given list and collecting\nJavaScript calls made by the website. Our Web crawler is based on the\n<a href=\"https:\/\/github.com\/openwpm\/OpenWPM\">OpenWPM<\/a> platform. A modified web browser\nextension <a href=\"https:\/\/github.com\/pes10k\/web-api-manager\">Web API Manager<\/a> collects\nstatistics on called JS APIs.<\/p>\n<p>The crawling process can be described in the following steps:<\/p>\n<ol>\n<li>The python script <code>start_docker_runs.py<\/code> launches the Docker image\n<a href=\"https:\/\/hub.docker.com\/repository\/docker\/martan305\/web_crawler\">martan305\/web_crawler<\/a>.\nThe parameters set in the Python startup script are given to the Docker\ncontainer as environmental variables.<\/li>\n<li>If a privacy extension is required (in the script parameter), uBlock Origin\nwill be installed as soon as the web browser is started.<\/li>\n<li>Python script running in the container visits web pages from a given list one\nby one.<\/li>\n<li>The browser waits for 30 seconds on each page. The customised web browser\nextension Web API Manager intercepts JS calls and stores the stats into the\nSQLite database.<\/li>\n<li>When the script finishes the crawl of assigned pages, it checks if there is a\nbatch of pages not yet visited. If there is an unprocessed batch, the\ncrawling continues with step 3 and the new list of web pages to visit. If the\ncrawler visited all sites, the crawling ends.<\/li>\n<\/ol>\n<p>For now, we only visited the homepages because we wanted to visit as many\ndifferent websites as possible. In the future, we plan to launch long-term\ncrawling, which will include subpages. In particular, we want to focus on\nvisiting login pages, where we expect fingerprint scripts to be included. Then,\nwe can compare API calls on login pages and other pages.<\/p>\n<p>Afterwards, we analyse the collected data with a standalone <a href=\"https:\/\/github.com\/martinbednar\/web_crawler_data_analysis\">analysis\ntool<\/a>. The analysis\nprocess consists of the following steps:<\/p>\n<ol>\n<li>Databases created by browsing with and without the web-browser extension\nuBlock Origin are loaded separately.<\/li>\n<li>Aggregation SQL queries are executed in databases. The results are loaded into\nPython dictionaries.<\/li>\n<li>Data sets are analysed, compared and the results are exported to CSV files.<\/li>\n<\/ol>\n<h2 id=\"measurement-results\"><a class=\"toclink\" href=\"#measurement-results\">Measurement results<\/a><\/h2>\n<p>This section presents the results of the analysis.<\/p>\n<p>We tried to visit the first 250 000 websites from the <a href=\"https:\/\/tranco-list.eu\/list\/X79N\/1000000\">Tranco list\nX79N<\/a>. 211 843 homepages of websites\nfrom the Tranco list were successfully visited in both modes - with and without\nuBlock Origin. More than 4 000 000 000 JS calls were intercepted and stored into\n5 000 <a href=\"https:\/\/nextcloud.fit.vutbr.cz\/s\/XKm3PCZnr2xkPH9\">SQLite databases<\/a> that\nhave a total size of over 880 GB.<\/p>\n<p>Let us focus on the answers to the research questions on JavaScript API calls\nmade by the websites. This blog post usually lists only 10 result lines for each\nexperiment. Complete tables with all rows can be found in the <a href=\"https:\/\/nextcloud.fit.vutbr.cz\/s\/xDfSAe3Nx7iFSm4\">results stored on\nthe server<\/a>.<\/p>\n<p>The meaning of the table columns:<\/p>\n<ul>\n<li>Endpoint = a function or a property provided by a web browser.<\/li>\n<li>API = a group of functions and properties that are thematically related.<\/li>\n<li>Website = URL of the visited website.<\/li>\n<li>Calls without uBlock Origin = the number of JavaScript calls intercepted\nwithout the uBlock Origin extension.<\/li>\n<li>Calls with uBlock Origin = the number of JavaScript calls intercepted with the\nuBlock Origin extension.<\/li>\n<li>Difference = the difference between the number of JavaScript calls intercepted\nwith and without the uBlock Origin extension.<\/li>\n<li>Difference [%] = the difference expressed as a percentage. Difference [%] = 100\n* Difference\/Calls without uBlock Origin<\/li>\n<li>Websites without uBlock [%] - what percentage of websites use the given API\n(measured without the uBlock Origin extension installed).<\/li>\n<li>Websites with uBlock [%] - what percentage of websites use the given API\n(measured with the uBlock Origin extension installed).<\/li>\n<\/ul>\n<p>All result tables are sorted by the <code>Difference [%]<\/code> column decreasing.<\/p>\n<h3 id=\"the-most-blocked-api\"><a class=\"toclink\" href=\"#the-most-blocked-api\">The most blocked API<\/a><\/h3>\n<table>\n<thead>\n<tr>\n<th>API<\/th>\n<th>Calls without uBlock<\/th>\n<th>Calls with uBlock<\/th>\n<th>Difference<\/th>\n<th>Difference [%]<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>Non-Standard<\/td>\n<td>340 777<\/td>\n<td>18 180<\/td>\n<td>322 597<\/td>\n<td>94,67%<\/td>\n<\/tr>\n<tr>\n<td>Web Audio API<\/td>\n<td>14 390 507<\/td>\n<td>1 431 286<\/td>\n<td>12 959 221<\/td>\n<td>90,05%<\/td>\n<\/tr>\n<tr>\n<td>Web Speech API<\/td>\n<td>93 889<\/td>\n<td>10 712<\/td>\n<td>83 177<\/td>\n<td>88,59%<\/td>\n<\/tr>\n<tr>\n<td>Background Tasks API<\/td>\n<td>5 950 947<\/td>\n<td>954 740<\/td>\n<td>4 996 207<\/td>\n<td>83,96%<\/td>\n<\/tr>\n<tr>\n<td>High Resolution Time (Level 2)<\/td>\n<td>367 400 369<\/td>\n<td>63 507 434<\/td>\n<td>303 892 935<\/td>\n<td>82,71%<\/td>\n<\/tr>\n<tr>\n<td>Navigation Timing<\/td>\n<td>8 160<\/td>\n<td>1 749<\/td>\n<td>6 411<\/td>\n<td>78,57%<\/td>\n<\/tr>\n<tr>\n<td>CSS Animations Level 1<\/td>\n<td>1 301<\/td>\n<td>317<\/td>\n<td>984<\/td>\n<td>75,63%<\/td>\n<\/tr>\n<tr>\n<td>Beacon<\/td>\n<td>223 209<\/td>\n<td>62 651<\/td>\n<td>160 558<\/td>\n<td>71,93%<\/td>\n<\/tr>\n<tr>\n<td>DOM Level 2: Traversal and Range<\/td>\n<td>25 814 025<\/td>\n<td>7 996 500<\/td>\n<td>17 817 525<\/td>\n<td>69,02%<\/td>\n<\/tr>\n<tr>\n<td>Indexed Database API<\/td>\n<td>1 308 464<\/td>\n<td>408 983<\/td>\n<td>899 481<\/td>\n<td>68,74%<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<h3 id=\"the-most-blocked-api-endpoints\"><a class=\"toclink\" href=\"#the-most-blocked-api-endpoints\">The most blocked API endpoints<\/a><\/h3>\n<table>\n<thead>\n<tr>\n<th>Endpoint<\/th>\n<th>API<\/th>\n<th>Calls without uBlock<\/th>\n<th>Calls with uBlock<\/th>\n<th>Difference<\/th>\n<th>Difference [%]<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>Range.prototype.cloneContents<\/td>\n<td>DOM Level 2: Traversal and Range<\/td>\n<td>2 603<\/td>\n<td>16<\/td>\n<td>2 587<\/td>\n<td>99,39%<\/td>\n<\/tr>\n<tr>\n<td>Range.prototype.setStartAfter<\/td>\n<td>DOM Level 2: Traversal and Range<\/td>\n<td>1 234<\/td>\n<td>9<\/td>\n<td>1 225<\/td>\n<td>99,27%<\/td>\n<\/tr>\n<tr>\n<td>MessagePort.prototype.close<\/td>\n<td>HTML: Channel Messaging<\/td>\n<td>51 802<\/td>\n<td>416<\/td>\n<td>51 386<\/td>\n<td>99,20%<\/td>\n<\/tr>\n<tr>\n<td>SubtleCrypto.prototype.deriveBits<\/td>\n<td>Web Cryptography API<\/td>\n<td>208<\/td>\n<td>2<\/td>\n<td>206<\/td>\n<td>99,04%<\/td>\n<\/tr>\n<tr>\n<td>Range.prototype.selectNode<\/td>\n<td>DOM Level 2: Traversal and Range<\/td>\n<td>337 795<\/td>\n<td>7 384<\/td>\n<td>330 411<\/td>\n<td>97,81%<\/td>\n<\/tr>\n<tr>\n<td>Range.prototype.getBoundingClientRect<\/td>\n<td>DOM Level 2: Traversal and Range<\/td>\n<td>337 650<\/td>\n<td>7 565<\/td>\n<td>330 085<\/td>\n<td>97,76%<\/td>\n<\/tr>\n<tr>\n<td>Document.prototype.close<\/td>\n<td>HTML: Dynamic Markup Insertion<\/td>\n<td>21 666<\/td>\n<td>666<\/td>\n<td>21 000<\/td>\n<td>96,93%<\/td>\n<\/tr>\n<tr>\n<td>Navigator.prototype.javaEnabled<\/td>\n<td>Non-Standard<\/td>\n<td>334 580<\/td>\n<td>11 781<\/td>\n<td>322 799<\/td>\n<td>96,48%<\/td>\n<\/tr>\n<tr>\n<td>ServiceWorkerContainer.prototype.getRegistration<\/td>\n<td>Service Workers<\/td>\n<td>34 176<\/td>\n<td>1 375<\/td>\n<td>32 801<\/td>\n<td>95,98%<\/td>\n<\/tr>\n<tr>\n<td>FormData.prototype.append<\/td>\n<td>XMLHttpRequest<\/td>\n<td>670 137<\/td>\n<td>27 767<\/td>\n<td>642 370<\/td>\n<td>95,86%<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<h3 id=\"use-of-apis-on-websites\"><a class=\"toclink\" href=\"#use-of-apis-on-websites\">Use of APIs on websites<\/a><\/h3>\n<table>\n<thead>\n<tr>\n<th>API<\/th>\n<th>Websites without uBlock [%]<\/th>\n<th>Websites with uBlock [%]<\/th>\n<th>Difference [%]<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>Non-Standard<\/td>\n<td>62,06%<\/td>\n<td>4,80%<\/td>\n<td>57,29%<\/td>\n<\/tr>\n<tr>\n<td>Web Cryptography API<\/td>\n<td>61,40%<\/td>\n<td>21,98%<\/td>\n<td>39,56%<\/td>\n<\/tr>\n<tr>\n<td>Beacon<\/td>\n<td>32,91%<\/td>\n<td>7,60%<\/td>\n<td>25,36%<\/td>\n<\/tr>\n<tr>\n<td>XMLHttpRequest<\/td>\n<td>71,53%<\/td>\n<td>47,76%<\/td>\n<td>24,08%<\/td>\n<\/tr>\n<tr>\n<td>HTML: Channel Messaging<\/td>\n<td>45,58%<\/td>\n<td>24,71%<\/td>\n<td>21,03%<\/td>\n<\/tr>\n<tr>\n<td>Performance Timeline<\/td>\n<td>41,15%<\/td>\n<td>21,22%<\/td>\n<td>20,06%<\/td>\n<\/tr>\n<tr>\n<td>High Resolution Time (Level 2)<\/td>\n<td>57,37%<\/td>\n<td>39,24%<\/td>\n<td>18,39%<\/td>\n<\/tr>\n<tr>\n<td>HTML 5<\/td>\n<td>50,14%<\/td>\n<td>33,45%<\/td>\n<td>16,91%<\/td>\n<\/tr>\n<tr>\n<td>HTML: Web Storage<\/td>\n<td>65,69%<\/td>\n<td>49,48%<\/td>\n<td>16,53%<\/td>\n<\/tr>\n<tr>\n<td>URL<\/td>\n<td>29,73%<\/td>\n<td>15,79%<\/td>\n<td>14,05%<\/td>\n<\/tr>\n<tr>\n<td>Web Speech API<\/td>\n<td>14,41%<\/td>\n<td>0,81%<\/td>\n<td>13,61%<\/td>\n<\/tr>\n<tr>\n<td>Background Tasks API<\/td>\n<td>22,23%<\/td>\n<td>9,40%<\/td>\n<td>12,89%<\/td>\n<\/tr>\n<tr>\n<td>HTML: Canvas Element<\/td>\n<td>42,54%<\/td>\n<td>29,89%<\/td>\n<td>12,85%<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<h3 id=\"the-number-of-apis-used-by-the-website\"><a class=\"toclink\" href=\"#the-number-of-apis-used-by-the-website\">The number of APIs used by the website<\/a><\/h3>\n<p><strong>Average value<\/strong> - Number of APIs used without uBlock Origin: <strong>18.78<\/strong><\/p>\n<p><img alt=\"image\" src=\"https:\/\/jshelter.org\/crawling_results\/APIs_without_uBlock.png\"><\/p>\n<p><strong>Average value<\/strong> - Number of APIs used with uBlock Origin: <strong>14.27<\/strong><\/p>\n<p><img alt=\"image\" src=\"https:\/\/jshelter.org\/crawling_results\/APIs_with_uBlock.png\"><\/p>\n<h3 id=\"the-number-of-javascript-calls-on-websites\"><a class=\"toclink\" href=\"#the-number-of-javascript-calls-on-websites\">The number of JavaScript calls on websites<\/a><\/h3>\n<p><strong>Average value<\/strong> - Number of JavaScript calls made without uBlock Origin: <strong>10\n629.92<\/strong><\/p>\n<p><img alt=\"image\" src=\"https:\/\/jshelter.org\/crawling_results\/JScalls_without_uBlock.png\"><\/p>\n<p><strong>Average value<\/strong> - Number of JavaScript calls made with uBlock Origin: <strong>7\n176.2<\/strong><\/p>\n<p><img alt=\"image\" src=\"https:\/\/jshelter.org\/crawling_results\/JScalls_with_uBlock.png\"><\/p>\n<h3 id=\"the-number-of-endpoint-calls-on-websites\"><a class=\"toclink\" href=\"#the-number-of-endpoint-calls-on-websites\">The number of endpoint calls on websites<\/a><\/h3>\n<table>\n<thead>\n<tr>\n<th>Website<\/th>\n<th>Endpoint<\/th>\n<th>Calls without uBlock<\/th>\n<th>Calls with uBlock<\/th>\n<th>Difference<\/th>\n<th>Difference [%]<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>https:\/\/www.woolworths.co.za\/<\/td>\n<td>window.getComputedStyle<\/td>\n<td>202 013<\/td>\n<td>1<\/td>\n<td>202 012<\/td>\n<td>100,00%<\/td>\n<\/tr>\n<tr>\n<td>https:\/\/www.superkopilka.com\/<\/td>\n<td>Performance.prototype.now<\/td>\n<td>158 034<\/td>\n<td>1<\/td>\n<td>158 033<\/td>\n<td>100,00%<\/td>\n<\/tr>\n<tr>\n<td>https:\/\/www.jarir.com\/<\/td>\n<td>DOMTokenList.prototype.contains<\/td>\n<td>156 917<\/td>\n<td>1<\/td>\n<td>156 916<\/td>\n<td>100,00%<\/td>\n<\/tr>\n<tr>\n<td>https:\/\/shm.ru\/#<\/td>\n<td>Performance.prototype.now<\/td>\n<td>153 653<\/td>\n<td>1<\/td>\n<td>153 652<\/td>\n<td>100,00%<\/td>\n<\/tr>\n<tr>\n<td>https:\/\/znanium.com\/<\/td>\n<td>Performance.prototype.now<\/td>\n<td>138 465<\/td>\n<td>1<\/td>\n<td>138 464<\/td>\n<td>100,00%<\/td>\n<\/tr>\n<tr>\n<td>https:\/\/www.soliton.az\/<\/td>\n<td>Performance.prototype.now<\/td>\n<td>137 467<\/td>\n<td>1<\/td>\n<td>137 466<\/td>\n<td>100,00%<\/td>\n<\/tr>\n<tr>\n<td>https:\/\/www.lasenza.com\/<\/td>\n<td>DOMTokenList.prototype.contains<\/td>\n<td>118 945<\/td>\n<td>1<\/td>\n<td>118 944<\/td>\n<td>100,00%<\/td>\n<\/tr>\n<tr>\n<td>https:\/\/nethouse.ru\/<\/td>\n<td>Performance.prototype.now<\/td>\n<td>113 580<\/td>\n<td>1<\/td>\n<td>113 579<\/td>\n<td>100,00%<\/td>\n<\/tr>\n<tr>\n<td>https:\/\/boodmo.com\/<\/td>\n<td>Performance.prototype.now<\/td>\n<td>108 376<\/td>\n<td>1<\/td>\n<td>108 375<\/td>\n<td>100,00%<\/td>\n<\/tr>\n<tr>\n<td>https:\/\/fipi.ru\/<\/td>\n<td>Performance.prototype.now<\/td>\n<td>108 255<\/td>\n<td>1<\/td>\n<td>108 254<\/td>\n<td>100,00%<\/td>\n<\/tr>\n<tr>\n<td>https:\/\/belregion.ru\/<\/td>\n<td>Performance.prototype.now<\/td>\n<td>105 227<\/td>\n<td>1<\/td>\n<td>105 226<\/td>\n<td>100,00%<\/td>\n<\/tr>\n<tr>\n<td>https:\/\/tsargrad.tv\/<\/td>\n<td>Performance.prototype.now<\/td>\n<td>99 445<\/td>\n<td>1<\/td>\n<td>99 444<\/td>\n<td>100,00%<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<h2 id=\"measurement-results-for-opensource-websites-only\"><a class=\"toclink\" href=\"#measurement-results-for-opensource-websites-only\">Measurement results for opensource websites only<\/a><\/h2>\n<p>As this project focuses on free and open source software, we compared the general\nresults with a list of pages connected to free software or open source.<\/p>\n<p>We collected the list of the home pages from <a href=\"https:\/\/gitweb.gentoo.org\/repo\/gentoo.git\/tree\/\">Gentoo\nrepository<\/a>.<\/p>\n<p>We tried to visit all 5 271 collected websites. 4 528 homepages were successfully\nvisited in both modes, with and without uBlock Origin. More than 11 000 000 JS\ncalls were intercepted and stored into 2 <a href=\"https:\/\/nextcloud.fit.vutbr.cz\/s\/yoLa5rcGzkgbSka\">SQLite\ndatabases<\/a> that have a total\nsize of over 3 GB.<\/p>\n<p>Only the first 10 lines of the analysis results are usually listed below.\nComplete tables with all rows can be found in the <a href=\"https:\/\/nextcloud.fit.vutbr.cz\/s\/LWANmRxoXc5YYzy\">results stored on the\nserver<\/a>.<\/p>\n<h3 id=\"the-most-blocked-api_1\"><a class=\"toclink\" href=\"#the-most-blocked-api_1\">The most blocked API<\/a><\/h3>\n<table>\n<thead>\n<tr>\n<th>API<\/th>\n<th>Calls without uBlock<\/th>\n<th>Calls with uBlock<\/th>\n<th>Difference<\/th>\n<th>Difference [%]<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>Resource Timing Level 2<\/td>\n<td>348<\/td>\n<td>8<\/td>\n<td>340<\/td>\n<td>97,70%<\/td>\n<\/tr>\n<tr>\n<td>Non-Standard<\/td>\n<td>1 711<\/td>\n<td>66<\/td>\n<td>1 645<\/td>\n<td>96,14%<\/td>\n<\/tr>\n<tr>\n<td>Background Tasks API<\/td>\n<td>18 763<\/td>\n<td>1 037<\/td>\n<td>17 726<\/td>\n<td>94,47%<\/td>\n<\/tr>\n<tr>\n<td>Web Speech API<\/td>\n<td>306<\/td>\n<td>18<\/td>\n<td>288<\/td>\n<td>94,12%<\/td>\n<\/tr>\n<tr>\n<td>HTML: Plugins<\/td>\n<td>13<\/td>\n<td>2<\/td>\n<td>11<\/td>\n<td>84,62%<\/td>\n<\/tr>\n<tr>\n<td>Permissions API<\/td>\n<td>705<\/td>\n<td>186<\/td>\n<td>519<\/td>\n<td>73,62%<\/td>\n<\/tr>\n<tr>\n<td>Performance Timeline<\/td>\n<td>3 981<\/td>\n<td>1 088<\/td>\n<td>2 893<\/td>\n<td>72,67%<\/td>\n<\/tr>\n<tr>\n<td>High Resolution Time (Level 2)<\/td>\n<td>849 390<\/td>\n<td>232 806<\/td>\n<td>616 584<\/td>\n<td>72,59%<\/td>\n<\/tr>\n<tr>\n<td>Navigation Timing<\/td>\n<td>21<\/td>\n<td>6<\/td>\n<td>15<\/td>\n<td>71,43%<\/td>\n<\/tr>\n<tr>\n<td>Performance Timeline (Level 2)<\/td>\n<td>2 548<\/td>\n<td>745<\/td>\n<td>1 803<\/td>\n<td>70,76%<\/td>\n<\/tr>\n<tr>\n<td>Storage API<\/td>\n<td>3<\/td>\n<td>1<\/td>\n<td>2<\/td>\n<td>66,67%<\/td>\n<\/tr>\n<tr>\n<td>Beacon<\/td>\n<td>727<\/td>\n<td>250<\/td>\n<td>477<\/td>\n<td>65,61%<\/td>\n<\/tr>\n<tr>\n<td>Web Cryptography API<\/td>\n<td>7 632<\/td>\n<td>2 928<\/td>\n<td>4 704<\/td>\n<td>61,64%<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<h3 id=\"the-most-blocked-api-endpoints_1\"><a class=\"toclink\" href=\"#the-most-blocked-api-endpoints_1\">The most blocked API endpoints<\/a><\/h3>\n<table>\n<thead>\n<tr>\n<th>Endpoint<\/th>\n<th>API<\/th>\n<th>Calls without uBlock<\/th>\n<th>Calls with uBlock<\/th>\n<th>Difference<\/th>\n<th>Difference [%]<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>IdleDeadline.prototype.timeRemaining<\/td>\n<td>Background Tasks API<\/td>\n<td>2 258<\/td>\n<td>2<\/td>\n<td>2 256<\/td>\n<td>99,91%<\/td>\n<\/tr>\n<tr>\n<td>Element.prototype.getAttributeNames<\/td>\n<td>DOM<\/td>\n<td>1 648<\/td>\n<td>12<\/td>\n<td>1 636<\/td>\n<td>99,27%<\/td>\n<\/tr>\n<tr>\n<td>MessagePort.prototype.close<\/td>\n<td>HTML: Channel Messaging<\/td>\n<td>268<\/td>\n<td>2<\/td>\n<td>266<\/td>\n<td>99,25%<\/td>\n<\/tr>\n<tr>\n<td>Document.prototype.elementFromPoint<\/td>\n<td>CSS Object Model (CSSOM)<\/td>\n<td>2 554<\/td>\n<td>22<\/td>\n<td>2 532<\/td>\n<td>99,14%<\/td>\n<\/tr>\n<tr>\n<td>Range.prototype.selectNode<\/td>\n<td>DOM Level 2: Traversal and Range<\/td>\n<td>106<\/td>\n<td>1<\/td>\n<td>105<\/td>\n<td>99,06%<\/td>\n<\/tr>\n<tr>\n<td>CanvasRenderingContext2D.prototype.createLinearGradient<\/td>\n<td>HTML: Canvas Element<\/td>\n<td>4 634<\/td>\n<td>56<\/td>\n<td>4 578<\/td>\n<td>98,79%<\/td>\n<\/tr>\n<tr>\n<td>CanvasRenderingContext2D.prototype.transform<\/td>\n<td>HTML: Canvas Element<\/td>\n<td>37 792<\/td>\n<td>845<\/td>\n<td>36 947<\/td>\n<td>97,76%<\/td>\n<\/tr>\n<tr>\n<td>PerformanceResourceTiming.prototype.toJSON<\/td>\n<td>Resource Timing Level 2<\/td>\n<td>348<\/td>\n<td>8<\/td>\n<td>340<\/td>\n<td>97,70%<\/td>\n<\/tr>\n<tr>\n<td>Navigator.prototype.javaEnabled<\/td>\n<td>Non-Standard<\/td>\n<td>1 691<\/td>\n<td>47<\/td>\n<td>1 644<\/td>\n<td>97,22%<\/td>\n<\/tr>\n<tr>\n<td>PerformanceObserverEntryList.prototype.getEntriesByName<\/td>\n<td>Performance Timeline (Level 2)<\/td>\n<td>52<\/td>\n<td>2<\/td>\n<td>50<\/td>\n<td>96,15%<\/td>\n<\/tr>\n<tr>\n<td>CanvasGradient.prototype.addColorStop<\/td>\n<td>HTML: Canvas Element<\/td>\n<td>9 623<\/td>\n<td>453<\/td>\n<td>9 170<\/td>\n<td>95,29%<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<h3 id=\"use-of-apis-on-websites_1\"><a class=\"toclink\" href=\"#use-of-apis-on-websites_1\">Use of APIs on websites<\/a><\/h3>\n<table>\n<thead>\n<tr>\n<th>API<\/th>\n<th>Websites without uBlock [%]<\/th>\n<th>Websites with uBlock [%]<\/th>\n<th>Difference [%]<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>Non-Standard<\/td>\n<td>26,47%<\/td>\n<td>1,13%<\/td>\n<td>25,26%<\/td>\n<\/tr>\n<tr>\n<td>Web Cryptography API<\/td>\n<td>21,94%<\/td>\n<td>7,24%<\/td>\n<td>14,64%<\/td>\n<\/tr>\n<tr>\n<td>XMLHttpRequest<\/td>\n<td>26,42%<\/td>\n<td>14,94%<\/td>\n<td>11,41%<\/td>\n<\/tr>\n<tr>\n<td>HTML 5<\/td>\n<td>13,70%<\/td>\n<td>9,08%<\/td>\n<td>4,58%<\/td>\n<\/tr>\n<tr>\n<td>Beacon<\/td>\n<td>6,35%<\/td>\n<td>1,76%<\/td>\n<td>4,58%<\/td>\n<\/tr>\n<tr>\n<td>Performance Timeline<\/td>\n<td>10,07%<\/td>\n<td>5,68%<\/td>\n<td>4,36%<\/td>\n<\/tr>\n<tr>\n<td>High Resolution Time (Level 2)<\/td>\n<td>15,46%<\/td>\n<td>11,12%<\/td>\n<td>4,29%<\/td>\n<\/tr>\n<tr>\n<td>HTML: Channel Messaging<\/td>\n<td>10,79%<\/td>\n<td>6,46%<\/td>\n<td>4,29%<\/td>\n<\/tr>\n<tr>\n<td>HTML: Web Storage<\/td>\n<td>20,60%<\/td>\n<td>16,26%<\/td>\n<td>4,27%<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<h3 id=\"the-number-of-apis-used-by-the-website_1\"><a class=\"toclink\" href=\"#the-number-of-apis-used-by-the-website_1\">The number of APIs used by the website<\/a><\/h3>\n<p><strong>Average value<\/strong> - Number of APIs used without uBlock Origin: <strong>7.4<\/strong><\/p>\n<p><img alt=\"image\" src=\"crawling_results\/APIs_without_uBlock_opensource.png\"><\/p>\n<p><strong>Average value<\/strong> - Number of APIs used with uBlock Origin: <strong>6.2<\/strong><\/p>\n<p><img alt=\"image\" src=\"crawling_results\/APIs_with_uBlock_opensource.png\"><\/p>\n<h3 id=\"the-number-of-javascript-calls-on-websites_1\"><a class=\"toclink\" href=\"#the-number-of-javascript-calls-on-websites_1\">The number of JavaScript calls on websites<\/a><\/h3>\n<p><strong>Average value<\/strong> - Number of JavaScript calls made without uBlock Origin:\n<strong>1598.18<\/strong><\/p>\n<p><img alt=\"image\" src=\"crawling_results\/JScalls_without_uBlock_opensource.png\"><\/p>\n<p><strong>Average value<\/strong> - Number of JavaScript calls made with uBlock Origin:\n<strong>1304.06<\/strong><\/p>\n<p><img alt=\"image\" src=\"crawling_results\/JScalls_with_uBlock_opensource.png\"><\/p>\n<h3 id=\"the-number-of-endpoint-calls-on-websites_1\"><a class=\"toclink\" href=\"#the-number-of-endpoint-calls-on-websites_1\">The number of endpoint calls on websites<\/a><\/h3>\n<table>\n<thead>\n<tr>\n<th>Website<\/th>\n<th>Endpoint<\/th>\n<th>Calls without uBlock<\/th>\n<th>Calls with uBlock<\/th>\n<th>Difference<\/th>\n<th>Difference [%]<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>https:\/\/wci.llnl.gov\/<\/td>\n<td>Element.prototype.hasAttribute<\/td>\n<td>34 860<\/td>\n<td>1<\/td>\n<td>34 859<\/td>\n<td>100,00%<\/td>\n<\/tr>\n<tr>\n<td>https:\/\/wiibrew.org\/wiki\/Main_Page<\/td>\n<td>Performance.prototype.now<\/td>\n<td>6 512<\/td>\n<td>1<\/td>\n<td>6 511<\/td>\n<td>99,98%<\/td>\n<\/tr>\n<tr>\n<td>http:\/\/www.vsound.org\/<\/td>\n<td>Document.prototype.getElementById<\/td>\n<td>5 797<\/td>\n<td>1<\/td>\n<td>5 796<\/td>\n<td>99,98%<\/td>\n<\/tr>\n<tr>\n<td>http:\/\/wormsofprey.org\/<\/td>\n<td>Document.prototype.getElementById<\/td>\n<td>5 793<\/td>\n<td>1<\/td>\n<td>5 792<\/td>\n<td>99,98%<\/td>\n<\/tr>\n<tr>\n<td>http:\/\/konst.org.ua\/<\/td>\n<td>Element.prototype.setAttribute<\/td>\n<td>4 248<\/td>\n<td>1<\/td>\n<td>4 247<\/td>\n<td>99,98%<\/td>\n<\/tr>\n<tr>\n<td>https:\/\/www.union.edu\/mathematics<\/td>\n<td>Performance.prototype.now<\/td>\n<td>5 317<\/td>\n<td>2<\/td>\n<td>5 315<\/td>\n<td>99,96%<\/td>\n<\/tr>\n<tr>\n<td>https:\/\/www.mongodb.com\/<\/td>\n<td>window.getComputedStyle<\/td>\n<td>30 516<\/td>\n<td>14<\/td>\n<td>30 502<\/td>\n<td>99,95%<\/td>\n<\/tr>\n<tr>\n<td>https:\/\/www.monotype.com\/<\/td>\n<td>Performance.prototype.now<\/td>\n<td>4 091<\/td>\n<td>2<\/td>\n<td>4 089<\/td>\n<td>99,95%<\/td>\n<\/tr>\n<tr>\n<td>https:\/\/sylabs.io\/<\/td>\n<td>Performance.prototype.now<\/td>\n<td>1 839<\/td>\n<td>1<\/td>\n<td>1 838<\/td>\n<td>99,95%<\/td>\n<\/tr>\n<tr>\n<td>http:\/\/vlgothic.dicey.org\/<\/td>\n<td>Performance.prototype.now<\/td>\n<td>3 427<\/td>\n<td>2<\/td>\n<td>3 425<\/td>\n<td>99,94%<\/td>\n<\/tr>\n<tr>\n<td>https:\/\/www.ppsspp.org\/<\/td>\n<td>Performance.prototype.now<\/td>\n<td>3 397<\/td>\n<td>2<\/td>\n<td>3 395<\/td>\n<td>99,94%<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<h2 id=\"measurement-results-for-fingerprint-detector\"><a class=\"toclink\" href=\"#measurement-results-for-fingerprint-detector\">Measurement results for FingerPrint Detector<\/a><\/h2>\n<p>We designed the web crawling primarily to retrieve data to create or evaluate\nheuristics for the <a href=\"\/fpdetection\/\">FingerPrint Detector (FPD)<\/a>. The above\nresults are helpful in understanding the JavaScript APIs usage on the web. For\nFPD, only the name of the endpoint and the weight of this endpoint are\nimportant. The weight expresses how often a given endpoint is used to create a\nfingerprint.<\/p>\n<p>The resulting table below was created from crawled data from websites of the\nTranco list . Two data sets were combined to obtain more accurate results. The\nfirst dataset obtained while crawling with uBlock Origin, the second one\nobtained while crawling with uMatrix. The resulting endpoint weight (marked as\n<code>average_weight<\/code>) was calculated in Python as follows:<\/p>\n<div class=\"highlight\"><pre><span><\/span><code><span class=\"nv\">weight_uMatrix<\/span> <span class=\"o\">=<\/span> <span class=\"nv\">max<\/span><span class=\"ss\">(<\/span><span class=\"mi\">0<\/span>, <span class=\"mi\">10<\/span> <span class=\"o\">+<\/span> <span class=\"ss\">((<\/span><span class=\"mi\">10<\/span><span class=\"o\">-<\/span><span class=\"nv\">len<\/span><span class=\"ss\">(<\/span><span class=\"nv\">str<\/span><span class=\"ss\">(<\/span><span class=\"nv\">calls<\/span><span class=\"ss\">)))<\/span><span class=\"o\">\/<\/span><span class=\"mi\">2<\/span><span class=\"ss\">)<\/span> <span class=\"o\">-<\/span> <span class=\"ss\">((<\/span><span class=\"mi\">100<\/span><span class=\"o\">-<\/span><span class=\"ss\">(<\/span><span class=\"mi\">100<\/span><span class=\"o\">*<\/span><span class=\"nv\">difference_uMatrix_percent<\/span><span class=\"ss\">))<\/span><span class=\"o\">\/<\/span><span class=\"mi\">3<\/span><span class=\"ss\">)<\/span> <span class=\"ss\">)<\/span>\n<span class=\"nv\">weight_uBlock<\/span> <span class=\"o\">=<\/span> <span class=\"nv\">max<\/span><span class=\"ss\">(<\/span><span class=\"mi\">0<\/span>, <span class=\"mi\">10<\/span> <span class=\"o\">+<\/span> <span class=\"ss\">((<\/span><span class=\"mi\">10<\/span><span class=\"o\">-<\/span><span class=\"nv\">len<\/span><span class=\"ss\">(<\/span><span class=\"nv\">str<\/span><span class=\"ss\">(<\/span><span class=\"nv\">calls<\/span><span class=\"ss\">)))<\/span><span class=\"o\">\/<\/span><span class=\"mi\">2<\/span><span class=\"ss\">)<\/span> <span class=\"o\">-<\/span> <span class=\"ss\">((<\/span><span class=\"mi\">100<\/span><span class=\"o\">-<\/span><span class=\"ss\">(<\/span><span class=\"mi\">100<\/span><span class=\"o\">*<\/span><span class=\"nv\">difference_uBlock_percent<\/span><span class=\"ss\">))<\/span><span class=\"o\">\/<\/span><span class=\"mi\">3<\/span><span class=\"ss\">)<\/span> <span class=\"ss\">)<\/span>\n\n<span class=\"k\">if<\/span> <span class=\"nv\">weight_uMatrix<\/span> <span class=\"o\">&gt;<\/span> <span class=\"mi\">0<\/span> <span class=\"nv\">and<\/span> <span class=\"nv\">weight_uBlock<\/span> <span class=\"o\">&gt;<\/span> <span class=\"mi\">0<\/span>:\n    <span class=\"nv\">average_weight<\/span> <span class=\"o\">=<\/span> <span class=\"nv\">round<\/span><span class=\"ss\">((<\/span><span class=\"nv\">weight_uMatrix<\/span> <span class=\"o\">+<\/span> <span class=\"nv\">weight_uBlock<\/span><span class=\"ss\">)<\/span><span class=\"o\">\/<\/span><span class=\"mi\">2<\/span><span class=\"ss\">)<\/span>\n<span class=\"k\">else<\/span>:\n    <span class=\"nv\">average_weight<\/span> <span class=\"o\">=<\/span> <span class=\"mi\">0<\/span>\n<\/code><\/pre><\/div>\n\n<p>Again, only the first 10 lines of the FPD analysis result are listed below.\nComplete table with all rows can be found in the <a href=\"https:\/\/nextcloud.fit.vutbr.cz\/s\/GjkTJzweccgxw6n\">results stored on the\nserver<\/a>.<\/p>\n<table>\n<thead>\n<tr>\n<th>Endpoint<\/th>\n<th>Average weight<\/th>\n<th>uMatrix weight<\/th>\n<th>uBlock weight<\/th>\n<th>Calls without extension<\/th>\n<th>Calls with uMatrix<\/th>\n<th>Calls with uBlock<\/th>\n<th>Difference uMatrix<\/th>\n<th>Difference uBlock<\/th>\n<th>Difference uMatrix [%]<\/th>\n<th>Difference uBlock [%]<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>Range.prototype.setStartAfter<\/td>\n<td>13<\/td>\n<td>13,427<\/td>\n<td>13,060<\/td>\n<td>455<\/td>\n<td>1<\/td>\n<td>6<\/td>\n<td>454<\/td>\n<td>449<\/td>\n<td>0,998<\/td>\n<td>0,987<\/td>\n<\/tr>\n<tr>\n<td>Range.prototype.insertNode<\/td>\n<td>13<\/td>\n<td>13,138<\/td>\n<td>12,920<\/td>\n<td>460<\/td>\n<td>5<\/td>\n<td>8<\/td>\n<td>455<\/td>\n<td>452<\/td>\n<td>0,989<\/td>\n<td>0,983<\/td>\n<\/tr>\n<tr>\n<td>HTMLFormControlsCollection.prototype.namedItem<\/td>\n<td>13<\/td>\n<td>12,714<\/td>\n<td>12,623<\/td>\n<td>2 211<\/td>\n<td>19<\/td>\n<td>25<\/td>\n<td>2 192<\/td>\n<td>2 186<\/td>\n<td>0,991<\/td>\n<td>0,989<\/td>\n<\/tr>\n<tr>\n<td>Document.prototype.open<\/td>\n<td>12<\/td>\n<td>12,475<\/td>\n<td>11,274<\/td>\n<td>8 190<\/td>\n<td>129<\/td>\n<td>424<\/td>\n<td>8 061<\/td>\n<td>7 766<\/td>\n<td>0,984<\/td>\n<td>0,948<\/td>\n<\/tr>\n<tr>\n<td>MessagePort.prototype.close<\/td>\n<td>12<\/td>\n<td>12,438<\/td>\n<td>12,424<\/td>\n<td>16 208<\/td>\n<td>30<\/td>\n<td>37<\/td>\n<td>16 178<\/td>\n<td>16 171<\/td>\n<td>0,998<\/td>\n<td>0,998<\/td>\n<\/tr>\n<tr>\n<td>Document.prototype.getElementsByTagNameNS<\/td>\n<td>12<\/td>\n<td>12,374<\/td>\n<td>10,811<\/td>\n<td>533<\/td>\n<td>18<\/td>\n<td>43<\/td>\n<td>515<\/td>\n<td>490<\/td>\n<td>0,966<\/td>\n<td>0,919<\/td>\n<\/tr>\n<tr>\n<td>Document.prototype.close<\/td>\n<td>12<\/td>\n<td>12,086<\/td>\n<td>11,944<\/td>\n<td>7 731<\/td>\n<td>212<\/td>\n<td>245<\/td>\n<td>7 519<\/td>\n<td>7 486<\/td>\n<td>0,973<\/td>\n<td>0,968<\/td>\n<\/tr>\n<tr>\n<td>SubtleCrypto.prototype.generateKey<\/td>\n<td>12<\/td>\n<td>11,876<\/td>\n<td>11,503<\/td>\n<td>985<\/td>\n<td>48<\/td>\n<td>59<\/td>\n<td>937<\/td>\n<td>926<\/td>\n<td>0,951<\/td>\n<td>0,940<\/td>\n<\/tr>\n<tr>\n<td>Document.prototype.elementsFromPoint<\/td>\n<td>12<\/td>\n<td>12,469<\/td>\n<td>11,365<\/td>\n<td>46 938<\/td>\n<td>44<\/td>\n<td>1 598<\/td>\n<td>46 894<\/td>\n<td>45 340<\/td>\n<td>0,999<\/td>\n<td>0,966<\/td>\n<\/tr>\n<tr>\n<td>Navigator.prototype.javaEnabled<\/td>\n<td>11<\/td>\n<td>10,892<\/td>\n<td>10,892<\/td>\n<td>110 665<\/td>\n<td>3 677<\/td>\n<td>3 679<\/td>\n<td>106 988<\/td>\n<td>106 986<\/td>\n<td>0,967<\/td>\n<td>0,967<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>More about FingerPrint Detector is written in the <a href=\"\/fpdetection\/\">following\nblogpost<\/a>.<\/p>\n<h2 id=\"conclusion\"><a class=\"toclink\" href=\"#conclusion\">Conclusion<\/a><\/h2>\n<p>The crawl identified JavaScript endpoints, often used to create a browser\nfingerprint. The observed data allow assigning weights for each endpoint.\n<a href=\"\/fpdetection\/\">FingerPrint Detector<\/a> configuration file uses the crawl results.<\/p>","category":{"@attributes":{"term":"pt"}}},{"title":"Farbling-based wrappers to hinder browser fingerprinting","link":{"@attributes":{"href":"https:\/\/jshelter.org\/farbling\/","rel":"alternate"}},"published":"2021-08-23T09:00:00+02:00","updated":"2025-07-10T07:01:03+02:00","author":{"name":"The JShelter team"},"id":"tag:jshelter.org,2021-08-23:\/farbling\/","summary":"<p><a href=\"https:\/\/arxiv.org\/pdf\/1905.01051.pdf\">Browser fingerprinting<\/a> is a more and more popular technique used to identify browsers. The fingerprint is computed based on the results of JavaScript calls, the content of HTTP headers, hardware characteristics, underlying operating system and other software information. Consequently, browser fingerprints are used for cross-domain tracking. However, users cannot clear \u2026<\/p>","content":"<p><a href=\"https:\/\/arxiv.org\/pdf\/1905.01051.pdf\">Browser fingerprinting<\/a> is a more and more popular technique used to identify browsers. The fingerprint is computed based on the results of JavaScript calls, the content of HTTP headers, hardware characteristics, underlying operating system and other software information. Consequently, browser fingerprints are used for cross-domain tracking. However, users cannot clear their browser fingerprint as it is not stored on the client-side. It is also challenging to determine whether a browser is being fingerprinted.<\/p>\n<p>Another issue that hinders fingerprinting protection is the ever-changing variety of supported APIs. Browsers implement new APIs over time, and existing APIs change. Consequently, it is necessary to continuously monitor the APIs being used for fingerprinting purposes to block fingerprinting attempts.<\/p>\n<p>Due to fingerprinting scripts being <a href=\"https:\/\/www.cs.princeton.edu\/~arvindn\/publications\/OpenWPM_1_million_site_tracking_measurement.pdf\">more prevalent<\/a>, various web browsers - for example, Tor, Brave, and Firefox - started implementing fingerprinting protection to protect users and their privacy.<\/p>\n<p>This post contains:<\/p>\n<div class=\"toc\">\n<ul>\n<li><a href=\"#brave-fingerprinting-protection\">Brave fingerprinting protection<\/a><ul>\n<li><a href=\"#how-does-farbling-work\">How does farbling work?<\/a><\/li>\n<li><a href=\"#canvas\">Canvas<\/a><\/li>\n<li><a href=\"#webgl\">WebGL<\/a><\/li>\n<li><a href=\"#web-audio\">Web Audio<\/a><\/li>\n<li><a href=\"#plugins\">Plugins<\/a><\/li>\n<li><a href=\"#user-agent\">User agent<\/a><\/li>\n<li><a href=\"#enumeratedevices\">EnumerateDevices<\/a><\/li>\n<li><a href=\"#hardwareconcurrency\">HardwareConcurrency<\/a><\/li>\n<\/ul>\n<\/li>\n<li><a href=\"#porting-farbling-to-jshelter\">Porting Farbling to JShelter<\/a><\/li>\n<li><a href=\"#conclusion\">Conclusion<\/a><\/li>\n<\/ul>\n<\/div>\n<h2 id=\"brave-fingerprinting-protection\"><a class=\"toclink\" href=\"#brave-fingerprinting-protection\">Brave fingerprinting protection<\/a><\/h2>\n<p>Why is Brave's Farbling special? Until recently, <a href=\"https:\/\/2019.www.torproject.org\/projects\/torbrowser\/design\/#fingerprinting-linkability\">Tor browser<\/a> had the most robust defence against fingerprinting. It (1) implemented modifications in various APIs, (2) blocks some other APIs, (3) runs in a window of predefined size, etc. to ensure all users have the same fingerprint. This approach is very effective at producing uniform fingerprint for all users, which makes it difficult for fingerprinters to differentiate between browsers. Still, such fingerprint is also brittle -- minor changes like resizing the window could cause the browser to have a unique fingerprint. Hence, users need to follow inconvenient steps to keep their fingerprint uniform.<\/p>\n<p>With all this in <a href=\"https:\/\/brave.com\/brave-fingerprinting-and-privacy-budgets\/\">mind<\/a>, Brave software decided to improve their fingerprinting protection. They <a href=\"https:\/\/brave.com\/privacy-updates-3\/\">proposed<\/a> new fingerprinting protection, Farbling, arguing that it is (almost) impossible to produce uniform fingerprint without compromising user experience. Their countermeasures involve randomising values based on previous research papers <a href=\"https:\/\/www.doc.ic.ac.uk\/~livshits\/papers\/pdf\/www15.pdf\">PriVaricator<\/a> and <a href=\"https:\/\/hal.inria.fr\/hal-01527580\/document\">FPRandom<\/a> Both papers have shown promising results, and Brave has perfected this approach, creating effective defence while retaining almost full user experience. Farbling is a comprehensive collection of modifications that aim at producing a unique fingerprint on every domain and in every session.<\/p>\n<h3 id=\"how-does-farbling-work\"><a class=\"toclink\" href=\"#how-does-farbling-work\">How does farbling work?<\/a><\/h3>\n<p>Farbling uses generated session and <a href=\"https:\/\/web.dev\/same-site-same-origin\/\">eTLD+1<\/a> keys to deterministically change outputs of certain APIs commonly used for browser fingerprinting. These little lies result in different websites calculating different fingerprints. Moreover, a previously visited website calculates a different fingerprint in a new browsing session.<\/p>\n<p>Farbling implementation is publicly available on Github <a href=\"https:\/\/github.com\/brave\/brave-browser\/issues\/8787\">issue<\/a> with discussions on design decisions, future plans and possible changes in a separate <a href=\"https:\/\/github.com\/brave\/brave-browser\/issues\/11770\">issue<\/a>.<\/p>\n<p>Farbling operates on three levels:\n 1. <strong>Off<\/strong> - countermeasures are not active\n 2. <strong>Balanced<\/strong> - various APIs have modified values based on domain\/session keys\n 3. <strong>Maximum<\/strong> - various  APIs values replaced by randomised values based on domain\/session keys<\/p>\n<p>Now, what changes did actually Brave implement to specific APIs?<\/p>\n<h3 id=\"canvas\"><a class=\"toclink\" href=\"#canvas\">Canvas<\/a><\/h3>\n<p>Canvas modifications are tracked in a separate <a href=\"https:\/\/github.com\/brave\/brave-browser\/issues\/9186\">issue<\/a>.\nBoth <em>balanced<\/em> and <em>maximum<\/em> approach modify API calls <code>CanvasRendering2dContext.getImageData<\/code>,\n<code>HTMLCanvasElement.toDataURL<\/code>,\n<code>HTMLCanvasElement.toBlob<\/code>, and\n<code>OffscreenCanvas.convertToBlob<\/code>. A <a href=\"https:\/\/github.com\/brave\/brave-core\/blob\/master\/chromium_src\/third_party\/blink\/renderer\/core\/execution_context\/execution_context.cc\">Filter function<\/a> changes values of certain pixels chosen based on session\/domain keys, resulting in a unique canvas fingerprint.\nOn <em>maximum<\/em> level, methods <code>CanvasRenderingContext2D.isPointInPath<\/code> and <code>CanvasRenderingContext2D.isPointInStroke<\/code> always return <em>false<\/em>.<\/p>\n<h3 id=\"webgl\"><a class=\"toclink\" href=\"#webgl\">WebGL<\/a><\/h3>\n<p>Modifications for both WebGL and WebGL2 are described in issues <a href=\"https:\/\/github.com\/brave\/brave-browser\/issues\/9188\">webgl<\/a> , <a href=\"https:\/\/github.com\/brave\/brave-browser\/issues\/9189\">webgl2<\/a>.\nOn <em>balanced<\/em> level\n<code>WebGLRenderingContext.getParameter<\/code> and other methods return slightly modified values.\n<code>WebGLRenderingContext.readPixels<\/code> is modified similarly to canvas methods.\nOn <em>maximum<\/em> level, <code>WebGLRenderingContext.getParameter<\/code> returns random strings for unmasked vendor and renderer, bottom values for other arguments. Other modified calls return bottom values. All modifications can be found in the issues mentioned above or directly in the <a href=\"https:\/\/github.com\/brave\/brave-core\/tree\/master\/chromium_src\/third_party\/blink\/renderer\/modules\/webgl\">code<\/a>.<\/p>\n<h3 id=\"web-audio\"><a class=\"toclink\" href=\"#web-audio\">Web Audio<\/a><\/h3>\n<p>The <a href=\"https:\/\/github.com\/brave\/brave-browser\/issues\/9187\">issue<\/a> modifies\nseveral endpoints of <code>AnalyserNode<\/code>  and  <code>AudioBuffer<\/code>  APIs used for audio data handling are modified.  On the <em>balanced<\/em> level, the amplitude of returned audio data is slightly changed based on the domain key. However, data are replaced by white noise generated from domain hash on the maximum level, so there is no relation with original data.<\/p>\n<h3 id=\"plugins\"><a class=\"toclink\" href=\"#plugins\">Plugins<\/a><\/h3>\n<p>Currently,\n<code>navigator.plugins<\/code> and <code>navigator.mimeTypes<\/code> are modified on <em>balanced<\/em> level to return an array with altered plugins and two fake plugins. On <em>maximum<\/em> level, the returned array contains only two fake plugins.\nSee <a href=\"https:\/\/github.com\/brave\/brave-browser\/issues\/9435\">issue1<\/a> and <a href=\"https:\/\/github.com\/brave\/brave-browser\/issues\/10597\">issue2<\/a> for more details.<\/p>\n<h3 id=\"user-agent\"><a class=\"toclink\" href=\"#user-agent\">User agent<\/a><\/h3>\n<p>Brave employs the default Chrome UA and the newest OS version as the user agent string. Also, a random number of blank spaces (up to 5) appended to the end of the user agent string.\nFor more details, see the <a href=\"https:\/\/github.com\/brave\/brave-browser\/issues\/9190\">GitHub issue<\/a>.<\/p>\n<h3 id=\"enumeratedevices\"><a class=\"toclink\" href=\"#enumeratedevices\">EnumerateDevices<\/a><\/h3>\n<p>This API is used to list I\/O media devices like microphone or speakers.  When fingerprinting protection is active, Brave returns a shuffled list of devices. For more details, see\n<a href=\"https:\/\/github.com\/brave\/brave-browser\/issues\/11271\">issue1<\/a> and\n<a href=\"https:\/\/github.com\/brave\/brave-browser\/issues\/8666\">issue2<\/a>.<\/p>\n<h3 id=\"hardwareconcurrency\"><a class=\"toclink\" href=\"#hardwareconcurrency\">HardwareConcurrency<\/a><\/h3>\n<p>The number of logical processors returned by this interface is modified as follows -- on <em>balanced<\/em> level, a valid value between 2 and the true value, on <em>maximum<\/em> level, a valid value between 2 and 8.\nSee the <a href=\"https:\/\/github.com\/brave\/brave-browser\/issues\/10808\">GitHub issue<\/a> for more details.<\/p>\n<h1 id=\"porting-farbling-to-jshelter\"><a class=\"toclink\" href=\"#porting-farbling-to-jshelter\">Porting Farbling to JShelter<\/a><\/h1>\n<p>Our goal was to extend JShelter anti-fingerprinting protections with similar measures to those available in Brave's Farbling.\nWe decided to implement Brave Farbling with minor tweaks. As Brave is an open-source project based on <a href=\"https:\/\/www.chromium.org\/Home\">Chromium<\/a>, core changes are available in the public <a href=\"https:\/\/github.com\/brave\/brave-core\">repository<\/a>. Furthermore, as Brave is licensed under <a href=\"https:\/\/www.mozilla.org\/en-US\/MPL\/2.0\/\">MPL 2.0<\/a> license, its countermeasures can be ported to JShelter.\nSimilarly to Brave, JShelter utilises session and domain hashes (currently, we use a different domain hash based on origin, however, we consider switching to the eTLD+1 approach used by Brave). Nevertheless, we ported only those changes that an extension can reasonably apply. So we do not plan to change system fonts as the true set of fonts can leak in several ways (e.g., CSS, canvas). We will keep a close eye on anti-fingerprining techniquest applied by Brave in the future.<\/p>\n<p>Former JShelter defences were left as an option so user can choose which protection they want. For example, for <strong>Canvas API<\/strong>, JShelter retains the old defence that returns a white image, but it is also possible to use Farbling and slightly modify the image.<\/p>\n<p><code>CanvasRenderingContext2D.isPointInPath<\/code> and <code>CanvasRenderingContext2D.isPointInStroke<\/code> are modified to return <em>false<\/em> with 5% probability, returning <em>false<\/em> to every call seems to be easily identifiable and it limits the usablity of the calls.<\/p>\n<p><strong>WebGL<\/strong>, <strong>Web audio<\/strong>, <strong>plugins<\/strong>,  <strong>hardwareConcurrency<\/strong> and <strong>deviceMemory<\/strong> have been changed accordingly to Brave. API <strong>enumerateDevices<\/strong> has the same functionality as in Brave. In addition, we add fake devices to the list. <strong>User agent<\/strong> wasn't modified because it can cause compatibility issues as we support multiple browsers. Adding empty spaces at the end of UAS seems to be quite a weak countermeasure. We will continue to watch changes in the user agent and may implement some defence in future, although it looks like a <a href=\"https:\/\/datatracker.ietf.org\/doc\/html\/rfc8942\">better solution<\/a> is on the way.<\/p>\n<p>JShelter 0.5 changes the default level -- <strong>level 2<\/strong> to apply the farbling-based defence for all covered APIs, and it will be very similar to the <em>balanced<\/em> level of <em>Brave<\/em>. <strong>Level 3<\/strong> is redesigned to partly apply new and partly old countermeasures to provide as little information as possible. Please report websites that does not work correctly with Farbling.<\/p>\n<p>During the examination of the ported code, we <a href=\"https:\/\/github.com\/brave\/brave-browser\/issues\/15882\">identified and reported<\/a> an issue in the original Brave implementation. The issue was acknowledged and fixed by Brave. This is the beauty of the free software: several projects can benefit from the same code-base and mutualy improve the quality.<\/p>\n<h1 id=\"conclusion\"><a class=\"toclink\" href=\"#conclusion\">Conclusion<\/a><\/h1>\n<p>Farbling-based wrappers produce very similar outputs to Brave. So with JShelter, Farbling-like capabilities are available in multiple browsers. Nevertheless, keep in mind that the best anti-fingerprinting techniques are still a research question, fingerprinting techniques are deployed for security reasons (and farbling-like anti-fingerprinting masking may complicate some log in processes), so it is not completely clear what defences are the best and the choice of the defences also depends on specific use cases. We will investigate fingerprinting scripts further during the future work on this project.<\/p>","category":{"@attributes":{"term":"posts"}}},{"title":"Farbling-based wrappers to hinder browser fingerprinting","link":{"@attributes":{"href":"https:\/\/jshelter.org\/pt\/farbling\/","rel":"alternate"}},"published":"2021-08-23T09:00:00+02:00","updated":"2025-07-10T07:01:03+02:00","author":{"name":"The JShelter team"},"id":"tag:jshelter.org,2021-08-23:\/pt\/farbling\/","summary":"<p><a href=\"https:\/\/arxiv.org\/pdf\/1905.01051.pdf\">Browser fingerprinting<\/a> is a more and more\npopular technique used to identify browsers. The fingerprint is computed based\non the results of JavaScript calls, the content of HTTP headers, hardware\ncharacteristics, underlying operating system and other software information.\nConsequently, browser fingerprints are used for cross-domain tracking. However,\nusers cannot clear \u2026<\/p>","content":"<p><a href=\"https:\/\/arxiv.org\/pdf\/1905.01051.pdf\">Browser fingerprinting<\/a> is a more and more\npopular technique used to identify browsers. The fingerprint is computed based\non the results of JavaScript calls, the content of HTTP headers, hardware\ncharacteristics, underlying operating system and other software information.\nConsequently, browser fingerprints are used for cross-domain tracking. However,\nusers cannot clear their browser fingerprint as it is not stored on the\nclient-side. It is also challenging to determine whether a browser is being\nfingerprinted.<\/p>\n<p>Another issue that hinders fingerprinting protection is the ever-changing variety\nof supported APIs. Browsers implement new APIs over time, and existing APIs\nchange. Consequently, it is necessary to continuously monitor the APIs being\nused for fingerprinting purposes to block fingerprinting attempts.<\/p>\n<p>Due to fingerprinting scripts being <a href=\"https:\/\/www.cs.princeton.edu\/~arvindn\/publications\/OpenWPM_1_million_site_tracking_measurement.pdf\">more\nprevalent<\/a>,\nvarious web browsers - for example, Tor, Brave, and Firefox - started\nimplementing fingerprinting protection to protect users and their privacy.<\/p>\n<p>This post contains:<\/p>\n<div class=\"toc\">\n<ul>\n<li><a href=\"#brave-fingerprinting-protection\">Brave fingerprinting protection<\/a><ul>\n<li><a href=\"#how-does-farbling-work\">How does farbling work?<\/a><\/li>\n<li><a href=\"#canvas\">Canvas<\/a><\/li>\n<li><a href=\"#webgl\">WebGL<\/a><\/li>\n<li><a href=\"#web-audio\">Web Audio<\/a><\/li>\n<li><a href=\"#plugins\">Plugins<\/a><\/li>\n<li><a href=\"#user-agent\">User agent<\/a><\/li>\n<li><a href=\"#enumeratedevices\">EnumerateDevices<\/a><\/li>\n<li><a href=\"#hardwareconcurrency\">HardwareConcurrency<\/a><\/li>\n<\/ul>\n<\/li>\n<li><a href=\"#porting-farbling-to-jshelter\">Porting Farbling to JShelter<\/a><\/li>\n<li><a href=\"#conclusion\">Conclusion<\/a><\/li>\n<\/ul>\n<\/div>\n<h2 id=\"brave-fingerprinting-protection\"><a class=\"toclink\" href=\"#brave-fingerprinting-protection\">Brave fingerprinting protection<\/a><\/h2>\n<p>Why is Brave's Farbling special? Until recently, <a href=\"https:\/\/2019.www.torproject.org\/projects\/torbrowser\/design\/#fingerprinting-linkability\">Tor\nbrowser<\/a>\nhad the most robust defence against fingerprinting. It (1) implemented\nmodifications in various APIs, (2) blocks some other APIs, (3) runs in a window\nof predefined size, etc. to ensure all users have the same fingerprint. This\napproach is very effective at producing uniform fingerprint for all users, which\nmakes it difficult for fingerprinters to differentiate between browsers. Still,\nsuch fingerprint is also brittle -- minor changes like resizing the window could\ncause the browser to have a unique fingerprint. Hence, users need to follow\ninconvenient steps to keep their fingerprint uniform.<\/p>\n<p>With all this in\n<a href=\"https:\/\/brave.com\/brave-fingerprinting-and-privacy-budgets\/\">mind<\/a>, Brave\nsoftware decided to improve their fingerprinting protection. They\n<a href=\"https:\/\/brave.com\/privacy-updates-3\/\">proposed<\/a> new fingerprinting protection,\nFarbling, arguing that it is (almost) impossible to produce uniform fingerprint\nwithout compromising user experience. Their countermeasures involve randomising\nvalues based on previous research papers\n<a href=\"https:\/\/www.doc.ic.ac.uk\/~livshits\/papers\/pdf\/www15.pdf\">PriVaricator<\/a> and\n<a href=\"https:\/\/hal.inria.fr\/hal-01527580\/document\">FPRandom<\/a> Both papers have shown\npromising results, and Brave has perfected this approach, creating effective\ndefence while retaining almost full user experience. Farbling is a comprehensive\ncollection of modifications that aim at producing a unique fingerprint on every\ndomain and in every session.<\/p>\n<h3 id=\"how-does-farbling-work\"><a class=\"toclink\" href=\"#how-does-farbling-work\">How does farbling work?<\/a><\/h3>\n<p>Farbling uses generated session and\n<a href=\"https:\/\/web.dev\/same-site-same-origin\/\">eTLD+1<\/a> keys to deterministically\nchange outputs of certain APIs commonly used for browser fingerprinting. These\nlittle lies result in different websites calculating different fingerprints.\nMoreover, a previously visited website calculates a different fingerprint in a\nnew browsing session.<\/p>\n<p>Farbling implementation is publicly available on Github\n<a href=\"https:\/\/github.com\/brave\/brave-browser\/issues\/8787\">issue<\/a> with discussions on\ndesign decisions, future plans and possible changes in a separate\n<a href=\"https:\/\/github.com\/brave\/brave-browser\/issues\/11770\">issue<\/a>.<\/p>\n<p>Farbling operates on three levels:<\/p>\n<ol>\n<li><strong>Off<\/strong> - countermeasures are not active<\/li>\n<li><strong>Balanced<\/strong> - various APIs have modified values based on domain\/session keys<\/li>\n<li><strong>Maximum<\/strong> - various APIs values replaced by randomised values based on\ndomain\/session keys<\/li>\n<\/ol>\n<p>Now, what changes did actually Brave implement to specific APIs?<\/p>\n<h3 id=\"canvas\"><a class=\"toclink\" href=\"#canvas\">Canvas<\/a><\/h3>\n<p>Canvas modifications are tracked in a separate\n<a href=\"https:\/\/github.com\/brave\/brave-browser\/issues\/9186\">issue<\/a>. Both <em>balanced<\/em> and\n<em>maximum<\/em> approach modify API calls <code>CanvasRendering2dContext.getImageData<\/code>,\n<code>HTMLCanvasElement.toDataURL<\/code>, <code>HTMLCanvasElement.toBlob<\/code>, and\n<code>OffscreenCanvas.convertToBlob<\/code>. A <a href=\"https:\/\/github.com\/brave\/brave-core\/blob\/master\/chromium_src\/third_party\/blink\/renderer\/core\/execution_context\/execution_context.cc\">Filter\nfunction<\/a>\nchanges values of certain pixels chosen based on session\/domain keys, resulting\nin a unique canvas fingerprint. On <em>maximum<\/em> level, methods\n<code>CanvasRenderingContext2D.isPointInPath<\/code> and\n<code>CanvasRenderingContext2D.isPointInStroke<\/code> always return <em>false<\/em>.<\/p>\n<h3 id=\"webgl\"><a class=\"toclink\" href=\"#webgl\">WebGL<\/a><\/h3>\n<p>Modifications for both WebGL and WebGL2 are described in issues\n<a href=\"https:\/\/github.com\/brave\/brave-browser\/issues\/9188\">webgl<\/a> ,\n<a href=\"https:\/\/github.com\/brave\/brave-browser\/issues\/9189\">webgl2<\/a>. On <em>balanced<\/em>\nlevel <code>WebGLRenderingContext.getParameter<\/code> and other methods return slightly\nmodified values. <code>WebGLRenderingContext.readPixels<\/code> is modified similarly to\ncanvas methods. On <em>maximum<\/em> level, <code>WebGLRenderingContext.getParameter<\/code> returns\nrandom strings for unmasked vendor and renderer, bottom values for other\narguments. Other modified calls return bottom values. All modifications can be\nfound in the issues mentioned above or directly in the\n<a href=\"https:\/\/github.com\/brave\/brave-core\/tree\/master\/chromium_src\/third_party\/blink\/renderer\/modules\/webgl\">code<\/a>.<\/p>\n<h3 id=\"web-audio\"><a class=\"toclink\" href=\"#web-audio\">Web Audio<\/a><\/h3>\n<p>The <a href=\"https:\/\/github.com\/brave\/brave-browser\/issues\/9187\">issue<\/a> modifies several\nendpoints of <code>AnalyserNode<\/code> and <code>AudioBuffer<\/code> APIs used for audio data handling\nare modified. On the <em>balanced<\/em> level, the amplitude of returned audio data is\nslightly changed based on the domain key. However, data are replaced by white\nnoise generated from domain hash on the maximum level, so there is no relation\nwith original data.<\/p>\n<h3 id=\"plugins\"><a class=\"toclink\" href=\"#plugins\">Plugins<\/a><\/h3>\n<p>Currently, <code>navigator.plugins<\/code> and <code>navigator.mimeTypes<\/code> are modified on\n<em>balanced<\/em> level to return an array with altered plugins and two fake plugins.\nOn <em>maximum<\/em> level, the returned array contains only two fake plugins. See\n<a href=\"https:\/\/github.com\/brave\/brave-browser\/issues\/9435\">issue1<\/a> and\n<a href=\"https:\/\/github.com\/brave\/brave-browser\/issues\/10597\">issue2<\/a> for more details.<\/p>\n<h3 id=\"user-agent\"><a class=\"toclink\" href=\"#user-agent\">User agent<\/a><\/h3>\n<p>Brave employs the default Chrome UA and the newest OS version as the user agent\nstring. Also, a random number of blank spaces (up to 5) appended to the end of\nthe user agent string. For more details, see the <a href=\"https:\/\/github.com\/brave\/brave-browser\/issues\/9190\">GitHub\nissue<\/a>.<\/p>\n<h3 id=\"enumeratedevices\"><a class=\"toclink\" href=\"#enumeratedevices\">EnumerateDevices<\/a><\/h3>\n<p>This API is used to list I\/O media devices like microphone or speakers. When\nfingerprinting protection is active, Brave returns a shuffled list of devices.\nFor more details, see\n<a href=\"https:\/\/github.com\/brave\/brave-browser\/issues\/11271\">issue1<\/a> and\n<a href=\"https:\/\/github.com\/brave\/brave-browser\/issues\/8666\">issue2<\/a>.<\/p>\n<h3 id=\"hardwareconcurrency\"><a class=\"toclink\" href=\"#hardwareconcurrency\">HardwareConcurrency<\/a><\/h3>\n<p>The number of logical processors returned by this interface is modified as\nfollows -- on <em>balanced<\/em> level, a valid value between 2 and the true value, on\n<em>maximum<\/em> level, a valid value between 2 and 8. See the <a href=\"https:\/\/github.com\/brave\/brave-browser\/issues\/10808\">GitHub\nissue<\/a> for more details.<\/p>\n<h1 id=\"porting-farbling-to-jshelter\"><a class=\"toclink\" href=\"#porting-farbling-to-jshelter\">Porting Farbling to JShelter<\/a><\/h1>\n<p>Our goal was to extend JShelter anti-fingerprinting protections with similar\nmeasures to those available in Brave's Farbling. We decided to implement Brave\nFarbling with minor tweaks. As Brave is an open-source project based on\n<a href=\"https:\/\/www.chromium.org\/Home\">Chromium<\/a>, core changes are available in the\npublic <a href=\"https:\/\/github.com\/brave\/brave-core\">repository<\/a>. Furthermore, as Brave\nis licensed under <a href=\"https:\/\/www.mozilla.org\/en-US\/MPL\/2.0\/\">MPL 2.0<\/a> license, its\ncountermeasures can be ported to JShelter. Similarly to Brave, JShelter utilises\nsession and domain hashes (currently, we use a different domain hash based on\norigin, however, we consider switching to the eTLD+1 approach used by Brave).\nNevertheless, we ported only those changes that an extension can reasonably\napply. So we do not plan to change system fonts as the true set of fonts can\nleak in several ways (e.g., CSS, canvas). We will keep a close eye on\nanti-fingerprining techniquest applied by Brave in the future.<\/p>\n<p>Former JShelter defences were left as an option so user can choose which\nprotection they want. For example, for <strong>Canvas API<\/strong>, JShelter retains the old\ndefence that returns a white image, but it is also possible to use Farbling and\nslightly modify the image.<\/p>\n<p><code>CanvasRenderingContext2D.isPointInPath<\/code> and\n<code>CanvasRenderingContext2D.isPointInStroke<\/code> are modified to return <em>false<\/em> with\n5% probability, returning <em>false<\/em> to every call seems to be easily identifiable\nand it limits the usablity of the calls.<\/p>\n<p><strong>WebGL<\/strong>, <strong>Web audio<\/strong>, <strong>plugins<\/strong>, <strong>hardwareConcurrency<\/strong> and\n<strong>deviceMemory<\/strong> have been changed accordingly to Brave. API\n<strong>enumerateDevices<\/strong> has the same functionality as in Brave. In addition, we add\nfake devices to the list. <strong>User agent<\/strong> wasn't modified because it can cause\ncompatibility issues as we support multiple browsers. Adding empty spaces at the\nend of UAS seems to be quite a weak countermeasure. We will continue to watch\nchanges in the user agent and may implement some defence in future, although it\nlooks like a <a href=\"https:\/\/datatracker.ietf.org\/doc\/html\/rfc8942\">better solution<\/a> is\non the way.<\/p>\n<p>JShelter 0.5 changes the default level -- <strong>level 2<\/strong> to apply the farbling-based\ndefence for all covered APIs, and it will be very similar to the <em>balanced<\/em>\nlevel of <em>Brave<\/em>. <strong>Level 3<\/strong> is redesigned to partly apply new and partly old\ncountermeasures to provide as little information as possible. Please report\nwebsites that does not work correctly with Farbling.<\/p>\n<p>During the examination of the ported code, we <a href=\"https:\/\/github.com\/brave\/brave-browser\/issues\/15882\">identified and\nreported<\/a> an issue in the\noriginal Brave implementation. The issue was acknowledged and fixed by Brave.\nThis is the beauty of the free software: several projects can benefit from the\nsame code-base and mutualy improve the quality.<\/p>\n<h1 id=\"conclusion\"><a class=\"toclink\" href=\"#conclusion\">Conclusion<\/a><\/h1>\n<p>Farbling-based wrappers produce very similar outputs to Brave. So with JShelter,\nFarbling-like capabilities are available in multiple browsers. Nevertheless,\nkeep in mind that the best anti-fingerprinting techniques are still a research\nquestion, fingerprinting techniques are deployed for security reasons (and\nfarbling-like anti-fingerprinting masking may complicate some log in processes),\nso it is not completely clear what defences are the best and the choice of the\ndefences also depends on specific use cases. We will investigate fingerprinting\nscripts further during the future work on this project.<\/p>","category":{"@attributes":{"term":"pt"}}},{"title":"How JShelter prevents other parties from sniffing on your local applications?","link":{"@attributes":{"href":"https:\/\/jshelter.org\/localportscanning\/","rel":"alternate"}},"published":"2021-06-15T09:00:00+02:00","updated":"2026-01-12T05:03:03+01:00","author":{"name":"The JShelter team"},"id":"tag:jshelter.org,2021-06-15:\/localportscanning\/","summary":"<p>We recently found a <a href=\"https:\/\/blog.nem.ec\/2020\/05\/24\/ebay-port-scanning\/\">blog post<\/a> about\nThreatMetrix Inc. (a part of LexisNexis) scanning locally open ports for about 30,000 web\nsites, including eBay. The figure below shows that a browser tries to connect to ports commonly used for remote access to the computer (e.g., RDesktop, VNC, TeamViewer \u2026<\/p>","content":"<p>We recently found a <a href=\"https:\/\/blog.nem.ec\/2020\/05\/24\/ebay-port-scanning\/\">blog post<\/a> about\nThreatMetrix Inc. (a part of LexisNexis) scanning locally open ports for about 30,000 web\nsites, including eBay. The figure below shows that a browser tries to connect to ports commonly used for remote access to the computer (e.g., RDesktop, VNC, TeamViewer) and other applications.<\/p>\n<p><img alt=\"A screenshot of the browser being used as a proxy to scan locally open ports\" src=\"https:\/\/jshelter.org\/localportscanning\/portscan-1_captured_traffic.png\"><\/p>\n<p>The obvious question is, what is the reason for such behaviour? The simple answer is security. See\nadditional links to <a href=\"https:\/\/securityboulevard.com\/2020\/05\/is-ebay-port-scanning-your-pc-probably\/\">Security Boulevard<\/a>, <a href=\"https:\/\/blog.avast.com\/why-is-ebay-port-scanning-my-computer-avast\">Avast<\/a>, and <a href=\"https:\/\/www.theregister.com\/2020\/05\/26\/ebay_port_scans_your_pc\/\">The register<\/a>.<\/p>\n<p>One possibility is that ThreatMetrix creates a <a href=\"https:\/\/arxiv.org\/pdf\/1905.01051.pdf\">fingerprint<\/a>, and locally running\napplications are a part of the fingerprint. Consequently, the authentication algorithm stores\nattributes about your device(s) and compare them during each log in with the previous values. Seeing\nthat you are logging in using a previously seen device, the algorithm can let you in with just a\npassword without additional proves. However, should you use a new device, the algorithm might decide that\nadditional authentication steps are required and send you an SMS.<\/p>\n<p>Another option is that ThreatMetrix knows that many fraudulent activities occur on\ndevices with specific ports open. Recall that the ports being checked concern remote desktop\naccess. Having a remote desktop port open means that the computer may be used by an adversary that does not sit near the computer but is connected remotely. Consequently, the authentication algorithm might decide that additional proves about the user identity should be checked.<\/p>\n<p>We do not know what the real reason behind the scanning is. It might be one of the above, both, or a\nsimilar reason.<\/p>\n<h3 id=\"ethical-and-legal-issues\"><a class=\"toclink\" href=\"#ethical-and-legal-issues\">Ethical and legal issues<\/a><\/h3>\n<p>Although it could be that the underlying intentions are benign and users actually do benefit from\nthe scanning, the scanning raises some ethical issues.<\/p>\n<p>Very often, security and privacy are interconnected. But sometimes, one might increase security by\nrevealing something private. In this case, ThreatMetrix learns information about the running device\nthat is not obvious to the device owner (a user or a company). Typically, the owner of the device\ndoes not even know that such information can leak. If the information\nstays with ThreatMetrix, then the benefits could appear to be greater than the disadvantages.\nHowever, adversaries could stole information from ThreatMetrix (see for example the <a href=\"https:\/\/en.wikipedia.org\/wiki\/2017_Equifax_data_breach\">Ecquifax breach<\/a>) or the company can start to <a href=\"https:\/\/www.vice.com\/en\/article\/qjdkq7\/avast-antivirus-sells-user-browsing-data-investigation\">sell<\/a> the <a href=\"https:\/\/www.pcmag.com\/news\/the-cost-of-avasts-free-antivirus-companies-can-spy-on-your-clicks\">information<\/a> or even <a href=\"https:\/\/brave.com\/rtb-evidence\/\">share with others<\/a>.<\/p>\n<p>So is the scanning and data collecting legal? As some of our developers and users are based in the EU, we will dig into the EU perspective. You might want to\nconsult your local laws if you are outside the EU. Moreover, as we are not lawyers, you might want to\nconsult one even in the EU.<\/p>\n<p><a href=\"https:\/\/eur-lex.europa.eu\/legal-content\/EN\/ALL\/?uri=CELEX:32002L0058\">EU ePrivacy Directive<\/a> applies. However, as <a href=\"https:\/\/ec.europa.eu\/justice\/article-29\/documentation\/opinion-recommendation\/files\/2014\/wp224_en.pdf\">WP29 clarified<\/a> (use case 7.5), user-centric security can be viewed as strictly necessary to provide\nthe service. So it seems likely that port scanning for security reasons would\ntrigger the ePrivacy exception and user consent is not necessary.<\/p>\n<p>As the port scanning is a part of the login mechanism, open ports are personal data\nwithout doubts. So GDPR also applies.\nGDPR also list security as a possible legitimate interest of a data controller (e.g. eBay), see\nrecital 49. Nevertheless, if such a scan is proportionate is an open question; it is possible that the legitimate interests of data controllers (such as eBay) are overriden by the interests or fundamental rights and freedoms of the data subject (you), see Article(6)(1)(f).\nThe Court of Justice of EU (CJEU) decided several issues that concerned legitimate interests and the necessity of processing, e.g. <a href=\"https:\/\/curia.europa.eu\/juris\/liste.jsf?num=C-13\/16\">C-13\/16, point 30 that also points to other related cases<\/a> or <a href=\"https:\/\/curia.europa.eu\/juris\/liste.jsf?num=C-708\/18\">C-708\/18 points 40\u201345<\/a>. It might be possible that it is strictly necessary for eBay to perform local port scanning.<\/p>\n<p>Nevertheless, Article 12-14 of GDPR lists requirements on the information that a data controller should reveal\nto each data subject before the data processing starts or in a reasonable time afterwards. Hence, each controller employing ThreatMetrix should reveal, for example, in the privacy policy, what categories of data it is using and\nfor which purposes. From the <a href=\"https:\/\/blog.avast.com\/why-is-ebay-port-scanning-my-computer-avast\">linked<\/a> <a href=\"https:\/\/www.theregister.com\/2020\/05\/26\/ebay_port_scans_your_pc\/\">articles<\/a>, it seems that ThreatMetrix and eBay are secretive about data being collected.<\/p>\n<p>Another GDPR issue might be data transfers to third countries. Data transfers of open ports may not be\ncompatible with GDPR in the light of the <a href=\"https:\/\/curia.europa.eu\/juris\/liste.jsf?num=C-311\/18\">CJEU C-311\/18<\/a> decision if the information leaves EEA.<\/p>\n<h3 id=\"why-is-not-my-browser-protecting-me-from-remote-servers-accessing-local-information\"><a class=\"toclink\" href=\"#why-is-not-my-browser-protecting-me-from-remote-servers-accessing-local-information\">Why is not my browser protecting me from remote servers accessing local information?<\/a><\/h3>\n<p>OK, so even though the scanning could be legal, one can disagree that others should be allowed to sniff on\nlocal applications. So why does a browser leak the information?<\/p>\n<p>Well, the browser employs so called <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/Security\/Same-origin_policy\">same origin policy<\/a> (SOP) that in abstract theory should prevent websites from the scans in question. As your local computer is of a different origin from the remote website, your computer should be protected by SOP. Nevertheless, SOP has its limitations. First of all, some <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/HTTP\/CORS\">cross-origin resource sharing<\/a> is beneficial, so the browser cannot block outgoing requests to other origins. Such behaviour opens possibilities for <a href=\"https:\/\/www.forcepoint.com\/sites\/default\/files\/resources\/files\/report-attacking-internal-network-en_0.pdf\">side-channels<\/a> to be identified. So even though the web page cannot communicate with applications on your computer (or in your network) without the cooperation of these applications, it can observe the behaviour and make some conclusions based on the observed errors, timing, etc.<\/p>\n<p>An (ad) blocker can prevent you from the activity. As the blockers typically leverage blocklists,\nsuch a port scanning script URL needs to match a rule in a block list. Once information about a\nmisbehaving script becomes public, a rule can be added to a block list. However, this could take some time. Additional techniques like <a href=\"https:\/\/blog.lukaszolejnik.com\/large-scale-analysis-of-dns-based-tracking-evasion-broad-data-leaks-included\/\">DNS de-cloaking<\/a>\nneed to be applied in this case.<\/p>\n<h3 id=\"network-boundary-shield-to-the-rescue\"><a class=\"toclink\" href=\"#network-boundary-shield-to-the-rescue\">Network Boundary Shield to the rescue<\/a><\/h3>\n<p>JShelter contains a Network Boundary Shield (NBS) that blocks outgoing browser requests based on the observed behaviour, i.e. a\npage hosted on public internet tries to access local URLs.\nNBS just works and cannot be fooled by changes in the URL path, DNS cloaking or other techniques.<\/p>\n<p><img alt=\"JShelter blocks the scan\" src=\"https:\/\/jshelter.org\/localportscanning\/portscan-2_request_blocked.png\"><\/p>\n<p>Firefox contains DNS API, so NBS works flawlessly. <del>In Chromium-based browsers, the exact blocking\nbehaviour depends on how quickly a scanning script can fire the requests and the precise\ndestination (IP address or a domain name). Depending on the interaction with DNS, NBS can be side-stepped on Chrome. In this case, ThreatMetrix does not try any evasion technique, so\nNBS just works in the case of eBay and ThreatMetrix.<\/del><\/p>\n<p>Update: In 2025, JShelter was forced to migrate to <a href=\"\/mv3\/\">Manifest v3<\/a> and NBS no longer works in\nChromium-based browsers as they lack the blocking web request API that is crucial to NBS.<\/p>","category":{"@attributes":{"term":"posts"}}},{"title":"How JShelter prevents other parties from sniffing on your local applications?","link":{"@attributes":{"href":"https:\/\/jshelter.org\/pt\/localportscanning\/","rel":"alternate"}},"published":"2021-06-15T09:00:00+02:00","updated":"2025-07-10T07:01:03+02:00","author":{"name":"The JShelter team"},"id":"tag:jshelter.org,2021-06-15:\/pt\/localportscanning\/","summary":"<p>We recently found a <a href=\"https:\/\/blog.nem.ec\/2020\/05\/24\/ebay-port-scanning\/\">blog\npost<\/a> about ThreatMetrix\nInc. (a part of LexisNexis) scanning locally open ports for about 30,000 web\nsites, including eBay. The figure below shows that a browser tries to connect to\nports commonly used for remote access to the computer (e.g., RDesktop, VNC,\nTeamViewer \u2026<\/p>","content":"<p>We recently found a <a href=\"https:\/\/blog.nem.ec\/2020\/05\/24\/ebay-port-scanning\/\">blog\npost<\/a> about ThreatMetrix\nInc. (a part of LexisNexis) scanning locally open ports for about 30,000 web\nsites, including eBay. The figure below shows that a browser tries to connect to\nports commonly used for remote access to the computer (e.g., RDesktop, VNC,\nTeamViewer) and other applications.<\/p>\n<p><img alt=\"A screenshot of the browser being used as a proxy to scan locally open\nports\" src=\"https:\/\/jshelter.org\/localportscanning\/portscan-1_captured_traffic.png\"><\/p>\n<p>The obvious question is, what is the reason for such behaviour? The simple answer\nis security. See additional links to <a href=\"https:\/\/securityboulevard.com\/2020\/05\/is-ebay-port-scanning-your-pc-probably\/\">Security\nBoulevard<\/a>,\n<a href=\"https:\/\/blog.avast.com\/why-is-ebay-port-scanning-my-computer-avast\">Avast<\/a>, and\n<a href=\"https:\/\/www.theregister.com\/2020\/05\/26\/ebay_port_scans_your_pc\/\">The register<\/a>.<\/p>\n<p>One possibility is that ThreatMetrix creates a\n<a href=\"https:\/\/arxiv.org\/pdf\/1905.01051.pdf\">fingerprint<\/a>, and locally running\napplications are a part of the fingerprint. Consequently, the authentication\nalgorithm stores attributes about your device(s) and compare them during each\nlog in with the previous values. Seeing that you are logging in using a\npreviously seen device, the algorithm can let you in with just a password\nwithout additional proves. However, should you use a new device, the algorithm\nmight decide that additional authentication steps are required and send you an\nSMS.<\/p>\n<p>Another option is that ThreatMetrix knows that many fraudulent activities occur\non devices with specific ports open. Recall that the ports being checked concern\nremote desktop access. Having a remote desktop port open means that the computer\nmay be used by an adversary that does not sit near the computer but is connected\nremotely. Consequently, the authentication algorithm might decide that\nadditional proves about the user identity should be checked.<\/p>\n<p>We do not know what the real reason behind the scanning is. It might be one of\nthe above, both, or a similar reason.<\/p>\n<h3 id=\"ethical-and-legal-issues\"><a class=\"toclink\" href=\"#ethical-and-legal-issues\">Ethical and legal issues<\/a><\/h3>\n<p>Although it could be that the underlying intentions are benign and users actually\ndo benefit from the scanning, the scanning raises some ethical issues.<\/p>\n<p>Very often, security and privacy are interconnected. But sometimes, one might\nincrease security by revealing something private. In this case, ThreatMetrix\nlearns information about the running device that is not obvious to the device\nowner (a user or a company). Typically, the owner of the device does not even\nknow that such information can leak. If the information stays with ThreatMetrix,\nthen the benefits could appear to be greater than the disadvantages. However,\nadversaries could stole information from ThreatMetrix (see for example the\n<a href=\"https:\/\/en.wikipedia.org\/wiki\/2017_Equifax_data_breach\">Ecquifax breach<\/a>) or\nthe company can start to\n<a href=\"https:\/\/www.vice.com\/en\/article\/qjdkq7\/avast-antivirus-sells-user-browsing-data-investigation\">sell<\/a>\nthe\n<a href=\"https:\/\/www.pcmag.com\/news\/the-cost-of-avasts-free-antivirus-companies-can-spy-on-your-clicks\">information<\/a>\nor even <a href=\"https:\/\/brave.com\/rtb-evidence\/\">share with others<\/a>.<\/p>\n<p>So is the scanning and data collecting legal? As some of our developers and users\nare based in the EU, we will dig into the EU perspective. You might want to\nconsult your local laws if you are outside the EU. Moreover, as we are not\nlawyers, you might want to consult one even in the EU.<\/p>\n<p><a href=\"https:\/\/eur-lex.europa.eu\/legal-content\/EN\/ALL\/?\nuri=CELEX:32002L0058\">EU ePrivacy Directive<\/a> applies. However, as <a href=\"https:\/\/ec.europa.eu\/justice\/article-29\/documentation\/opinion-recommendation\/files\/2014\/wp224_en.pdf\">WP29\nclarified<\/a>\n(use case 7.5), user-centric security can be viewed as strictly necessary to\nprovide the service. So it seems likely that port scanning for security reasons\nwould trigger the ePrivacy exception and user consent is not necessary.<\/p>\n<p>As the port scanning is a part of the login mechanism, open ports are personal\ndata without doubts. So GDPR also applies. GDPR also list security as a possible\nlegitimate interest of a data controller (e.g. eBay), see recital 49.\nNevertheless, if such a scan is proportionate is an open question; it is\npossible that the legitimate interests of data controllers (such as eBay) are\noverriden by the interests or fundamental rights and freedoms of the data\nsubject (you), see Article(6)(1)(f). The Court of Justice of EU (CJEU) decided\nseveral issues that concerned legitimate interests and the necessity of\nprocessing, e.g. <a href=\"https:\/\/curia.europa.eu\/juris\/liste.jsf?num=C-13\/16\">C-13\/16, point 30 that also points to other related\ncases<\/a> or <a href=\"https:\/\/curia.europa.eu\/juris\/liste.jsf?num=C-708\/18\">C-708\/18 points\n40\u201345<\/a>. It might be\npossible that it is strictly necessary for eBay to perform local port scanning.<\/p>\n<p>Nevertheless, Article 12-14 of GDPR lists requirements on the information that a\ndata controller should reveal to each data subject before the data processing\nstarts or in a reasonable time afterwards. Hence, each controller employing\nThreatMetrix should reveal, for example, in the privacy policy, what categories\nof data it is using and for which purposes. From the\n<a href=\"https:\/\/blog.avast.com\/why-is-ebay-port-scanning-my-computer-avast\">linked<\/a>\n<a href=\"https:\/\/www.theregister.com\/2020\/05\/26\/ebay_port_scans_your_pc\/\">articles<\/a>, it\nseems that ThreatMetrix and eBay are secretive about data being collected.<\/p>\n<p>Another GDPR issue might be data transfers to third countries. Data transfers of\nopen ports may not be compatible with GDPR in the light of the <a href=\"https:\/\/curia.europa.eu\/juris\/liste.jsf?num=C-311\/18\">CJEU\nC-311\/18<\/a> decision if the\ninformation leaves EEA.<\/p>\n<h3 id=\"why-is-not-my-browser-protecting-me-from-remote-servers-accessing-local\"><a class=\"toclink\" href=\"#why-is-not-my-browser-protecting-me-from-remote-servers-accessing-local\">Why is not my browser protecting me from remote servers accessing local<\/a><\/h3>\n<p>information?<\/p>\n<p>OK, so even though the scanning could be legal, one can disagree that others\nshould be allowed to sniff on local applications. So why does a browser leak the\ninformation?<\/p>\n<p>Well, the browser employs so called <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/Security\/Same-origin_policy\">same origin\npolicy<\/a>\n(SOP) that in abstract theory should prevent websites from the scans in\nquestion. As your local computer is of a different origin from the remote\nwebsite, your computer should be protected by SOP. Nevertheless, SOP has its\nlimitations. First of all, some <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/HTTP\/CORS\">cross-origin resource\nsharing<\/a> is beneficial,\nso the browser cannot block outgoing requests to other origins. Such behaviour\nopens possibilities for\n<a href=\"https:\/\/www.forcepoint.com\/sites\/default\/files\/resources\/files\/report-attacking-internal-network-en_0.pdf\">side-channels<\/a>\nto be identified. So even though the web page cannot communicate with\napplications on your computer (or in your network) without the cooperation of\nthese applications, it can observe the behaviour and make some conclusions based\non the observed errors, timing, etc.<\/p>\n<p>An (ad) blocker can prevent you from the activity. As the blockers typically\nleverage blocklists, such a port scanning script URL needs to match a rule in a\nblock list. Once information about a misbehaving script becomes public, a rule\ncan be added to a block list. However, this could take some time. Additional\ntechniques like <a href=\"https:\/\/blog.lukaszolejnik.com\/large-scale-analysis-of-dns-based-tracking-evasion-broad-data-leaks-included\/\">DNS\nde-cloaking<\/a>\nneed to be applied in this case.<\/p>\n<h3 id=\"network-boundary-shield-to-the-rescue\"><a class=\"toclink\" href=\"#network-boundary-shield-to-the-rescue\">Network Boundary Shield to the rescue<\/a><\/h3>\n<p>JShelter contains a Network Boundary Shield (NBS) that blocks outgoing browser\nrequests based on the observed behaviour, i.e. a page hosted on public internet\ntries to access local URLs. NBS just works and cannot be fooled by changes in\nthe URL path, DNS cloaking or other techniques.<\/p>\n<p><img alt=\"JShelter bloqueia a verifica\u00e7\u00e3o\" src=\"https:\/\/jshelter.org\/localportscanning\/portscan-2_request_blocked.png\"><\/p>\n<p>Firefox contains DNS API, so NBS works flawlessly. In Chromium-based browsers,\nthe exact blocking behaviour depends on how quickly a scanning script can fire\nthe requests and the precise destination (IP address or a domain name).\nDepending on the interaction with DNS, NBS can be side-stepped on Chrome. In\nthis case, ThreatMetrix does not try any evasion technique, so NBS just works in\nthe case of eBay and ThreatMetrix.<\/p>","category":{"@attributes":{"term":"pt"}}},{"title":"Measurement of JavaScript API usage on the web","link":{"@attributes":{"href":"https:\/\/jshelter.org\/crawling\/","rel":"alternate"}},"published":"2021-03-30T14:00:00+02:00","updated":"2025-04-16T09:06:03+02:00","author":{"name":"The JShelter team"},"id":"tag:jshelter.org,2021-03-30:\/crawling\/","summary":"<p>The world wide web is a complex environment. Web pages can access many APIs ranging from text formatting to access to nearby Bluetooth devices. While many APIs are used for legitimate purposes, some are misused to track and identify their users without their knowledge. In this paper, we propose a \u2026<\/p>","content":"<p>The world wide web is a complex environment. Web pages can access many APIs ranging from text formatting to access to nearby Bluetooth devices. While many APIs are used for legitimate purposes, some are misused to track and identify their users without their knowledge. In this paper, we propose a methodology to measure the usage of JavaScript APIs on the public web. The methodology consists of an automated visit of several thousand websites and intercepting JavaScript calls performed by the pages. We also provide a design and architecture of a measurement platform that can be used for an automated visit of a list of websites. The proposed platform is based on OpenWPM. The browser is instrumented by OpenWPM and a customized Web API Manager extension is responsible for capturing JavaScript API calls.<\/p>\n<h3 id=\"introduction\"><a class=\"toclink\" href=\"#introduction\">Introduction<\/a><\/h3>\n<p>Web browsers offer a wide range of possibilities. on the surface\nthey <em>just<\/em> display web pages, but under the hood, web\nbrowsers provide a bridge between a\u00a0viewed page and the host\noperating system. A\u00a0web browser allows a web page to access information\nlike values from sensors, information about battery status, installed\nfonts, and much more. The advertisement industry often takes advantage of\nthe wide range of information provided by web browsers to create a web browser\n<a href=\"https:\/\/amiunique.org\/links\">fingerprint<\/a>. Most commonly, the fingeprinters misuse Web APIs (also\ncalled JavaScript APIs).<\/p>\n<p>This blog post is mainly concerned\nwith user tracking and fingerprinting.\nFor example Battery Status API implementation on Mozilla Firefox\nrevealed very precise value allowing the trackers to identify the user\nfor a\u00a0<a href=\"https:\/\/petsymposium.org\/2017\/papers\/hotpets\/batterystatus-not-included.pdf\">period of time<\/a>.\nAs the Battery Status API was used heavily for fingerprinting, it\nhas been removed from Mozilla Firefox in 2017. Other examples of\nJavaScript APIs that are used often for fingerprinting are <a href=\"http:\/\/cseweb.ucsd.edu\/~hovav\/papers\/ms12.html\">Canvas API<\/a>, <a href=\"https:\/\/senglehardt.com\/papers\/princeton_phd_dissertation_englehardt.pdf\">Audio API<\/a>,\n<a href=\"https:\/\/arxiv.org\/abs\/2008.04480\">Permissions API<\/a> or <a href=\"https:\/\/dl.acm.org\/doi\/10.1145\/3243734.3243860\">APIs for device sensors<\/a>.<\/p>\n<p>In our work, we aim to measure the JavaScript APIs usage by popular\nwebsites. In this article, we present core technologies to accomplish\nthese measurements. The stack of technologies is based on <a href=\"https:\/\/github.com\/mozilla\/OpenWPM\">OpenWPM<\/a>\nenriched by a browser extension, that allows us to intercept JavaScript\ncalls of different APIs. This browser extension is based on Proxy\nobjects.<\/p>\n<p>Our work is based on work of <a href=\"https:\/\/www3.cs.uic.edu\/pub\/Bits\/PeterSnyder\/Browser_Feature_Usage_on_the_Modern_Web.pdf\">Peter Snyders et al.<\/a>\ncarried in 2016. Since then many new\nAPIs were specified and implemented in web browsers, see the figure below\n(based on data from <a href=\"https:\/\/caniuse.com\/\">Can I\u00a0Use? website<\/a>).<\/p>\n<p><img alt=\"Progress of Web APIs amount implemented in distinct browsers in time.\" src=\"https:\/\/jshelter.org\/crawling\/crawling-apis.png\"><\/p>\n<h3 id=\"methodology-proposal\"><a class=\"toclink\" href=\"#methodology-proposal\">Methodology proposal<\/a><\/h3>\n<p>This section describes the methodology that we plan to use.\nThis methodology is based on\nAs it is based on the work of Peter Snyders, it is already validated.\nMoreover, using a methodology that is very close to the\noriginal one should show us a difference in the usage of the JavaScript\nAPIs in 2016 and 2021.<\/p>\n<p>The main idea of the measurement is to visit several thousands of the\nmost <a href=\"https:\/\/tranco-list.eu\/#aboutus\">popular pages<\/a> on the internet and intercept as many JavaScript calls\nas possible.<\/p>\n<p>Visiting websites will be performed through Mozilla Firefox with\nan extension that intercepts and logs the JavaScript calls.\nWe will visit not\nonly the landing page but also the subset of subpages of each website.\nFrom the landing page, we will extract three links that point to\na\u00a0subpage of a given page. From each of these three subpages, we will\nget another three subpage links resulting in up to 13 pages of a given\nwebsite being visited. This amount of pages should be high enough to\ncatch the most of JavaScript calls.\nWe will wait and intercept JavaScript\ncalls for 30 seconds on each page to wait of API calls performed during the page load.<\/p>\n<p>Results of our measurements should also provide information about the\nJavaScript APIs, that were probably used in a\u00a0manner, that is not\nnecessary for a\u00a0page to be working and is very likely used in a way,\nthat the user would not find useful. To achieve this,<\/p>\n<p>We will run our\nmeasurements on every page in two different modes. Firstly, we will\nvisit the page using the a browser withou adblocker. Later, we will also employ an adblocker.\nHence, the study will show the difference API usage of regular pages and trackers.<\/p>\n<h4 id=\"the-original-study\"><a class=\"toclink\" href=\"#the-original-study\">The original study<\/a><\/h4>\n<p>The Snyders' study suggests that some of the JavaScript APIs are\nextremely popular and they are used on more than 90% of measured pages\n(e.g. a\u00a0well known <code>Document.createElement<\/code> method from DOM API). On the\nother hand, there are many APIs that are used by a minority of measured\npages. That being said, almost 50% of JavaScript APIs implemented in the\nbrowser at the time were not used by any of the measured pages.<\/p>\n<p>The study also suggests that there is no direct connection between the\nimplementation date of a given JavaScript API in the browser (or by its\nspecification date by some of the specifications vendors) and its\npopularity in using by websites. Concretely, there are some old\nJavaScript APIs, such as <code>XMLHttpRequest<\/code> that are still very popular.\nHowever, there are also quite new\nJavaScript APIs, that are used very frequently (i.e., <code>Selectors API\nLevel 1<\/code>).<\/p>\n<p>The conducted study also measured the pages in two ways - with the ad\nblocker and without any ad blocking extension. Results of measurements\nshowed that the blocking of different JavaScript APIs is not uniform and\nsome APIs are blocked more often than others. Specifically, 10% of\nJavaScript APIs were blocked in 90% of cases resulting in a fact that\n83% of APIs were used on less than 1% of websites when the page was\nvisited with active blocking extension.<\/p>\n<h4 id=\"web-api-manager\"><a class=\"toclink\" href=\"#web-api-manager\">Web API Manager<\/a><\/h4>\n<p>Web API Manager is a browser extension, that aims to block explicitly\ndefined JavaScript APIs. It has been developed by Snyders in 2016 and\nused in several studies conducted by <a href=\"https:\/\/www.peteresnyder.com\/\">Snyders et al.<\/a>.<\/p>\n<p>The original purpose of the Web API Manager is to block explicitly\ndefined JavaScript APIs. However, in our measurements, we just need to\nintercept the API calls, log these calls and delegate the calls to\noriginal receivers.<\/p>\n<p>The main principle of Web API Manager is based on Proxy objects. This\nmetaprogramming technique allows intercepting calls performed on\nobjects. While the main goal of the Web API Manager extension is to\nblock the calls performed on objects that belong to particular\nJavaScript APIs, our goal is only to intercept these operations and\ndelegate them to their original receivers. We will use the Log Aggregator interface to log the API\ncalls.<\/p>\n<p>To provide a Web API Manager the list of JavaScript APIs members we need\na list of supported APIs. The APIs implemented in Mozilla Firefox are available as <a href=\"https:\/\/searchfox.org\/mozilla-central\/source\/dom\/webidl\">IDL files<\/a>.<\/p>\n<h4 id=\"measurement-tools\"><a class=\"toclink\" href=\"#measurement-tools\">Measurement tools<\/a><\/h4>\n<p>The figure below shows a simplified\nillustration of the measurement platform. There is OpenWPM in the middle of the\narchitecture. OpenWPM orchestrates\nSelenium and Mozilla Firefox with the proxy-based intercepting Web API Manager.<\/p>\n<p><img alt=\"image\" src=\"https:\/\/jshelter.org\/crawling\/crawling-architecture.png\"><\/p>\n<h3 id=\"the-impact-on-jshelter\"><a class=\"toclink\" href=\"#the-impact-on-jshelter\">The impact on JShelter<\/a><\/h3>\n<p>Once we have data from our crawling study, we will compare the data with <a href=\"https:\/\/github.com\/uiowa-irl\/FP-Inspector\/blob\/master\/Data\/potential_fingerprinting_APIs.md\">another recent study<\/a>. As already mentioned, we want to develop a fingerprinting detection based on  counting the number of different\nAPIs employed by a page, especially APIs that are not frequently used for benign purposes. When\na fingerprinting attempt is identified, we want to (1) inform the user, (2) prevent uploading of the\nfingerprint to the server, (3) prevent storing the fingerprint for later usage.<\/p>","category":{"@attributes":{"term":"posts"}}},{"title":"Measurement of JavaScript API usage on the web","link":{"@attributes":{"href":"https:\/\/jshelter.org\/pt\/crawling\/","rel":"alternate"}},"published":"2021-03-30T14:00:00+02:00","updated":"2025-07-10T07:01:03+02:00","author":{"name":"The JShelter team"},"id":"tag:jshelter.org,2021-03-30:\/pt\/crawling\/","summary":"<p>The world wide web is a complex environment. Web pages can access many APIs\nranging from text formatting to access to nearby Bluetooth devices. While many\nAPIs are used for legitimate purposes, some are misused to track and identify\ntheir users without their knowledge. In this paper, we propose a \u2026<\/p>","content":"<p>The world wide web is a complex environment. Web pages can access many APIs\nranging from text formatting to access to nearby Bluetooth devices. While many\nAPIs are used for legitimate purposes, some are misused to track and identify\ntheir users without their knowledge. In this paper, we propose a methodology to\nmeasure the usage of JavaScript APIs on the public web. The methodology consists\nof an automated visit of several thousand websites and intercepting JavaScript\ncalls performed by the pages. We also provide a design and architecture of a\nmeasurement platform that can be used for an automated visit of a list of\nwebsites. The proposed platform is based on OpenWPM. The browser is instrumented\nby OpenWPM and a customized Web API Manager extension is responsible for\ncapturing JavaScript API calls.<\/p>\n<h3 id=\"introduction\"><a class=\"toclink\" href=\"#introduction\">Introduction<\/a><\/h3>\n<p>Web browsers offer a wide range of possibilities. on the surface they <em>just<\/em>\ndisplay web pages, but under the hood, web browsers provide a bridge between\na\u00a0viewed page and the host operating system. A\u00a0web browser allows a web page to\naccess information like values from sensors, information about battery status,\ninstalled fonts, and much more. The advertisement industry often takes advantage\nof the wide range of information provided by web browsers to create a web\nbrowser <a href=\"https:\/\/amiunique.org\/links\">fingerprint<\/a>. Most commonly, the\nfingeprinters misuse Web APIs (also called JavaScript APIs).<\/p>\n<p>This blog post is mainly concerned with user tracking and fingerprinting. For\nexample Battery Status API implementation on Mozilla Firefox revealed very\nprecise value allowing the trackers to identify the user for a\u00a0<a href=\"https:\/\/petsymposium.org\/2017\/papers\/hotpets\/batterystatus-not-included.pdf\">period of\ntime<\/a>.\nAs the Battery Status API was used heavily for fingerprinting, it has been\nremoved from Mozilla Firefox in 2017. Other examples of JavaScript APIs that are\nused often for fingerprinting are <a href=\"http:\/\/cseweb.ucsd.edu\/~hovav\/papers\/ms12.html\">Canvas\nAPI<\/a>, <a href=\"https:\/\/senglehardt.com\/papers\/princeton_phd_dissertation_englehardt.pdf\">Audio\nAPI<\/a>,\n<a href=\"https:\/\/arxiv.org\/abs\/2008.04480\">Permissions API<\/a> or <a href=\"https:\/\/dl.acm.org\/doi\/10.1145\/3243734.3243860\">APIs for device\nsensors<\/a>.<\/p>\n<p>In our work, we aim to measure the JavaScript APIs usage by popular websites. In\nthis article, we present core technologies to accomplish these measurements. The\nstack of technologies is based on <a href=\"https:\/\/github.com\/mozilla\/OpenWPM\">OpenWPM<\/a>\nenriched by a browser extension, that allows us to intercept JavaScript calls of\ndifferent APIs. This browser extension is based on Proxy objects.<\/p>\n<p>Our work is based on work of <a href=\"https:\/\/www3.cs.uic.edu\/pub\/Bits\/PeterSnyder\/Browser_Feature_Usage_on_the_Modern_Web.pdf\">Peter Snyders et\nal.<\/a>\ncarried in 2016. Since then many new APIs were specified and implemented in web\nbrowsers, see the figure below (based on data from <a href=\"https:\/\/caniuse.com\/\">Can I\u00a0Use?\nwebsite<\/a>).<\/p>\n<p><img alt=\"Progress of Web APIs amount implemented in distinct browsers in\ntime.\" src=\"https:\/\/jshelter.org\/crawling\/crawling-apis.png\"><\/p>\n<h3 id=\"methodology-proposal\"><a class=\"toclink\" href=\"#methodology-proposal\">Methodology proposal<\/a><\/h3>\n<p>This section describes the methodology that we plan to use. This methodology is\nbased on As it is based on the work of Peter Snyders, it is already validated.\nMoreover, using a methodology that is very close to the original one should show\nus a difference in the usage of the JavaScript APIs in 2016 and 2021.<\/p>\n<p>The main idea of the measurement is to visit several thousands of the most\n<a href=\"https:\/\/tranco-list.eu\/#aboutus\">popular pages<\/a> on the internet and intercept\nas many JavaScript calls as possible.<\/p>\n<p>Visiting websites will be performed through Mozilla Firefox with an extension\nthat intercepts and logs the JavaScript calls. We will visit not only the\nlanding page but also the subset of subpages of each website. From the landing\npage, we will extract three links that point to a\u00a0subpage of a given page. From\neach of these three subpages, we will get another three subpage links resulting\nin up to 13 pages of a given website being visited. This amount of pages should\nbe high enough to catch the most of JavaScript calls. We will wait and intercept\nJavaScript calls for 30 seconds on each page to wait of API calls performed\nduring the page load.<\/p>\n<p>Results of our measurements should also provide information about the JavaScript\nAPIs, that were probably used in a\u00a0manner, that is not necessary for a\u00a0page to\nbe working and is very likely used in a way, that the user would not find\nuseful. To achieve this,<\/p>\n<p>We will run our measurements on every page in two different modes. Firstly, we\nwill visit the page using the a browser withou adblocker. Later, we will also\nemploy an adblocker. Hence, the study will show the difference API usage of\nregular pages and trackers.<\/p>\n<h4 id=\"the-original-study\"><a class=\"toclink\" href=\"#the-original-study\">The original study<\/a><\/h4>\n<p>The Snyders' study suggests that some of the JavaScript APIs are extremely\npopular and they are used on more than 90% of measured pages (e.g. a\u00a0well known\n<code>Document.createElement<\/code> method from DOM API). On the other hand, there are many\nAPIs that are used by a minority of measured pages. That being said, almost 50%\nof JavaScript APIs implemented in the browser at the time were not used by any\nof the measured pages.<\/p>\n<p>The study also suggests that there is no direct connection between the\nimplementation date of a given JavaScript API in the browser (or by its\nspecification date by some of the specifications vendors) and its popularity in\nusing by websites. Concretely, there are some old JavaScript APIs, such as\n<code>XMLHttpRequest<\/code> that are still very popular. However, there are also quite new\nJavaScript APIs, that are used very frequently (i.e., <code>Selectors API Level 1<\/code>).<\/p>\n<p>The conducted study also measured the pages in two ways - with the ad blocker and\nwithout any ad blocking extension. Results of measurements showed that the\nblocking of different JavaScript APIs is not uniform and some APIs are blocked\nmore often than others. Specifically, 10% of JavaScript APIs were blocked in 90%\nof cases resulting in a fact that 83% of APIs were used on less than 1% of\nwebsites when the page was visited with active blocking extension.<\/p>\n<h4 id=\"web-api-manager\"><a class=\"toclink\" href=\"#web-api-manager\">Web API Manager<\/a><\/h4>\n<p>Web API Manager is a browser extension, that aims to block explicitly defined\nJavaScript APIs. It has been developed by Snyders in 2016 and used in several\nstudies conducted by <a href=\"https:\/\/www.peteresnyder.com\/https:\/\/www.peteresnyder.com\/\">Snyders et\nal.<\/a>.<\/p>\n<p>The original purpose of the Web API Manager is to block explicitly defined\nJavaScript APIs. However, in our measurements, we just need to intercept the API\ncalls, log these calls and delegate the calls to original receivers.<\/p>\n<p>The main principle of Web API Manager is based on Proxy objects. This\nmetaprogramming technique allows intercepting calls performed on objects. While\nthe main goal of the Web API Manager extension is to block the calls performed\non objects that belong to particular JavaScript APIs, our goal is only to\nintercept these operations and delegate them to their original receivers. We\nwill use the Log Aggregator interface to log the API calls.<\/p>\n<p>To provide a Web API Manager the list of JavaScript APIs members we need a list\nof supported APIs. The APIs implemented in Mozilla Firefox are available as <a href=\"https:\/\/searchfox.org\/mozilla-central\/source\/dom\/webidl\">IDL\nfiles<\/a>.<\/p>\n<h4 id=\"measurement-tools\"><a class=\"toclink\" href=\"#measurement-tools\">Measurement tools<\/a><\/h4>\n<p>The figure below shows a simplified illustration of the measurement platform.\nThere is OpenWPM in the middle of the architecture. OpenWPM orchestrates\nSelenium and Mozilla Firefox with the proxy-based intercepting Web API Manager.<\/p>\n<p><img alt=\"image\" src=\"https:\/\/jshelter.org\/crawling\/crawling-architecture.png\"><\/p>\n<h3 id=\"the-impact-on-jshelter\"><a class=\"toclink\" href=\"#the-impact-on-jshelter\">The impact on JShelter<\/a><\/h3>\n<p>Once we have data from our crawling study, we will compare the data with <a href=\"https:\/\/github.com\/uiowa-irl\/FP-Inspector\/blob\/master\/Data\/potential_fingerprinting_APIs.md\">another\nrecent\nstudy<\/a>.\nAs already mentioned, we want to develop a fingerprinting detection based on\ncounting the number of different APIs employed by a page, especially APIs that\nare not frequently used for benign purposes. When a fingerprinting attempt is\nidentified, we want to (1) inform the user, (2) prevent uploading of the\nfingerprint to the server, (3) prevent storing the fingerprint for later usage.<\/p>","category":{"@attributes":{"term":"pt"}}},{"title":"We received support from NGI0 PET Fund","link":{"@attributes":{"href":"https:\/\/jshelter.org\/support\/","rel":"alternate"}},"published":"2021-03-30T09:00:00+02:00","updated":"2024-03-26T12:54:03+01:00","author":{"name":"The JShelter team"},"id":"tag:jshelter.org,2021-03-30:\/support\/","summary":"<p>We are very happy to announce that the <a href=\"https:\/\/nlnet.nl\/project\/JSRestrictor\/\">JavaScript\nRestrictor received support from NGI0 PET Fund<\/a>, a fund established by NLnet with financial\nsupport from the European Commission's Next Generation Internet programme, under the aegis of DG\nCommunications Networks, Content and Technology under grant agreement No 825310.<\/p>\n<p>We are very \u2026<\/p>","content":"<p>We are very happy to announce that the <a href=\"https:\/\/nlnet.nl\/project\/JSRestrictor\/\">JavaScript\nRestrictor received support from NGI0 PET Fund<\/a>, a fund established by NLnet with financial\nsupport from the European Commission's Next Generation Internet programme, under the aegis of DG\nCommunications Networks, Content and Technology under grant agreement No 825310.<\/p>\n<p>We are very excited to improve the extension further. We will focus on the following main goals:<\/p>\n<h3 id=\"1-investigate-fingerprinting-scripts-and-prepare-wrappers\"><a class=\"toclink\" href=\"#1-investigate-fingerprinting-scripts-and-prepare-wrappers\">1. Investigate fingerprinting scripts and prepare wrappers<\/a><\/h3>\n<p>Review the previously identified APIs suitable for fingerprinting. Select APIs suitable for JShelter and\nadd wrappers for these APIs. This work has already started, see issue #66. Additionally, we want to focus\non identification of methods used for fingeprinting such as those identified by Iqbal et al., see\nhttps:\/\/uiowa-irl.github.io\/FP-Inspector\/<\/p>\n<h3 id=\"2-prevent-unique-identification-of-a-device\"><a class=\"toclink\" href=\"#2-prevent-unique-identification-of-a-device\">2. Prevent unique identification of a device<\/a><\/h3>\n<p>It is hard, if not impossible, to both prevent fingerprinting and still provide customized environment\nfor the user. Hence, we want to identify fingerprinting attempts by counting the number of different\nAPIs employed by a page, especially APIs that are not frequently used for benign purposes. When\na fingerprinting attempt is identified, we want to (1) inform the user, (2) prevent uploading of the\nfingerprint to the server, (3) prevent storing the fingerprint for later usage.<\/p>\n<h3 id=\"3-code-ported-from-chrome-zero\"><a class=\"toclink\" href=\"#3-code-ported-from-chrome-zero\">3. Code ported from Chrome Zero<\/a><\/h3>\n<p>In version 0.3, we integrated features of Chrome Zero 7 as it is no longer maintained. By\nintegrating the functionality to JShelter, we want to keep the counter-meassures available in a\nmaintained extension. However, we do not have sufficient tests for the functionality.<\/p>\n<h3 id=\"4-evaluation-and-porting-of-code-from-brave\"><a class=\"toclink\" href=\"#4-evaluation-and-porting-of-code-from-brave\">4. Evaluation and porting of code from Brave<\/a><\/h3>\n<p>Brave browser currently implements anti-fingerprinting techniques that aim at providing little lies\nabout the browser environment. We want to evaluate the messures and select techniques that are\nsuitable for JShelter.<\/p>\n<h3 id=\"5-fixing-known-bugs\"><a class=\"toclink\" href=\"#5-fixing-known-bugs\">5. Fixing known bugs<\/a><\/h3>\n<p>We want to focus on the proposed changes and found bugs that are reported in the GitHub bug tracker.<\/p>\n<ul>\n<li>We already closed issues #53, #62, and #72 as a part of this project. The fixes are already available as\n    a part of the 0.4 subversions.<\/li>\n<li>We want to also deal with issues #56 and #71 that are crucial for the success of the extension.<\/li>\n<li>We will focus on other identified bugs in the wrappers or developped techniques.<\/li>\n<\/ul>\n<h3 id=\"6-cooperation-with-the-privacy-shield-project\"><a class=\"toclink\" href=\"#6-cooperation-with-the-privacy-shield-project\">6. Cooperation with the Privacy Shield project<\/a><\/h3>\n<p>We are also excited to announce that we found other partners that are willing to work on our code\nbase through the NGI0 PET Fund, <a href=\"https:\/\/nlnet.nl\/project\/JavascriptShield\/\">Privacy Shield\nproject run by Free Software Foundation<\/a>. Expect inclusion of code that will help to defend your\nfreedoms and provide anti-malware protections. This cooperation should also improve the GUI of the\nextension and create explenatory web pages explaining the functionality and its risks. It is\npossible that the project will be rebranded as a result of the cooperation.<\/p>","category":{"@attributes":{"term":"posts"}}},{"title":"We received support from NGI0 PET Fund","link":{"@attributes":{"href":"https:\/\/jshelter.org\/pt\/support\/","rel":"alternate"}},"published":"2021-03-30T09:00:00+02:00","updated":"2025-07-10T07:01:03+02:00","author":{"name":"The JShelter team"},"id":"tag:jshelter.org,2021-03-30:\/pt\/support\/","summary":"<p>We are very happy to announce that the <a\nhref=\"https:\/\/nlnet.nl\/project\/JSRestrictor\/\">JavaScript Restrictor received\nsupport from NGI0 PET Fund<\/a>, a fund established by NLnet with financial\nsupport from the European Commission's Next Generation Internet programme, under\nthe aegis of DG Communications Networks, Content and Technology under grant\nagreement No 825310.<\/p>\n<p>We are very \u2026<\/p>","content":"<p>We are very happy to announce that the <a\nhref=\"https:\/\/nlnet.nl\/project\/JSRestrictor\/\">JavaScript Restrictor received\nsupport from NGI0 PET Fund<\/a>, a fund established by NLnet with financial\nsupport from the European Commission's Next Generation Internet programme, under\nthe aegis of DG Communications Networks, Content and Technology under grant\nagreement No 825310.<\/p>\n<p>We are very excited to improve the extension further. We will focus on the\nfollowing main goals:<\/p>\n<h3 id=\"1-investigate-fingerprinting-scripts-and-prepare-wrappers\"><a class=\"toclink\" href=\"#1-investigate-fingerprinting-scripts-and-prepare-wrappers\">1. Investigate fingerprinting scripts and prepare wrappers<\/a><\/h3>\n<p>Review the previously identified APIs suitable for fingerprinting. Select APIs\nsuitable for JShelter and add wrappers for these APIs. This work has already\nstarted, see issue #66. Additionally, we want to focus on identification of\nmethods used for fingeprinting such as those identified by Iqbal et al., see\nhttps:\/\/uiowa-irl.github.io\/FP-Inspector\/<\/p>\n<h3 id=\"2-prevent-unique-identification-of-a-device\"><a class=\"toclink\" href=\"#2-prevent-unique-identification-of-a-device\">2. Prevent unique identification of a device<\/a><\/h3>\n<p>It is hard, if not impossible, to both prevent fingerprinting and still provide\ncustomized environment for the user. Hence, we want to identify fingerprinting\nattempts by counting the number of different APIs employed by a page, especially\nAPIs that are not frequently used for benign purposes. When a fingerprinting\nattempt is identified, we want to (1) inform the user, (2) prevent uploading of\nthe fingerprint to the server, (3) prevent storing the fingerprint for later\nusage.<\/p>\n<h3 id=\"3-code-ported-from-chrome-zero\"><a class=\"toclink\" href=\"#3-code-ported-from-chrome-zero\">3. Code ported from Chrome Zero<\/a><\/h3>\n<p>In version 0.3, we integrated features of Chrome Zero 7 as it is no longer\nmaintained. By integrating the functionality to JShelter, we want to keep the\ncounter-meassures available in a maintained extension. However, we do not have\nsufficient tests for the functionality.<\/p>\n<h3 id=\"4-evaluation-and-porting-of-code-from-brave\"><a class=\"toclink\" href=\"#4-evaluation-and-porting-of-code-from-brave\">4. Evaluation and porting of code from Brave<\/a><\/h3>\n<p>Brave browser currently implements anti-fingerprinting techniques that aim at\nproviding little lies about the browser environment. We want to evaluate the\nmessures and select techniques that are suitable for JShelter.<\/p>\n<h3 id=\"5-fixing-known-bugs\"><a class=\"toclink\" href=\"#5-fixing-known-bugs\">5. Fixing known bugs<\/a><\/h3>\n<p>We want to focus on the proposed changes and found bugs that are reported in the\nGitHub bug tracker.<\/p>\n<ul>\n<li>We already closed issues #53, #62, and #72 as a part of this project. The fixes\nare already available as a part of the 0.4 subversions.<\/li>\n<li>We want to also deal with issues #56 and #71 that are crucial for the success\nof the extension.<\/li>\n<li>We will focus on other identified bugs in the wrappers or developped techniques.<\/li>\n<\/ul>\n<h3 id=\"6-cooperation-with-the-privacy-shield-project\"><a class=\"toclink\" href=\"#6-cooperation-with-the-privacy-shield-project\">6. Cooperation with the Privacy Shield project<\/a><\/h3>\n<p>We are also excited to announce that we found other partners that are willing to\nwork on our code base through the NGI0 PET Fund, <a\nhref=\"https:\/\/nlnet.nl\/project\/JavascriptShield\/\">Privacy Shield project run by\nFree Software Foundation<\/a>. Expect inclusion of code that will help to defend\nyour freedoms and provide anti-malware protections. This cooperation should also\nimprove the GUI of the extension and create explenatory web pages explaining the\nfunctionality and its risks. It is possible that the project will be rebranded\nas a result of the cooperation.<\/p>","category":{"@attributes":{"term":"pt"}}}]}