diff --git a/README.md b/README.md index e69de29..1ea5935 100644 --- a/README.md +++ b/README.md @@ -0,0 +1,15 @@ +This add-on allows users to prevent websites from using the Javascript <canvas> API to fingerprint them. Users can choose to block the <canvas> API entirely on some or all websites (which may break some websites) or just block or fake its fingerprinting-friendly readout API. More information on <canvas> fingerprinting can be found at http://www.browserleaks.com/canvas. + +The different block modes are: + + +At present, only my domain (kkapsner.de) is whitelisted by default. diff --git a/canvasblocker.xpi b/canvasblocker.xpi index 87d6c1d..3ede9fb 100644 Binary files a/canvasblocker.xpi and b/canvasblocker.xpi differ diff --git a/data/options.css b/data/options.css index b219665..389cb42 100644 --- a/data/options.css +++ b/data/options.css @@ -13,4 +13,8 @@ setting[pref-name="showCallingFile"]{ setting[pref-name="showCompleteCallingStack"]{ border-top: 0px transparent none; padding-bottom: 0.5em; +} +setting[pref-name="stackList"]{ + border-top: 0px transparent none; + padding-bottom: 0.5em; } \ No newline at end of file diff --git a/install.rdf b/install.rdf index 60661a5..fe025dd 100644 --- a/install.rdf +++ b/install.rdf @@ -1,19 +1,24 @@ - + + CanvasBlocker@kkapsner.de - 0.2.0-Development 2 true false + 0.2.3-Development + CanvasBlocker + Changes the JS-API for modifying <canvas> to prevent Canvas-Fingerprinting. + Korbinian Kapsner + https://github.com/kkapsner/CanvasBlocker/ + data:text/xml,<placeholder/> + 2 {ec8030f7-c20a-464f-9b0e-13a3a9e97384} - 38.0 - 40.* + 40.0 + 46.* @@ -39,18 +44,9 @@ {aa3c5121-dab2-40e2-81ca-7ea25febc110} - 26.0 - 30.0a1 + 40.0 + 46.* - - - - CanvasBlocker - Changes the JS-API for modifying <canvas> to prevent Canvas-Fingerprinting. - Korbinian Kapsner - https://github.com/kkapsner/CanvasBlocker/ - 2 - \ No newline at end of file diff --git a/lib/lists.js b/lib/lists.js index 895022a..49592f0 100644 --- a/lib/lists.js +++ b/lib/lists.js @@ -51,7 +51,7 @@ function getDomainRegExpList(domainList){ var lists = { white: [], - ignore: [], + "ignore": [], black: [] }; @@ -69,6 +69,32 @@ Object.keys(lists).forEach(function(type){ updateList(type); }); +function updateStackList(){ + try { + var data = JSON.parse(prefs.stackList); + if (!Array.isArray(data)){ + data = [data]; + } + var list = data.filter(function(entry){ + return typeof entry === "object" && typeof entry.url === "string"; + }); + } + catch(e){ + var list = []; + } + list.match = function(stack){ + return this.some(function(stackRule){ + return stack.match(stackRule); + }); + }; + lists.stack = list; +} +lists.stack = []; +preferences.on("stackList", function(){ + updateStackList(); +}); +updateStackList(); + exports.get = function getList(type){ "use strict"; @@ -78,7 +104,7 @@ exports.appendTo = function appendToList(type, entry){ "use strict"; prefs[type + "List"] += (prefs[type + "List"]? ",": "") + entry; - prefService.set("extensions.CanvasBlocker@kkapsner.de.whiteList", prefs[type + "List"]); + prefService.set("extensions.CanvasBlocker@kkapsner.de." + type + "List", prefs[type + "List"]); updateList(type); }; exports.update = updateList; \ No newline at end of file diff --git a/lib/main.js b/lib/main.js index 505f1e7..35426b2 100644 --- a/lib/main.js +++ b/lib/main.js @@ -18,8 +18,8 @@ const preferences = require("sdk/simple-prefs"); const prefs = preferences.prefs; - function checkURL(url){ - var match = sharedFunctions.checkURL(url, prefs.blockMode).match(/^(block|allow|fake|ask)(|Readout|Everything|Context)$/); + function check(callingStack, url){ + var match = sharedFunctions.check(callingStack, url, prefs.blockMode).match(/^(block|allow|fake|ask)(|Readout|Everything|Context)$/); if (match){ return { type: (match[2] === "Everything" || match[2] === "")? @@ -53,17 +53,17 @@ enumerable: true, configureable: false, get: function(){ - var status = checkURL(window.location); + var callingStack = sharedFunctions.errorToCallingStack(new Error()); + var status = check(callingStack, window.location); if (status.type.indexOf(changedFunction.type) !== -1){ - var callingStackMsg = sharedFunctions.errorToCallingStackMsg(new Error()); if (status.mode === "ask"){ - status.mode = ask(window, changedFunction.type, this, callingStackMsg); + status.mode = ask(window, changedFunction.type, this, callingStack); } switch (status.mode){ case "allow": return original; case "fake": - notify(window, callingStackMsg); + notify(window, callingStack); return changedFunction.fake || undef; //case "block": default: diff --git a/lib/notifications.js b/lib/notifications.js index 8537b16..6c1e297 100644 --- a/lib/notifications.js +++ b/lib/notifications.js @@ -55,21 +55,40 @@ exports.notify = function(window, callingStackMsg){ label: _("ignorelistDomain"), accessKey: "", callback: function(){ - lists.appendTo("ignore", notification.domain); + var domain = browser.contentWindow.prompt( + _("inputIgnoreDomain"), + notification.domain + ); + if (domain){ + lists.appendTo("ignore", domain); + } } }, { label: _("whitelistURL"), accessKey: "", callback: function(){ - lists.appendTo("white", "^" + notification.url.replace(/([\\\+\*\?\[\^\]\$\(\)\{\}\=\!\|\.])/g, "\\$1") + "$"); + var url = browser.contentWindow.prompt( + _("inputWhitelistDomain"), + "^" + notification.url.replace(/([\\\+\*\?\[\^\]\$\(\)\{\}\=\!\|\.])/g, "\\$1") + "$" + ); + if (url){ + lists.appendTo("white", url); + } } }, { label: _("whitelistDomain"), accessKey: "", callback: function(){ - lists.appendTo("white", notification.domain); + var domain = browser.contentWindow.prompt( + _("inputWhitelistURL"), + notification.domain + ); + if (domain){ + lists.appendTo("white", domain); + } + } }, { diff --git a/lib/sharedFunctions.js b/lib/sharedFunctions.js index cb2cb98..bf2a27d 100644 --- a/lib/sharedFunctions.js +++ b/lib/sharedFunctions.js @@ -23,12 +23,25 @@ var _ = function(name, replace){ return str; }; +function check(stack, url, blockMode){ + if (prefs.enableStackList && checkStack(stack)){ + return "allow"; + } + else { + return checkURL(url, blockMode); + } +} function checkURL(url, blockMode){ "use strict"; - if (url.protocol === "about:" || url.protocol === "chrome:") { - return "allow"; + switch (url.protocol){ + case "about:": + if (url.href === "about:blank"){ + break; + } + case "chrome:": + return "allow"; } var mode = "block"; @@ -67,6 +80,11 @@ function checkURL(url, blockMode){ return mode; } +function checkStack(stack){ + "use strict"; + + return lists.get("stack").match(stack); +} // Stack parsing function parseStackEntry(entry){ @@ -75,17 +93,32 @@ function parseStackEntry(entry){ var m = /@(.*):(\d*):(\d*)$/.exec(entry) || ["", entry, "--", "--"]; return { url: m[1], - line: m[2], - column: m[3], + line: parseInt(m[2], 10), + column: parseInt(m[3], 10), raw: entry }; } +function stackRuleMatch(stackEntry, stackRule){ + if (!stackEntry){ + return false; + } + if (stackEntry.url !== stackRule.url){ + return false; + } + if ((typeof stackRule.line) !== "undefined" && stackEntry.line !== stackRule.line){ + return false; + } + if ((typeof stackRule.column) !== "undefined" && stackEntry.column !== stackRule.column){ + return false; + } + return true; +} + // parse calling stack -function errorToCallingStackMsg(error){ +function errorToCallingStack(error){ "use strict"; - var msg = ""; var callers = error.stack.trim().split("\n"); //console.log(callers); var findme = callers.shift(); // Remove us from the stack @@ -96,20 +129,39 @@ function errorToCallingStackMsg(error){ var doubleStackStart = caller.search(findme) !== -1; inDoubleStack = inDoubleStack || doubleStackStart; return !inDoubleStack; - }); - msg += "\n\n" + _("sourceOutput") + ": "; - if (prefs.showCompleteCallingStack){ - msg += callers.reduce(function(stack, c){ - return stack + "\n\t" + _("stackEntryOutput", parseStackEntry(c)); - }, ""); - } - else{ - msg += _("stackEntryOutput", parseStackEntry(callers[0])); - } - - return msg; + }).map(parseStackEntry); + return { + toString: function(){ + var msg = ""; + msg += "\n\n" + _("sourceOutput") + ": "; + if (prefs.showCompleteCallingStack){ + msg += callers.reduce(function(stack, c){ + return stack + "\n\t" + _("stackEntryOutput", c); + }, ""); + } + else{ + msg += _("stackEntryOutput", callers[0]); + } + + return msg; + }, + match: function(stackRule){ + if (typeof stackRule.stackPosition !== "undefined"){ + var pos = stackRule.stackPosition; + if (pos < 0){ + pos += callers.length; + } + return stackRuleMatch(callers[pos], stackRule); + } + else { + return callers.some(function(stackEntry){ + return stackRuleMatch(stackEntry, stackRule); + }); + } + } + }; } -exports.checkURL = checkURL; +exports.check = check; exports.parseStackEntry = parseStackEntry; -exports.errorToCallingStackMsg = errorToCallingStackMsg; \ No newline at end of file +exports.errorToCallingStack = errorToCallingStack; \ No newline at end of file diff --git a/locale/de-DE.json b/locale/de-DE.json new file mode 100644 index 0000000..bfffa8d --- /dev/null +++ b/locale/de-DE.json @@ -0,0 +1,52 @@ +{ + "allowPDFCanvas_description": "Die native pdf.js verwendet um den Inhalt von PDFs anzuzeigen. Wenn viele Nachfragedialoge erscheinen oder die PDF-Ansicht nicht funktioniert, müssen diese erlaubt werden.", + "allowPDFCanvas_title": " in PDFs erlauben", + "askForInvisiblePermission": "Wollen Sie unsichtbare erlauben?", + "askForInvisibleReadoutPermission": "Wollen Sie das Auslesen von unsichtbaren erlauben?", + "askForPermission": "Wollen Sie erlauben?", + "askForReadoutPermission": "Wollen Sie das Auslesen von erlauben?", + "askForVisiblePermission": "Wollen Sie das rot umrandete erlauben?", + "askForVisibleReadoutPermission": "Wollen Sie das Auslesen des rot umrandeten erlauben?", + "askOnlyOnce_description": "Wenn der Blockiermodus des Canvas Blockers auf \"um Erlaubnis fragen\" oder \"bei Auslese-API um Erlaubnis fragen\" gesetzt ist, erscheint jedes mal ein Abfragedialog, wenn eine Seite versucht, die (Auslese-)API aufzurufen. Diese Einstellung versucht diese Abfrage nur einmal pro Seite anzuzeigen, unabhängig davon wie oft die API aufgerufen wird. Es können trotzdem mehrere Dialoge pro Seite erscheinen.", + "askOnlyOnce_title": "Nur einmal nachfragen", + "blackList_description": "Domänen oder URLs, die die -API niemals verwenden dürfen. Mehrere Einträge müssen durch ein Komma getrennt werden.", + "blackList_title": "Blacklist", + "blockMode_description": "", + "blockMode_options.allow everything": "alles erlauben", + "blockMode_options.allow only white list": "nur Einträge der Whitelist erlauben", + "blockMode_options.ask for permission": "um Erlaubnis fragen", + "blockMode_options.ask for readout API permission": "bei Auslese-API um Erlaubnis fragen", + "blockMode_options.block everything": "alles blockieren", + "blockMode_options.block only black list": "nur Einträge der Blacklist blockieren", + "blockMode_options.block readout API": "Auslese-API blockieren", + "blockMode_options.fake readout API": "Auslese-API vortäuschen", + "blockMode_title": "Blockiermodus", + "disableNotifications": "Benachrichtigungen deaktivieren", + "displayCallingStack": "Aufrufestack anzeigen", + "displayFullURL": "URL anzeigen", + "enableStackList_description": "", + "enableStackList_title": "Dateispezifische Whitelist verwenden", + "fakedReadout": "Auslese vorgetäuscht auf {url}", + "ignoreList_description": "Domänen oder URLs, bei denen keine Benachrichtigung angezeigt werden. Mehrere Einträge müssen durch ein Komma getrennt werden.", + "ignoreList_title": "Ignorierliste", + "ignorelistDomain": "ignoriere Domain", + "ignorelistURL": "ignoriere URL", + "inputIgnoreDomain": "Geben Sie die Domain ein, die ignoriert werden soll:", + "inputWhitelistDomain": "Geben Sie die URL RegExp ein, die erlaubt werden soll:", + "inputWhitelistURL": "Geben Sie die Domain ein, die erlaubt werden soll:", + "settings": "Einstellungen", + "showCallingFile_description": "", + "showCallingFile_title": "Aufrufende Datei anzeigen", + "showCompleteCallingStack_description": "", + "showCompleteCallingStack_title": "Kompletten Aufrufestack anzeigen", + "showNotifications_description": "Benachrichtigungen anzeigen, wenn der Blockiermodus auf \"Auslese-API vortäuschen\" gesetzt ist.", + "showNotifications_title": "Benachrichtigungen anzeigen", + "sourceOutput": "Aufrufende Datei", + "stackEntryOutput": "{url} Zeile {line} Spalte {column}", + "stackList_description": "JS-Dateien, die die -API verwenden dürfen. Die Angabe muss hier im JSON-Format vorliegen. Beispiel: [{\"url\": \"http://domain/datei1.js\"}, {\"url\": \"http://domain/datei2.js\", \"line\": 1, \"column\": 4, \"stackPosition\": -3}]", + "stackList_title": "Dateispezifische Whitelist", + "whiteList_description": "Domänen oder URLs, die die -API verwenden dürfen. Mehrere Einträge müssen durch ein Komma getrennt werden.", + "whiteList_title": "Whitelist", + "whitelistDomain": "erlaube Domain", + "whitelistURL": "erlaube URL" +} \ No newline at end of file diff --git a/locale/de-DE.properties b/locale/de-DE.properties deleted file mode 100644 index 9d3e436..0000000 --- a/locale/de-DE.properties +++ /dev/null @@ -1,56 +0,0 @@ -whiteList_title= Whitelist -whiteList_description= Domänen oder URLs, die die -API verwenden dürfen. Mehrere Einträge müssen durch ein Komma getrennt weren. - -blackList_title= Blacklist -blackList_description= Domänen oder URLs, die die -API niemals verwenden dürfen. Mehrere Einträge müssen durch ein Komma getrennt weren. - -ignoreList_title= Ignorierliste -ignoreList_description= Domänen oder URLs, bei denen keine Benachrichtgung angezeigt werden. Mehrere Einträge müssen durch ein Komma getrennt weren. - -blockMode_title= Blockiermodus -blockMode_description= - -blockMode_options.block everything= alles blockieren -blockMode_options.allow only white list= nur Einträge der Whitelist erlauben -blockMode_options.ask for permission= um Erlaubnis fragen -blockMode_options.block readout API= Auslese-API blockieren -blockMode_options.fake readout API= Auslese-API vortäuschen -blockMode_options.ask for readout API permission= bei Auslese-API um Erlaubnis fragen -blockMode_options.block only black list= nur Einträge der Blacklist blockieren -blockMode_options.allow everything= alles erlauben - -askOnlyOnce_title= Nur einmal nachfragen -askOnlyOnce_description= Wenn der Blockiermodus des Canvas Blockers auf "um Erlaubnis fragen" oder "bei Auslese-API um Erlaubnis fragen" gesetzt ist, erscheint jedes mal ein Abfragedialog, wenn eine Seite versucht, die (Auslese-)API aufzurufen. Diese Einstellung versucht diese Abfrage nur einmal pro Seite anzuzeigen, unabhängig davon wie oft die API aufgerufen wird. Es können trotzudem mehrere Dialoge pro Seite erscheinen. - -showNotifications_title= Benachrichtigungen anzeiten -showNotifications_description= Benachrichtigungen anzeigen, wenn der Blockiermodus auf "Auslese-API vortäuschen" gesetzt ist. - -showCallingFile_title= Aufrufende Datei anzeigen -showCallingFile_description= - -showCompleteCallingStack_title= Kompletten Aufrufestack anzeigen -showCompleteCallingStack_description= - -allowPDFCanvas_title= in PDFs erlauben -allowPDFCanvas_description= Die native pdf.js verwendet um den Inhalt von PDFs anzuzeigen. Wenn viele Nachfragedialoge erscheinen oder die PDF-Ansicht nicht funktioniert, müssen diese erlaubt werden. - -askForPermission= Wollen Sie erlauben? -askForVisiblePermission= Wollen Sie das rot umrandete erlauben? -askForInvisiblePermission= Wollen Sie unsichtbare erlauben? -askForReadoutPermission= Wollen Sie das Auslesen von erlauben? -askForVisibleReadoutPermission= Wollen Sie das Auslesen des rot umrandeten erlauben? -askForInvisibleReadoutPermission= Wollen Sie das Auslesen von unsichtbaren erlauben? - -sourceOutput= Aufrufende Datei -stackEntryOutput= {url} Zeile {line} Spalte {column} - -fakedReadout = Auslese vorgetäuscht auf {url} -settings = Einstellungen -displayCallingStack = Aufrufestack anzeigen -displayFullURL = URL anzeigen -whitelistURL = erlaube URL -whitelistDomain = erlaube Domain -disableNotifications = Benachrichtigungen deaktivieren - -ignorelistURL = ignoriere URL -ignorelistDomain = ignoriere Domain \ No newline at end of file diff --git a/locale/en-US.json b/locale/en-US.json new file mode 100644 index 0000000..63f4dbd --- /dev/null +++ b/locale/en-US.json @@ -0,0 +1,53 @@ +{ + "allowPDFCanvas_description": "Firefox's native PDF reader uses the API to display PDF content. If too many ask dialogs appear or the PDF reader does not work at all, these have to be allowed.", + "allowPDFCanvas_title": "Allow canvas in PDFs", + "askForInvisiblePermission": "Do you want to allow invisible ?", + "askForInvisibleReadoutPermission": "Do you want to allow invisible readout?", + "askForPermission": "Do you want to allow ?", + "askForReadoutPermission": "Do you want to allow readout?", + "askForVisiblePermission": "Do you want to allow the red bordered ?", + "askForVisibleReadoutPermission": "Do you want to allow the readout of the red bordered ?", + "askOnlyOnce_description": "When Canvas Blocker's Block mode is set to 'ask permission' or 'ask permission for readout API', a confirm message will appear every time a page tries to access the API or readout API. This setting tries to display the confirm message only once for each page regardless of how many times the page tries to access the API. Nevertheless, multiple confirm messages may still be displayed on some pages.", + "askOnlyOnce_title": "Ask only once", + "blackList_description": "Domains or URLs where the -API should always be blocked. To add multiple entries, separate them by commas.", + "blackList_title": "Black list", + "blockMode_description": "", + "blockMode_options.allow everything": "allow everything", + "blockMode_options.allow only white list": "allow only white list", + "blockMode_options.ask for permission": "ask for permission", + "blockMode_options.ask for readout API permission": "ask for readout API permission", + "blockMode_options.block everything": "block everything", + "blockMode_options.block only black list": "block only black list", + "blockMode_options.block readout API": "block readout API", + "blockMode_options.fake readout API": "fake readout API", + "blockMode_title": "Block mode", + "disableNotifications": "disable notifications", + "displayCallingStack": "display calling stack", + "displayFullURL": "display full URL", + "enableStackList_description": "", + "enableStackList_title": "Use file specific white list", + "fakedReadout": "Faked readout on {url}", + "ignoreList_description": "Domains or URLs where no notification will be shown. To add multiple entries, separate them by commas.", + "ignoreList_title": "Ignore list", + "ignorelistDomain": "ignore domain", + "ignorelistURL": "ignore URL", + "inputIgnoreDomain": "Input domain to add to ignore list:", + "inputWhitelistDomain": "Input URL RegExp to add to white list:", + "inputWhitelistURL": "Input domain to add to white list:", + "settings": "settings", + "showCallingFile_description": "", + "showCallingFile_title": "Show calling file", + "showCompleteCallingStack_description": "", + "showCompleteCallingStack_title": "Display complete calling stack", + "showNotifications_description": "Show a notification when the block mode is set to \"fake readout API\".", + "showNotifications_title": "Show notifications", + "sourceOutput": "Calling file", + "stackEntryOutput": "{url} line {line} column {column}", + "stackList_description": "JS files which are allowed to use the -API. The input has to be in JSON format. Example: [{\"url\": \"http://domain/file1.js\"}, {\"url\": \"http://domain/file2.js\", \"line\": 1, \"column\": 4, \"stackPosition\": -3}]", + "stackList_title": "File specific white list", + "whilteList_title": "White list", + "whiteList_description": "Domains or URLs where the -API should not be blocked. To add multiple entries, separate them by commas.", + "whitelist": "whitelist", + "whitelistDomain": "whitelist domain", + "whitelistURL": "whitelist URL" +} \ No newline at end of file diff --git a/locale/en-US.properties b/locale/en-US.properties deleted file mode 100644 index e2399a7..0000000 --- a/locale/en-US.properties +++ /dev/null @@ -1,57 +0,0 @@ -whilteList_title= White list -whiteList_description= Domains or URLs where the -API should not be blocked. To add multiple entries, separate them by commas. - -blackList_title= Black list -blackList_description= Domains or URLs where the -API should always be blocked. To add multiple entries, separate them by commas. - -ignoreList_title= Ignore list -ignoreList_description= Domains or URLs where no notification will be shown. To add multiple entries, separate them by commas. - -blockMode_title= Block mode -blockMode_description= - -blockMode_options.block everything= block everything -blockMode_options.allow only white list= allow only white list -blockMode_options.ask for permission= ask for permission -blockMode_options.block readout API= block readout API -blockMode_options.fake readout API= fake readout API -blockMode_options.ask for readout API permission= ask for readout API permission -blockMode_options.block only black list= block only black list -blockMode_options.allow everything= allow everything - -askOnlyOnce_title= Ask only once -askOnlyOnce_description= When Canvas Blocker's Block mode is set to 'ask permission' or 'ask permission for readout API', a confirm message will appear every time a page tries to access the API or readout API. This setting tries to display the confirm message only once for each page regardless of how many times the page tries to access the API. Nevertheless, multiple confirm messages may still be displayed on some pages. - -showNotifications_title= Show notifications -showNotifications_description= Show a notification when the block mode is set to "fake readout API". - -showCallingFile_title= Show calling file -showCallingFile_description= - -showCompleteCallingStack_title= Display complete calling stack -showCompleteCallingStack_description= - -allowPDFCanvas_title= Allow canvas in PDFs -allowPDFCanvas_description= Firefox's native PDF reader uses the API to display PDF content. If too many ask dialogs appear or the PDF reader does not work at all, these have to be allowed. - -askForPermission= Do you want to allow ? -askForVisiblePermission= Do you want to allow the red bordered ? -askForInvisiblePermission= Do you want to allow invisible ? -askForReadoutPermission= Do you want to allow readout? -askForVisibleReadoutPermission= Do you want to allow the readout of the red bordered ? -askForInvisibleReadoutPermission= Do you want to allow invisible readout? - -sourceOutput= Calling file -stackEntryOutput= {url} line {line} column {column} - -fakedReadout = Faked readout on {url} -settings = settings -displayCallingStack = display calling stack -displayFullURL = display full URL -whitelist = whitelist -whitelistURL = whitelist URL -whitelistDomain = whitelist domain -disableNotifications = disable notifications - -ignorelistURL = ignore URL -ignorelistDomain = ignore domain \ No newline at end of file diff --git a/locales.json b/locales.json new file mode 100644 index 0000000..7a2112b --- /dev/null +++ b/locales.json @@ -0,0 +1 @@ +{"locales": ["de-DE", "en-US"]} diff --git a/package.json b/package.json index abf5532..4e6eddb 100644 --- a/package.json +++ b/package.json @@ -91,11 +91,22 @@ "title": "Display complete calling stack", "type": "bool", "value": false + },{ + "name": "enableStackList", + "title": "Use file specific scoped white list", + "type": "bool", + "value": false + }, + { + "name": "stackList", + "title": "File specific white list", + "type": "string", + "value": "" } ], "main": "lib/main.js", "author": "Korbinian Kapsner", "license": "MPL 2.0", - "version": "0.2.1-Development", + "version": "0.2.3-Development", "permissions": {"private-browsing": true} }