[
  {
    "path": ".github/FUNDING.yml",
    "content": "github: [jonasstrehle]\npatreon: unyt\nopen_collective: #\nko_fi: jonasstrehle\ntidelift: #\ncommunity_bridge: #\nliberapay: #\nissuehunt: #\notechie: #\ncustom: ['https://www.buymeacoffee.com/jonasstrehle']\n"
  },
  {
    "path": ".gitignore",
    "content": ".DS_Store\n/server/data.json\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2021 Jonas Strehle\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "<p align=\"center\">\r\n  <a href=\"https://supercookie.me\">\r\n    <img src=\"http://supercookie.me/favicon.ico\" alt=\"supercookie\" width=\"100\" />\r\n  </a>\r\n</p>\r\n<p align=\"center\">\r\n  <a href=\"https://supercookie.me/workwise\">\r\n    <img src=\"https://img.shields.io/badge/Docs-supercookie.me-blue\" alt=\"Documentation\">\r\n  </a>\r\n</p>\r\n<p align=\"center\">\r\n  <a href=\"https://supercookie.me\">\r\n    <img src=\"https://img.shields.io/website?down_message=down&up_color=green&up_message=online&url=https%3A%2F%2Fsupercookie.me\" alt=\"Website Status\">\r\n  </a>\r\n  <a href=\"https://github.com/jonasstrehle/supercookie\">\r\n    <img src=\"https://img.shields.io/github/license/jonasstrehle/supercookie\" alt=\"License\">\r\n  </a>\r\n</p>\r\n<p align=\"center\">\r\n  <a href=\"https://supercookie.me\">\r\n    <img src=\"https://img.shields.io/badge/dynamic/json?label=Fingerprints&query=index&url=https://supercookie.me/api&color=yellow\" alt=\"Fingerprint index\">\r\n  </a>\r\n  <a href=\"https://supercookie.me\">\r\n    <img src=\"https://img.shields.io/badge/dynamic/json?label=Current%20redirects&query=bits&url=https://supercookie.me/api&color=yellow\" alt=\"N Redirects\">\r\n  </a>\r\n</p>\r\n\r\n\r\n**Supercookie** uses favicons to assign a unique identifier to website visitors.<br>\r\nUnlike traditional tracking methods, this ID can be stored almost persistently and cannot be easily cleared by the user.\r\n\r\nThe tracking method works even in the browser's incognito mode and is not cleared by flushing the cache, closing the browser or restarting the operating system, using a VPN or installing AdBlockers. 🍿 [Live demo](https://supercookie.me).\r\n\r\n## About\r\n\r\n### 💭 Inspiration\r\n\r\n- Paper by Scientists at University of Illinois, Chicago: [par.nsf.gov](https://par.nsf.gov/servlets/purl/10268961)\r\n- Article by heise: [heise.de](https://heise.de/-5027814) \r\n\r\n### 🌱 Purpose\r\n\r\nThis repository is for **educational** and **demonstration purposes** only!\r\n\r\nThe demo of \"supercookie\" as well as the publication of the source code of this repository is intended to draw attention to the problem of tracking possibilities using favicons.\r\n\r\n📕 [Full documentation](https://supercookie.me/workwise)\r\n\r\n## Installation\r\n\r\n### 🔧 Docker\r\n**requirements**: \r\n<img src=\"https://www.docker.com/wp-content/uploads/2022/03/vertical-logo-monochromatic.png\" width=\"12\"> [Docker daemon](https://docs.docker.com/get-docker/)\r\n\r\n1. Clone repository\r\n```bash\r\ngit clone https://github.com/jonasstrehle/supercookie\r\n```\r\n\r\n2. Update .env file in [supercookie/server/.env](https://github.com/jonasstrehle/supercookie/blob/main/server/.env)\r\n```env\r\nHOST_MAIN=yourdomain.com #or localhost:10080\r\nPORT_MAIN=10080\r\n\r\nHOST_DEMO=demo.yourdomain.com #or localhost:10081\r\nPORT_DEMO=10081\r\n```\r\n\r\n3. Run container\r\n```bash\r\ncd supercookie/server\r\ndocker-compose up\r\n```\r\n\r\n-> Webserver will be running at https://yourdomain.com\r\n\r\n\r\n\r\n### 🤖 Local machine\r\n**requirements**: \r\n<img src=\"https://seeklogo.com/images/N/nodejs-logo-FBE122E377-seeklogo.com.png\" width=\"12\"> [Node.js](https://nodejs.org/)\r\n\r\n1. Clone repository\r\n```bash\r\ngit clone https://github.com/jonasstrehle/supercookie\r\n```\r\n\r\n2. Update .env file in [supercookie/server/.env](https://github.com/jonasstrehle/supercookie/blob/main/server/.env)\r\n```env\r\nHOST_MAIN=localhost:10080\r\nPORT_MAIN=10080\r\n\r\nHOST_DEMO=localhost:10081\r\nPORT_DEMO=10081\r\n```\r\n\r\n3. Run service\r\n```bash\r\ncd supercookie/server\r\nnode --experimental-json-modules main.js\r\n```\r\n\r\n-> Webserver will be running at http://localhost:10080\r\n\r\n\r\n## Workwise of [supercookie](https://supercookie.me/workwise)\r\n\r\n\r\n### [📖 Background](https://supercookie.me/workwise#content-background)\r\n\r\nModern browsers offer a wide range of features to improve and simplify the user experience.\r\nOne of these features are the so-called favicons: A favicon is a small (usually 16×16 or 32×32 pixels) logo used by web browsers to brand a website in a recognizable way. Favicons are usually shown by most browsers in the address bar and next to the page's name in a list of bookmarks.\r\n\r\nTo serve a favicon on their website, a developer has to include an <link rel> attribute in the webpage’s header. If this tag does exist, the browser requests the icon from the predefined source and if the server response contains an valid icon file that can be properly rendered this icon is displayed by the browser. In any other case, a blank favicon is shown.\r\n\r\n```html\r\n<link rel=\"icon\" href=\"/favicon.ico\" type=\"image/x-icon\">\r\n```\r\n\r\nThe favicons must be made very easily accessible by the browser. Therefore, they are cached in a separate local database on the system, called the favicon cache (F-Cache). A F-Cache data entries includes the visited URL (subdomain, domain, route, URL paramter), the favicon ID and the time to live (TTL).\r\nWhile this provides web developers the ability to delineate parts of their website using a wide variety of icons for individual routes and subdomains, it also leads to a possible tracking scenario.\r\n\r\nWhen a user visits a website, the browser checks if a favicon is needed by looking up the source of the shortcut icon link reference of the requested webpage.\r\nThe browser initialy checks the local F-cache for an entry containing the URL of the active website. If a favicon entry exists, the icon will be loaded from the cache and then displayed. However, if there is no entry, for example because no favicon has ever been loaded under this particular domain, or the data in the cache is out of date, the browser makes a GET request to the server to load the site's favicon.\r\n\r\n\r\n### [💣 Threat Model](https://supercookie.me/workwise#content-threat-model)\r\n\r\nIn the article a possible threat model is explained that allows to assign a unique identifier to each browser in order to draw conclusions about the user and to be able to identify this user even in case of applied anti-fingerprint measures, such as the use of a VPN, deletion of cookies, deletion of the browser cache or manipulation of the client header information.\r\n\r\nA web server can draw conclusions about whether a browser has already loaded a favicon or not:\r\nSo when the browser requests a web page, if the favicon is not in the local F-cache, another request for the favicon is made. If the icon already exists in the F-Cache, no further request is sent.\r\nBy combining the state of delivered and not delivered favicons for specific URL paths for a browser, a unique pattern (identification number) can be assigned to the client.\r\nWhen the website is reloaded, the web server can reconstruct the identification number with the network requests sent by the client for the missing favicons and thus identify the browser.\r\n\r\n\r\n\r\n\r\n<p align=\"center\">\r\n  <a href=\"https://supercookie.me\">\r\n    <img src=\"https://supercookie.me/assets/header.png\" alt=\"Supercookie Header\" width=\"600\" />\r\n  </a>\r\n</p>\r\n\r\n<table>\r\n  <thead>\r\n    <tr>\r\n      <th></th>\r\n      <th align=\"center\"><img width=\"350\" height=\"0\"> <p>conventional cookies</p></th>\r\n      <th align=\"center\"><img width=\"350\" height=\"0\"> <p>supercookie</p></th>\r\n    </tr>\r\n  </thead>\r\n  <tbody>\r\n    <tr>\r\n      <td>Identification accuracy</td>\r\n      <td align=\"center\">-</td>\r\n      <td align=\"center\">100%</td>\r\n    </tr>\r\n    <tr>\r\n      <td>Incognito / Private mode detection</td>\r\n      <td align=\"center\">❌</td>\r\n      <td align=\"center\">✅</td>\r\n    </tr>\r\n    <tr>\r\n      <td>Persistent after flushed website cache and cookies</td>\r\n      <td align=\"center\">❌</td>\r\n      <td align=\"center\">✅</td>\r\n    </tr>\r\n    <tr>\r\n      <td>Identify multiple windows</td>\r\n      <td align=\"center\">❌</td>\r\n      <td align=\"center\">✅</td>\r\n    </tr>\r\n    <tr>\r\n      <td>Working with Anti-Tracking SW</td>\r\n      <td align=\"center\">❌</td>\r\n      <td align=\"center\">✅</td>\r\n    </tr>\r\n  </tbody>\r\n</table>\r\n\r\n\r\n### [🎯 Target](https://supercookie.me/workwise#content-target)\r\n\r\nIt looks like all top browsers (<img src=\"https://www.google.com/favicon.ico\" width=\"12\"> [Chrome](https://google.com/chrome/), <img src=\"https://www.mozilla.org/favicon.ico\" width=\"12\"> [Firefox](https://www.mozilla.org/en-US/firefox/new/), <img src=\"https://www.apple.com/favicon.ico\" width=\"12\"> [Safari](https://www.apple.com/safari/), <img src=\"https://www.microsoft.com/favicon.ico\" width=\"12\"> [Edge](https://www.microsoft.com/edge/)) are vulnerable to this attack scenario.<br>\r\nMobile browsers are also affected.\r\n\r\n#### Current versions\r\n\r\n<table>\r\n  <thead>\r\n    <tr>\r\n      <th align=\"center\"><p>Browser</p></th>\r\n      <th align=\"center\"><p>Windows</p></th>\r\n      <th align=\"center\"><p>MacOS</p></th>\r\n      <th align=\"center\"><p>Linux</p></th>\r\n      <th align=\"center\"><p>iOS</p></th>\r\n      <th align=\"center\"><p>Android</p></th>\r\n      <th align=\"center\"><i>Info</i></th>\r\n    </tr>\r\n  </thead>\r\n  <tbody>\r\n    <tr>\r\n        <td>Chrome <em>(v 111.0)</em></td>\r\n        <td align=\"center\">✅</td>\r\n        <td align=\"center\">✅</td>\r\n        <td align=\"center\">✅</td>\r\n        <td align=\"center\">?</td>\r\n        <td align=\"center\">✅</td>\r\n        <td>-</td>\r\n    </tr>\r\n    <tr>\r\n        <td>Safari <em>(v 14.0)</em></td>\r\n        <td align=\"center\">-</td>\r\n        <td align=\"center\">✅</td>\r\n        <td align=\"center\">-</td>\r\n        <td align=\"center\">✅</td>\r\n        <td align=\"center\">-</td>\r\n        <td>-</td>\r\n    </tr>\r\n    <tr>\r\n        <td>Edge <em>(v 87.0)</em></td>\r\n        <td align=\"center\">✅</td>\r\n        <td align=\"center\">✅</td>\r\n        <td align=\"center\">❌</td>\r\n        <td align=\"center\">❌</td>\r\n        <td align=\"center\">✅</td>\r\n        <td>-</td>\r\n    </tr>\r\n    <tr>\r\n        <td>Firefox <em>(v 86.0)</em></td>\r\n        <td align=\"center\">✅</td>\r\n        <td align=\"center\">✅</td>\r\n        <td align=\"center\">❌</td>\r\n        <td align=\"center\">❌</td>\r\n        <td align=\"center\">❌</td>\r\n        <td>Fingerprint different in incognito mode</td>\r\n    </tr>\r\n    <tr>\r\n        <td>Brave <em>(v 1.19.92)</em></td>\r\n        <td align=\"center\">❌</td>\r\n        <td align=\"center\">❌</td>\r\n        <td align=\"center\">❌</td>\r\n        <td align=\"center\">❔</td>\r\n        <td align=\"center\">❌</td>\r\n        <td>-</td>\r\n    </tr>\r\n  </tbody>\r\n</table>\r\n\r\n\r\n#### Previous versions\r\n\r\n<table>\r\n  <thead>\r\n    <tr>\r\n      <th align=\"center\"><p>Browser</p></th>\r\n      <th align=\"center\"><p>Windows</p></th>\r\n      <th align=\"center\"><p>MacOS</p></th>\r\n      <th align=\"center\"><p>Linux</p></th>\r\n      <th align=\"center\"><p>iOS</p></th>\r\n      <th align=\"center\"><p>Android</p></th>\r\n      <th align=\"center\"><i>Info</i></th>\r\n    </tr>\r\n  </thead>\r\n  <tbody>\r\n    <tr>\r\n      <td><b>Brave</b> (v 1.14.0)</td>\r\n      <td align=\"center\">✅</td>\r\n      <td align=\"center\">✅</td>\r\n      <td align=\"center\">✅</td>\r\n      <td align=\"center\">✅</td>\r\n      <td align=\"center\">✅</td>\r\n      <td>-</td>\r\n    </tr>\r\n    <tr>\r\n      <td><b>Firefox</b> (&lt; v 84.0)</td>\r\n      <td align=\"center\">✅</td>\r\n      <td align=\"center\">✅</td>\r\n      <td align=\"center\">❔</td>\r\n      <td align=\"center\">❌</td>\r\n      <td align=\"center\">✅</td>\r\n      <td>-</td>\r\n    </tr>\r\n  </tbody>\r\n</table>\r\n\r\n\r\n### [⚙ Scalability & Performance](https://supercookie.me/workwise#content-scalability-performance)\r\n\r\nBy varying the number of bits that corresponds to the number of redirects to subpaths, this attack can be scaled almost arbitrarily.\r\nIt can distinguish 2^N unique users, where N is the number of redirects on the client side.\r\nThe time taken for the read and write operation increases as the number of distinguishable clients does.\r\n<br>\r\nIn order to keep the number of redirects as minimal as possible, N can have a dynamic length. \r\nMore about this [here](https://supercookie.me/workwise#content-scalability-performance).\r\n\r\n### [📌How to defend against?](https://supercookie.me/workwise)\r\n\r\nThe most straightforward solution is to disable the favicon cache completely. As long as the browser vendors do not provide a feature against this vulnerability it's probably the best way to clear the F-cache.\r\n\r\n* [Chrome](https://www.google.com/chrome/) • **MacOS**<br>\r\n  - Delete `~/Library/Application Support/Google/Chrome/Default/Favicons`\r\n  - Delete `~/Library/Application Support/Google/Chrome/Default/Favicons-journal`\r\n\r\n* [Chrome](https://www.google.com/chrome/) • **Windows**<br>\r\n  - Delete `C:\\Users\\username\\AppData\\Local\\Google\\Chrome\\User Data\\Default`\r\n\r\n* [Safari](https://www.apple.com/safari/) • **MacOS**<br>\r\n  - Delete content of `~/Library/Safari/Favicon Cache`\r\n\r\n* [Edge](https://www.microsoft.com/edge) • **MacOS**<br>\r\n  - Delete `~/Library/Application Support/Microsoft Edge/Default/Favicon`\r\n  - Delete `~/Library/Application Support/Microsoft Edge/Default/Favicons-journal`\r\n\r\n## Other\r\n\r\n### 🙎‍♂️ About me\r\n\r\nI am a twenty five year old student from 🇩🇪 Germany. I like to work in software design and development and have an interest in the IT security domain.\r\n\r\nThis repository, including the setup of a demonstration portal, was created within two days as part of a private research project on the topic of \"Tracking on the Web\".\r\n\r\n\r\n### [💖 Support the project](https://ko-fi.com/jonasstrehle)\r\n\r\n[![ko-fi](https://ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/jonasstrehle)\r\n\r\n## Spread the world!\r\n\r\nLiked the project? Just give it a star ⭐ and spread the world!\r\n\r\n* [Bruce Schneier on schneier.com](https://www.schneier.com/crypto-gram/archives/2021/0315.html#cg5)\r\n* [Matthew Gault on vice.com](https://www.vice.com/amp/en/article/n7v5y7/browser-favicons-can-be-used-as-undeletable-supercookies-to-track-you-online?__twitter_impression=true)\r\n* [Rhett Jones on gizmodo.com](https://gizmodo.com/favicons-could-be-the-supercookie-that-tracks-you-every-1846229089/)\r\n* [Dev Kundaliyaon on computing.co.uk](https://www.computing.co.uk/news/4027035/tiny-favicons-utilised-track-users-movements-online)\r\n* [Barclay Ballard on techradar.com](https://www.techradar.com/news/these-tiny-icons-could-be-tracking-you-across-the-internet)\r\n* [Discussion on ycombinator.com](https://news.ycombinator.com/item?id=26051370)\r\n* 🇩🇪 [Andreas Proschofsky on derstandard.de](https://www.derstandard.de/story/2000124123751/supercookies-datensammler-finden-immer-neue-wege-die-nutzer-auszuspionieren)\r\n* 🇩🇪 [Dieter Petereit on t3n.de](https://t3n.de/news/tracking-id-favicons-supercookie-1355514/)\r\n* 🇪🇸 [ALVY on microsiervos.com](https://www.microsiervos.com/archivo/seguridad/supercookie-me-identificador-personal-imborrable-icono-favicon.html)\r\n* 🇧🇷 [Felipe Demartini on canaltech.com.br](https://canaltech.com.br/seguranca/favicons-podem-ser-usados-para-rastrear-usuarios-online-permanentemente-178834/)\r\n* 🇧🇬 [Daniel Despodov on kaldata.com](https://www.kaldata.com/it-%D0%BD%D0%BE%D0%B2%D0%B8%D0%BD%D0%B8/%D0%BD%D0%BE%D0%B2-%D0%BC%D0%B5%D1%82%D0%BE%D0%B4-%D0%B7%D0%B0-%D0%B8%D0%B4%D0%B5%D0%BD%D1%82%D0%B8%D1%84%D0%B8%D0%BA%D0%B0%D1%86%D0%B8%D1%8F-%D0%BD%D0%B0-%D0%BA%D0%BE%D0%BD%D0%BA%D1%80%D0%B5%D1%82-355279.html)\r\n* 🇫🇷 [Guillaume Belfiore on clubic.com](https://www.clubic.com/navigateur-internet/actualite-353236-publicite-les-favicons-des-sites-web-pourraient-se-montrer-un-peu-trop-curieux.html)\r\n* 🇨🇳 [study875 on cnbeta.com (Via archive.org)](https://web.archive.org/web/20220701105509/http://cnbeta.com/articles/tech/1089095.htm)\r\n* 🇷🇺 [ITSumma on habr.com](https://habr.com/ru/company/itsumma/blog/542734/)\r\n* 🇷🇺 [securitylab.ru](https://www.securitylab.ru/news/516436.php)\r\n* <img src=\"https://youtube.com/favicon.ico\" width=\"20\"> [Seytonic on YouTube](https://youtu.be/X7OW5hTt5hY)\r\n\r\n\r\n\r\n"
  },
  {
    "path": "server/docker-compose.yml",
    "content": "version: \"3\"\n\nservices:\n  proxy:\n    image: \"traefik:v2.0\"\n    container_name: supercookie-proxy\n    hostname: supercookie-proxy\n    restart: always\n    command:\n      - --api=true\n      - --api.insecure=true\n      - --ping\n      - --providers.docker=true\n      - --providers.docker.network=main\n      - --providers.docker.exposedbydefault=false\n      - --entrypoints.web.address=:80\n      - --entrypoints.web-secure.address=:443\n      - --certificatesresolvers.myhttpchallenge.acme.httpchallenge=true\n      - --certificatesresolvers.myhttpchallenge.acme.httpchallenge.entrypoint=web\n      - --certificatesresolvers.myhttpchallenge.acme.caserver=https://acme-v02.api.letsencrypt.org/directory\n      - --certificatesresolvers.myhttpchallenge.acme.email=postmaster@unyt.cc\n      - --certificatesresolvers.myhttpchallenge.acme.storage=/letsencrypt/acme.json\n    ports:\n      - \"80:80\"\n      - \"443:443\"\n    expose:\n      - 80\n    networks:\n      - \"main\"\n      - \"internal\"\n    volumes:\n      - ./letsencrypt:/letsencrypt\n      - /var/run/docker.sock:/var/run/docker.sock:ro\n\n  supercookie:\n    container_name: supercookie-web\n    hostname: supercookie-web\n    restart: always\n    image: \"node\"\n    build: .\n    working_dir: /home/node/app\n    volumes:\n      - ./:/home/node/app\n      - ./node_modules:/home/node/app/node_modules\n      - ./tsconfig.json:/home/node/app/tsconfig.json\n      - ./.env:/home/node/app/.env\n    expose:\n      - ${PORT_MAIN}\n      - ${PORT_DEMO}\n    labels:\n      - \"traefik.enable=true\"\n      - \"traefik.http.middlewares.redirect-to-https.redirectscheme.scheme=https\"\n\n      # web main\n      - \"traefik.http.services.service-supercookie-web-1.loadbalancer.server.port=${PORT_MAIN}\"\n      - \"traefik.http.routers.supercookie-web-1.rule=Host(`${HOST_MAIN}`)\"\n      - \"traefik.http.routers.supercookie-web-1.entrypoints=web\"\n      - \"traefik.http.routers.supercookie-web-1.middlewares=redirect-to-https@docker\"\n      - \"traefik.http.routers.supercookie-web-1.service=service-supercookie-web-1\"\n\n      - \"traefik.http.routers.supercookie-web-1-secured.rule=Host(`${HOST_MAIN}`)\"\n      - \"traefik.http.routers.supercookie-web-1-secured.tls=true\"\n      - \"traefik.http.routers.supercookie-web-1-secured.tls.certresolver=myhttpchallenge\"\n      - \"traefik.http.routers.supercookie-web-1-secured.service=service-supercookie-web-1\"\n\n      # web demo\n      - \"traefik.http.services.service-supercookie-web-2.loadbalancer.server.port=${PORT_DEMO}\"\n      - \"traefik.http.routers.supercookie-web-2.rule=Host(`${HOST_DEMO}`)\"\n      - \"traefik.http.routers.supercookie-web-2.entrypoints=web\"\n      - \"traefik.http.routers.supercookie-web-2.middlewares=redirect-to-https@docker\"\n      - \"traefik.http.routers.supercookie-web-2.service=service-supercookie-web-2\"\n\n      - \"traefik.http.routers.supercookie-web-2-secured.rule=Host(`${HOST_DEMO}`)\"\n      - \"traefik.http.routers.supercookie-web-2-secured.tls=true\"\n      - \"traefik.http.routers.supercookie-web-2-secured.tls.certresolver=myhttpchallenge\"\n      - \"traefik.http.routers.supercookie-web-2-secured.service=service-supercookie-web-2\"\n    command: bash -c \"node --experimental-json-modules ./main.js\"\n    networks:\n      - main\n      - internal\n\nnetworks:\n  main:\n    external: true\n  internal:"
  },
  {
    "path": "server/main.js",
    "content": "import express from \"express\";\nimport path from \"path\";\nimport fs from \"fs\";\nimport cookieParser from \"cookie-parser\";\nimport crypto from \"crypto\";\nimport cors from \"cors\";\nimport dotenv from \"dotenv\";\nconst generateUUID = (pattern = \"xxxx-xxxx-xxxx-xxxx-xxxx\", charset = \"abcdefghijklmnopqrstuvwxyz0123456789\") => pattern.replace(/[x]/g, () => charset[Math.floor(Math.random() * charset.length)]);\nconst hashNumber = (value) => crypto.createHash(\"MD5\")\n    .update(value.toString())\n    .digest(\"hex\").slice(-12).split(/(?=(?:..)*$)/)\n    .join(' ').toUpperCase();\nconst createRoutes = (base, count) => {\n    const array = [];\n    for (let i = 0; i < count; i++)\n        array.push(crypto.createHash(\"MD5\")\n            .update(`${base}${i.toString()}`).digest(\"base64\")\n            .replace(/(\\=|\\+|\\/)/g, '0').substring(0, 22));\n    return array;\n};\nclass Storage {\n    constructor() {\n        this._path = path.join(path.resolve(), \"data.json\");\n        this._content = {};\n        if (!this.existsPersistent())\n            this.createPersistent();\n        this.read();\n    }\n    get content() {\n        return this._contentProxy;\n    }\n    set content(data) {\n        this._content = data;\n        const _this = this;\n        const proxy = {\n            get(target, key) {\n                if (typeof target[key] === 'object' && target[key] !== null)\n                    return new Proxy(target[key], proxy);\n                else\n                    return target[key];\n            },\n            set(target, key, value) {\n                target[key] = value;\n                _this.write(_this.content);\n                return true;\n            }\n        };\n        this._contentProxy = new Proxy(this._content, proxy);\n        _this.write(_this.content);\n    }\n    read() {\n        return this.content = JSON.parse(fs.readFileSync(this._path).toString() || \"{}\"), this;\n    }\n    write(content) {\n        fs.writeFileSync(this._path, JSON.stringify(content, null, '\\t'));\n        return this;\n    }\n    createPersistent() {\n        this.write({});\n    }\n    existsPersistent() {\n        return fs.existsSync(this._path);\n    }\n}\nconst STORAGE = new Storage().content;\ndotenv.config();\nconst WEBSERVER_DOMAIN_1 = process.env[\"HOST_MAIN\"] ?? \"localhost:10080\";\nconst WEBSERVER_DOMAIN_2 = process.env[\"HOST_DEMO\"] ?? \"localhost:10081\";\nconst WEBSERVER_PORT_1 = +process.env[\"PORT_MAIN\"] ?? 10080;\nconst WEBSERVER_PORT_2 = +process.env[\"PORT_DEMO\"] ?? 10081;\nconst CACHE_IDENTIFIER = STORAGE.cacheID ?? generateUUID(\"xxxxxxxx\", \"0123456789abcdef\");\nconst N = 32;\nconst FILE = \"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO+ip1sAAAAASUVORK5CYII=\";\nconst webserver_1 = express();\nconst webserver_2 = express();\nconst maxN = 2 ** N - 1;\nwebserver_1.options('*', cors());\nwebserver_2.options('*', cors());\nconsole.info(`supercookie | Starting up using N=${N}, C-ID='${CACHE_IDENTIFIER}' ...`);\nconsole.info(`supercookie | There are ${Math.max(maxN - 1 - (STORAGE.index ?? 1), 0)}/${maxN - 1} unique identifiers left.`);\nlet Webserver = (() => {\n    class Webserver {\n        static getVector(identifier) {\n            const booleanVector = (identifier >>> 0).toString(2)\n                .padStart(this.routes.length, '0').split('')\n                .map((element) => element === '1')\n                .reverse();\n            const vector = new Array();\n            booleanVector.forEach((value, index) => value ? vector.push(this.getRouteByIndex(index)) : void 0);\n            return vector;\n        }\n        static getIdentifier(vector, size = vector.size) {\n            return parseInt(this.routes.map((route) => vector.has(route) ? 0 : 1)\n                .join('').slice(0, size).split('').reverse().join(''), 2);\n        }\n        static hasRoute(route) {\n            return this.routes.includes(route);\n        }\n        static getRouteByIndex(index) {\n            return this.routes[index] ?? null;\n        }\n        static getIndexByRoute(route) {\n            return this.routes.indexOf(route) ?? null;\n        }\n        static getNextRoute(route) {\n            const index = this.routes.indexOf(route);\n            if (index === -1)\n                throw \"Route is not valid.\";\n            return this.getRouteByIndex(index + 1);\n        }\n        static setCookie(res, name, value, options = { httpOnly: false, expires: new Date(Date.now() + 60 * 1000) }) {\n            return res.cookie(name, value, options), res;\n        }\n        static sendFile(res, route, options = {}, type = \"html\") {\n            let content = fs.readFileSync(route).toString();\n            Object.keys(options).sort((a, b) => b.length - a.length).forEach((key) => {\n                content = content.replace(new RegExp(`\\{\\{${key}\\}\\}`, 'g'), (options[key]?.toString() || '')\n                    .replace(/&/g, \"&amp;\")\n                    .replace(/</g, \"&lt;\")\n                    .replace(/>/g, \"&gt;\")\n                    .replace(/\"/g, \"&quot;\")\n                    .replace(/'/g, \"&#039;\"));\n            });\n            res.header({\n                \"Cache-Control\": \"private, no-cache, no-store, must-revalidate\",\n                \"Expires\": -1,\n                \"Pragma\": \"no-cache\"\n            });\n            res.type(type);\n            return res.send(content), res;\n        }\n    }\n    Webserver.routes = createRoutes(CACHE_IDENTIFIER, N).map((value) => `${CACHE_IDENTIFIER}:${value}`);\n    return Webserver;\n})();\nlet Profile = (() => {\n    class Profile {\n        constructor(uid, identifier = null) {\n            this._identifier = null;\n            this._visitedRoutes = new Set();\n            this._storageSize = -1;\n            this._uid = uid;\n            if (identifier !== null)\n                this._identifier = identifier,\n                    this._vector = Webserver.getVector(identifier);\n            Profile.list.add(this);\n        }\n        static get(uid) {\n            return this.has(uid) ?\n                Array.from(this.list).filter((profile) => profile.uid === uid)?.pop() :\n                null;\n        }\n        static has(uid) {\n            return Array.from(this.list).some((profile) => profile.uid === uid);\n        }\n        static from(uid, identifier) {\n            return !this.has(uid) ? new Profile(uid, identifier) : null;\n        }\n        destructor() {\n            Profile.list.delete(this);\n        }\n        get uid() {\n            return this._uid;\n        }\n        get vector() {\n            return this._vector;\n        }\n        get visited() {\n            return this._visitedRoutes;\n        }\n        get identifier() {\n            return this._identifier;\n        }\n        getRouteByIndex(index) {\n            return this.vector[index] ?? null;\n        }\n        _isReading() {\n            return this._identifier === null;\n        }\n        _visitRoute(route) {\n            this._visitedRoutes.add(route);\n        }\n        _calcIdentifier() {\n            return this._identifier = Webserver.getIdentifier(this._visitedRoutes, this._storageSize), this.identifier;\n        }\n        _setStorageSize(size) {\n            this._storageSize = size;\n        }\n        get storageSize() {\n            return this._storageSize;\n        }\n    }\n    Profile.list = new Set();\n    return Profile;\n})();\n;\nwebserver_2.set(\"trust proxy\", 1);\nwebserver_2.use(cookieParser());\nwebserver_2.use((req, res, next) => {\n    if (new RegExp(`https?:\\/\\/${WEBSERVER_DOMAIN_2}`).test(req.headers.origin))\n        res.setHeader(\"Access-Control-Allow-Origin\", req.headers.origin);\n    res.header(\"Access-Control-Allow-Methods\", \"GET, OPTIONS\");\n    res.header(\"Access-Control-Allow-Headers\", \"Origin, X-Requested-With, Content-Type, Accept\");\n    return next();\n});\nconst midSet = new Set();\nconst generateWriteToken = () => {\n    const uuid = generateUUID();\n    setTimeout(() => midSet.delete(uuid), 1000 * 60);\n    return midSet.add(uuid), uuid;\n};\nconst deleteWriteToken = (token) => midSet.delete(token);\nconst hasWriteToken = (token) => midSet.has(token);\nwebserver_2.get(\"/read\", (_req, res) => {\n    const uid = generateUUID();\n    console.info(`supercookie | Visitor uid='${uid}' is known • Read`);\n    const profile = Profile.from(uid);\n    profile._setStorageSize(Math.floor(Math.log2(STORAGE.index ?? 1)) + 1);\n    if (profile === null)\n        return res.redirect(\"/read\");\n    Webserver.setCookie(res, \"uid\", uid);\n    res.redirect(`/t/${Webserver.getRouteByIndex(0)}?f=${generateUUID()}`);\n});\nwebserver_2.get(\"/write/:mid\", (req, res) => {\n    const mid = req.params.mid;\n    if (!hasWriteToken(mid))\n        return res.redirect('/');\n    res.clearCookie(\"mid\");\n    deleteWriteToken(mid);\n    const uid = generateUUID();\n    console.info(`supercookie | Visitor uid='${uid}' is unknown • Write`, STORAGE.index);\n    const profile = Profile.from(uid, STORAGE.index);\n    if (profile === null)\n        return res.redirect('/');\n    STORAGE.index++;\n    Webserver.setCookie(res, \"uid\", uid);\n    res.redirect(`/t/${Webserver.getRouteByIndex(0)}`);\n});\nwebserver_2.get(\"/t/:ref\", (req, res) => {\n    const referrer = req.params.ref;\n    const uid = req.cookies.uid;\n    const profile = Profile.get(uid);\n    if (!Webserver.hasRoute(referrer) || profile === null)\n        return res.redirect('/');\n    const route = Webserver.getNextRoute(referrer);\n    if (profile._isReading() && profile.visited.has(referrer))\n        return res.redirect('/');\n    let nextReferrer = null;\n    const redirectCount = profile._isReading() ?\n        profile.storageSize :\n        Math.floor(Math.log2(profile.identifier)) + 1;\n    if (route)\n        nextReferrer = `t/${route}?f=${generateUUID()}`;\n    if (!profile._isReading()) {\n        if (Webserver.getIndexByRoute(referrer) >= redirectCount - 1)\n            nextReferrer = \"read\";\n    }\n    else if (Webserver.getIndexByRoute(referrer) >= redirectCount - 1 || nextReferrer === null)\n        nextReferrer = \"identity\";\n    const bit = !profile._isReading() ? profile.vector.includes(referrer) : \"{}\";\n    Webserver.sendFile(res, path.join(path.resolve(), \"www/referrer.html\"), {\n        delay: profile._isReading() ? 500 : 800,\n        referrer: nextReferrer,\n        favicon: referrer,\n        bit: bit,\n        index: `${Webserver.getIndexByRoute(referrer) + 1} / ${redirectCount}`\n    });\n});\nwebserver_2.get(\"/identity\", (req, res) => {\n    const uid = req.cookies.uid;\n    const profile = Profile.get(uid);\n    if (profile === null)\n        return res.redirect('/');\n    res.clearCookie(\"uid\");\n    res.clearCookie(\"vid\");\n    const identifier = profile._calcIdentifier();\n    if (identifier === maxN || profile.visited.size === 0 || identifier === 0)\n        return res.redirect(`/write/${generateWriteToken()}`);\n    if (identifier !== 0) {\n        const identifierHash = hashNumber(identifier);\n        console.info(`supercookie | Visitor successfully identified as '${identifierHash}' • (#${identifier}).`);\n        Webserver.sendFile(res, path.join(path.resolve(), \"www/identity.html\"), {\n            hash: identifierHash,\n            identifier: `#${identifier}`,\n            url_workwise: `${WEBSERVER_DOMAIN_1}/workwise`,\n            url_main: WEBSERVER_DOMAIN_1\n        });\n    }\n    else\n        Webserver.sendFile(res, path.join(path.resolve(), \"www/identity.html\"), {\n            hash: \"AN ON YM US\",\n            identifier: \"browser not vulnerable\",\n            url_workwise: `${WEBSERVER_DOMAIN_1}/workwise`,\n            url_main: WEBSERVER_DOMAIN_1\n        });\n});\nwebserver_2.get(`/${CACHE_IDENTIFIER}`, (req, res) => {\n    const rid = !!req.cookies.rid;\n    res.clearCookie(\"rid\");\n    if (!rid)\n        Webserver.sendFile(res, path.join(path.resolve(), \"www/redirect.html\"), {\n            url_demo: WEBSERVER_DOMAIN_2\n        });\n    else\n        Webserver.sendFile(res, path.join(path.resolve(), \"www/launch.html\"), {\n            favicon: CACHE_IDENTIFIER\n        });\n});\nwebserver_2.get('/', (_req, res) => {\n    Webserver.setCookie(res, \"rid\", true);\n    res.clearCookie(\"mid\");\n    res.redirect(`/${CACHE_IDENTIFIER}`);\n});\nwebserver_2.get(\"/l/:ref\", (_req, res) => {\n    console.info(`supercookie | Unknown visitor detected.`);\n    Webserver.setCookie(res, \"mid\", generateWriteToken());\n    const data = Buffer.from(FILE, \"base64\");\n    res.writeHead(200, {\n        \"Cache-Control\": \"public, max-age=31536000\",\n        \"Expires\": new Date(Date.now() + 31536000000).toUTCString(),\n        \"Content-Type\": \"image/png\",\n        \"Content-Length\": data.length\n    });\n    res.end(data);\n});\nwebserver_2.get(\"/i/:ref\", (req, res) => {\n    const data = Buffer.from(FILE, \"base64\");\n    res.writeHead(200, {\n        \"Cache-Control\": \"public, max-age=31536000\",\n        \"Expires\": new Date(Date.now() + 31536000000).toUTCString(),\n        \"Content-Type\": \"image/png\",\n        \"Content-Length\": data.length\n    });\n    res.end(data);\n});\nwebserver_2.get(\"/f/:ref\", (req, res) => {\n    const referrer = req.params.ref;\n    const uid = req.cookies.uid;\n    if (!Profile.has(uid) || !Webserver.hasRoute(referrer))\n        return res.status(404), res.end();\n    const profile = Profile.get(uid);\n    if (profile._isReading()) {\n        profile._visitRoute(referrer);\n        console.info(`supercookie | Favicon requested by uid='${uid}' • Read `, Webserver.getIndexByRoute(referrer), \"•\", Array.from(profile.visited).map(route => Webserver.getIndexByRoute(route)));\n        return;\n    }\n    if (!profile.vector.includes(referrer)) {\n        console.info(`supercookie | Favicon requested by uid='${uid}' • Write`, Webserver.getIndexByRoute(referrer), \"•\", Array.from(profile.vector).map(route => Webserver.getIndexByRoute(route)));\n        return;\n    }\n    const data = Buffer.from(FILE, \"base64\");\n    res.writeHead(200, {\n        \"Cache-Control\": \"public, max-age=31536000\",\n        \"Expires\": new Date(Date.now() + 31536000000).toUTCString(),\n        \"Content-Type\": \"image/png\",\n        \"Content-Length\": data.length\n    });\n    res.end(data);\n});\nwebserver_1.use(\"/assets\", express.static(path.join(path.resolve(), \"www/assets\"), { index: false }));\nwebserver_2.use(\"/assets\", express.static(path.join(path.resolve(), \"www/assets\"), { index: false }));\nwebserver_1.get('/', (_req, res) => {\n    Webserver.sendFile(res, path.join(path.resolve(), \"www/index.html\"), {\n        url_demo: WEBSERVER_DOMAIN_2\n    });\n});\nwebserver_1.get(\"/favicon.ico\", (_req, res) => {\n    res.sendFile(path.join(path.resolve(), \"www/favicon.ico\"));\n});\nwebserver_2.get(\"/favicon.ico\", (_req, res) => {\n    res.sendFile(path.join(path.resolve(), \"www/favicon.ico\"));\n});\nwebserver_1.get(\"/workwise\", (_req, res) => {\n    Webserver.sendFile(res, path.join(path.resolve(), \"www/workwise.html\"), {\n        url_main: WEBSERVER_DOMAIN_1\n    });\n});\nwebserver_1.get(\"/api\", (_req, res) => {\n    res.type(\"json\");\n    res.status(200);\n    res.send({\n        index: STORAGE.index,\n        cache: STORAGE.cacheID,\n        bits: Math.floor(Math.log2(STORAGE.index ?? 1)) + 1,\n        N: N,\n        maxN: maxN\n    });\n});\nwebserver_1.get('*', (_req, res) => {\n    res.redirect('/');\n});\nwebserver_2.get('*', (req, res) => {\n    Webserver.sendFile(res, path.join(path.resolve(), \"www/404.html\"), {\n        path: decodeURIComponent(req.path),\n        url_main: WEBSERVER_DOMAIN_1\n    });\n});\nwebserver_1.listen(WEBSERVER_PORT_1, () => console.info(`express-web | Webserver-1 for '${WEBSERVER_DOMAIN_1}' running on port:`, WEBSERVER_PORT_1));\nwebserver_2.listen(WEBSERVER_PORT_2, () => console.info(`express-web | Webserver-2 for '${WEBSERVER_DOMAIN_2}' running on port:`, WEBSERVER_PORT_2));\nSTORAGE.index = STORAGE.index ?? 1;\nSTORAGE.cacheID = CACHE_IDENTIFIER;\n"
  },
  {
    "path": "server/main.ts",
    "content": "import express from \"express\";\nimport path from \"path\";\nimport fs from \"fs\";\nimport cookieParser from \"cookie-parser\";\nimport crypto from \"crypto\";\nimport cors from \"cors\";\nimport dotenv from \"dotenv\";\n\n/**\n * Creates UUID in the specified pattern's\n * form using charset\n * @param pattern \n * @param charset \n */\nconst generateUUID = (\n    pattern: string = \"xxxx-xxxx-xxxx-xxxx-xxxx\", \n    charset: string = \"abcdefghijklmnopqrstuvwxyz0123456789\"): string =>\n\tpattern.replace(/[x]/g, () => charset[Math.floor(Math.random() * charset.length)]);\n\n/**\n * Creates HEX-hash from number \n * @param value\n */\nconst hashNumber = (value: number): string => crypto.createHash(\"MD5\")\n    .update(value.toString())\n    .digest(\"hex\").slice(-12).split(/(?=(?:..)*$)/)\n    .join(' ').toUpperCase();\n\n/**\n * Creates string-array with length \"count\"\n * from value \"base\"\n * @param base \n * @param count \n */\nconst createRoutes = (base: string, count: number): Array<string> => {\n    const array = [];\n    for (let i=0; i<count; i++)\n        array.push(crypto.createHash(\"MD5\")\n            .update(`${base}${i.toString()}`).digest(\"base64\")\n            .replace(/(\\=|\\+|\\/)/g, '0').substring(0, 22));\n    return array;\n}\n\n/**\n * @class Storage\n * For writing and reading\n * persistent JSON on file-system\n */\nclass Storage {\n    private _path: string = path.join(path.resolve(), \"data.json\");\n    private _content: object = {};\n    private _contentProxy: object;\n    constructor() {\n        if (!this.existsPersistent())\n            this.createPersistent();\n        this.read();\n    }\n    public get content(): any {\n        return this._contentProxy;\n    }\n    public set content(data: any) {\n        this._content = data;\n        const _this = this;\n        const proxy = {\n            get(target: any, key: any) {\n                if (typeof target[key] === 'object' && target[key] !== null) \n                    return new Proxy(target[key], proxy)\n                else return target[key];\n            },\n            set (target: any, key: any, value: any): any {\n                target[key] = value;\n                _this.write(_this.content);\n                return true;\n            }\n        }\n        this._contentProxy = new Proxy(this._content, proxy);\n        _this.write(_this.content);\n    }\n    private read(): Storage {\n        return this.content = JSON.parse(fs.readFileSync(this._path).toString() || \"{}\"), this;\n    }\n    private write(content: object): Storage {\n        fs.writeFileSync(this._path, JSON.stringify(content, null, '\\t'));\n        return this;\n    }\n    private createPersistent() {\n        this.write({});\n    }\n    private existsPersistent() {\n        return fs.existsSync(this._path);\n    }\n}\nconst STORAGE: any = new Storage().content;\ndotenv.config();\n\n/****************************************************************************************************\\\n * @global\n * User options (edit in .env file)\n */\nconst WEBSERVER_DOMAIN_1: string    = process.env[\"HOST_MAIN\"] ?? \"localhost:10080\";\nconst WEBSERVER_DOMAIN_2: string    = process.env[\"HOST_DEMO\"] ?? \"localhost:10081\";\nconst WEBSERVER_PORT_1: number      = +process.env[\"PORT_MAIN\"] ?? 10080;\nconst WEBSERVER_PORT_2: number      = +process.env[\"PORT_DEMO\"] ?? 10081;\nconst CACHE_IDENTIFIER: string      = STORAGE.cacheID ?? generateUUID(\"xxxxxxxx\", \"0123456789abcdef\");\n\nconst N: number                     = 32; // max 2^N unique ids possible\n/*****************************************************************************************************/\n\n\nconst FILE = \"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO+ip1sAAAAASUVORK5CYII=\";\nconst webserver_1: express.Express = express();\nconst webserver_2: express.Express = express();\nconst maxN: number = 2**N - 1;\n\nwebserver_1.options('*', cors());\nwebserver_2.options('*', cors());\n\nconsole.info(`supercookie | Starting up using N=${N}, C-ID='${CACHE_IDENTIFIER}' ...`);\nconsole.info(`supercookie | There are ${Math.max(maxN - 1 - (STORAGE.index ?? 1), 0)}/${maxN-1} unique identifiers left.`);\n\n\n/**\n * @class Webserver\n * Webserver defaults\n */\nclass Webserver {\n    public static routes: Array<string> = createRoutes(CACHE_IDENTIFIER, N).map((value: string) => `${CACHE_IDENTIFIER}:${value}`);\n    \n    public static getVector(identifier: number): Array<string> {\n        const booleanVector: Array<boolean> = (identifier >>> 0).toString(2)\n            .padStart(this.routes.length, '0').split('')\n            .map((element: '0' | '1') => element === '1')\n            .reverse();\n        const vector = new Array<string>();\n        booleanVector.forEach((value: boolean, index: number) => value ? vector.push(this.getRouteByIndex(index)) : void 0);\n        return vector;\n    }\n    public static getIdentifier(vector: Set<string>, size: number = vector.size): number {\n        return parseInt(this.routes.map((route: string) => vector.has(route) ? 0 : 1)\n            .join('').slice(0, size).split('').reverse().join(''), 2);\n    }\n    public static hasRoute(route: string): boolean {\n        return this.routes.includes(route);\n    }\n    public static getRouteByIndex(index: number): string {\n        return this.routes[index] ?? null;\n    }\n    public static getIndexByRoute(route: string): number {\n        return this.routes.indexOf(route) ?? null;\n    }\n    public static getNextRoute(route: string): string | null {\n        const index = this.routes.indexOf(route);\n        if (index === -1)\n            throw \"Route is not valid.\";\n        return this.getRouteByIndex(index+1);\n    }\n    public static setCookie(res: express.Response,\n                            name: string, value: any, \n                            options: express.CookieOptions = { httpOnly: false, expires: new Date(Date.now() + 60 * 1000) }): express.Response {\n        return res.cookie(name, value, options), res;\n    }\n    public static sendFile( res: express.Response, \n                            route: string, options: any = {}, type: string = \"html\"): express.Response {\n        let content = fs.readFileSync(route).toString();\n        Object.keys(options).sort((a: string, b: string) => b.length - a.length).forEach((key: string) => {\n            content = content.replace(\n                new RegExp(`\\{\\{${key}\\}\\}`, 'g'), \n                (options[key]?.toString() || '')\n                .replace(/&/g, \"&amp;\")\n                .replace(/</g, \"&lt;\")\n                .replace(/>/g, \"&gt;\")\n                .replace(/\"/g, \"&quot;\")\n                .replace(/'/g, \"&#039;\")\n            );\n        });\n        res.header({\n            \"Cache-Control\": \"private, no-cache, no-store, must-revalidate\",\n            \"Expires\": -1,\n            \"Pragma\": \"no-cache\"\n        });\n        res.type(type);\n        return res.send(content), res;\n    }\n}\n\n/**\n * @class Profile\n * Read / Write class\n */\nclass Profile {\n    public static list: Set<Profile> = new Set<Profile>();\n    public static get(uid: string): Profile {\n        return this.has(uid) ? \n            Array.from(this.list).filter((profile: Profile) => profile.uid === uid)?.pop(): \n            null;\n    }\n    public static has(uid: string): boolean {\n        return Array.from(this.list).some((profile: Profile) => profile.uid === uid);\n    }\n    public static from(uid: string, identifier?: number): Profile {\n        return !this.has(uid) ? new Profile(uid, identifier): null;\n    }\n\n    private _uid: string;\n    private _vector: Array<string>;\n    private _identifier: number = null;\n    private _visitedRoutes: Set<string> = new Set<string>();\n    private _storageSize: number = -1;\n\n    constructor(uid: string, identifier: number = null) {\n        this._uid = uid;\n        if (identifier !== null) \n            this._identifier = identifier,\n            this._vector = Webserver.getVector(identifier);\n        Profile.list.add(this);\n    }\n    public destructor() {\n        Profile.list.delete(this);\n    }\n    public get uid(): string {\n        return this._uid;\n    }\n    public get vector(): Array<string> {\n        return this._vector;\n    }\n    public get visited(): Set<string> {\n        return this._visitedRoutes;\n    }\n    public get identifier(): number {\n        return this._identifier;\n    }\n    public getRouteByIndex(index: number): string {\n        return this.vector[index] ?? null;\n    }\n    public _isReading(): boolean {\n        return this._identifier === null;\n    }\n    public _visitRoute(route: string) {\n        this._visitedRoutes.add(route);\n    }\n    public _calcIdentifier(): number {\n        return this._identifier = Webserver.getIdentifier(this._visitedRoutes, this._storageSize), this.identifier;\n    }\n    public _setStorageSize(size: number) {\n        this._storageSize = size;\n    }\n    public get storageSize(): number {\n        return this._storageSize;\n    }\n};\n\nwebserver_2.set(\"trust proxy\", 1);\nwebserver_2.use(cookieParser());\nwebserver_2.use((req: express.Request, res: express.Response, next: Function) => {  \n    if (new RegExp(`https?:\\/\\/${WEBSERVER_DOMAIN_2}`).test(req.headers.origin))\n        res.setHeader(\"Access-Control-Allow-Origin\", req.headers.origin);\n    res.header(\"Access-Control-Allow-Methods\", \"GET, OPTIONS\");\n    res.header(\"Access-Control-Allow-Headers\", \"Origin, X-Requested-With, Content-Type, Accept\");\n    return next();\n});\n\n\n/**\n * @description\n * Using token based \"write authentification\" to avoid spam to /write path\n */\nconst midSet: Set<string> = new Set<string>();\nconst generateWriteToken = (): string => {\n    const uuid = generateUUID();\n    setTimeout(() => midSet.delete(uuid), 1_000 * 60);\n    return midSet.add(uuid), uuid;\n}\nconst deleteWriteToken = (token: string) => midSet.delete(token);\nconst hasWriteToken = (token: string): boolean => midSet.has(token);\n\n/**\n * @description\n * When navigating to path /read the mode of an (known) visitor is set to \"write\". \n * Assuming that the data has already been written to the browser, the webserver\n * is redirecting the user to the first route.\n */\nwebserver_2.get(\"/read\", (_req: express.Request, res: express.Response) => {\n    const uid = generateUUID();\n    console.info(`supercookie | Visitor uid='${uid}' is known • Read`);\n    const profile: Profile = Profile.from(uid);\n    profile._setStorageSize(Math.floor(Math.log2(STORAGE.index ?? 1)) + 1);\n    if (profile === null)\n        return res.redirect(\"/read\");\n    Webserver.setCookie(res, \"uid\", uid);\n    res.redirect(`/t/${Webserver.getRouteByIndex(0)}?f=${generateUUID()}`)\n});\n\n/**\n * @description\n * If a user navigates to path /write a new (unknown) visitor entry is created.\n * Assuming that the data has not been written to the browser, the webserver\n * is redirecting the user to the first route.\n */\nwebserver_2.get(\"/write/:mid\", (req: express.Request, res: express.Response) => {\n    const mid = req.params.mid;\n    if (!hasWriteToken(mid))\n        return res.redirect('/');\n    res.clearCookie(\"mid\");\n    deleteWriteToken(mid);\n    const uid = generateUUID();\n    console.info(`supercookie | Visitor uid='${uid}' is unknown • Write`, STORAGE.index);\n    const profile: Profile = Profile.from(uid, STORAGE.index);\n    if (profile === null)\n        return res.redirect('/');\n    STORAGE.index++;\n    Webserver.setCookie(res, \"uid\", uid);\n    res.redirect(`/t/${Webserver.getRouteByIndex(0)}`);\n});\n\n/**\n * @description\n * Under the /t path, the user is redirected to the next possible route.\n */\nwebserver_2.get(\"/t/:ref\", (req: express.Request, res: express.Response) => {\n    const referrer: string = req.params.ref;\n    const uid: string = req.cookies.uid;\n    const profile: Profile = Profile.get(uid);\n\n    if (!Webserver.hasRoute(referrer) || profile === null)\n        return res.redirect('/');\n    const route: string = Webserver.getNextRoute(referrer);\n\n    /** reload issue */\n    if (profile._isReading() && profile.visited.has(referrer))\n        return res.redirect('/');\n    let nextReferrer: string = null;\n    const redirectCount: number = profile._isReading() ? \n        profile.storageSize: \n        Math.floor(Math.log2(profile.identifier)) + 1;\n\n    if (route) \n        nextReferrer = `t/${route}?f=${generateUUID()}`;\n    if (!profile._isReading()) {\n        if (Webserver.getIndexByRoute(referrer) >= redirectCount - 1)\n            nextReferrer = \"read\";\n    } else if (Webserver.getIndexByRoute(referrer) >= redirectCount - 1 || nextReferrer === null)\n        nextReferrer = \"identity\";\n\n    const bit = !profile._isReading() ? profile.vector.includes(referrer) : \"{}\";\n    Webserver.sendFile(res, path.join(path.resolve(), \"www/referrer.html\"), {\n        delay: profile._isReading() ? 500 : 800,\n        referrer: nextReferrer,\n        favicon: referrer,\n        bit: bit,\n        index: `${Webserver.getIndexByRoute(referrer)+1} / ${redirectCount}`\n    });\n});\n\n/**\n * @description\n * After finishing the reading process, the browser is redirected to the /identity route. \n * Here, the browser is assigned the calculated identifier and displayed to the user.\n */\nwebserver_2.get(\"/identity\", (req: express.Request, res: express.Response) => {\n    const uid: string = req.cookies.uid;\n    const profile: Profile = Profile.get(uid);\n    if (profile === null)\n        return res.redirect('/');\n    res.clearCookie(\"uid\");\n    res.clearCookie(\"vid\");\n    const identifier = profile._calcIdentifier();\n    if (identifier === maxN || profile.visited.size === 0 || identifier === 0)\n        return res.redirect(`/write/${generateWriteToken()}`);\n    if (identifier !== 0) {\n        const identifierHash: string = hashNumber(identifier);\n        console.info(`supercookie | Visitor successfully identified as '${identifierHash}' • (#${identifier}).`);\n        Webserver.sendFile(res, path.join(path.resolve(), \"www/identity.html\"), {\n            hash: identifierHash,\n            identifier: `#${identifier}`,\n\n            url_workwise: `${WEBSERVER_DOMAIN_1}/workwise`,\n            url_main: WEBSERVER_DOMAIN_1\n        });\n    } else Webserver.sendFile(res, path.join(path.resolve(), \"www/identity.html\"), {\n        hash: \"AN ON YM US\",\n        identifier: \"browser not vulnerable\",\n\n        url_workwise: `${WEBSERVER_DOMAIN_1}/workwise`,\n        url_main: WEBSERVER_DOMAIN_1\n    });\n});\n\n/**\n * @description\n * Fixing a chrome (v 87.0) problem using javascript redirect instead of \n * express redirect (in redirect.html)\n */\nwebserver_2.get(`/${CACHE_IDENTIFIER}`, (req: express.Request, res: express.Response) => {\n    const rid: boolean = !!req.cookies.rid;\n    res.clearCookie(\"rid\");\n    if (!rid) \n        Webserver.sendFile(res, path.join(path.resolve(), \"www/redirect.html\"), {\n            url_demo: WEBSERVER_DOMAIN_2\n        });\n    else\n        Webserver.sendFile(res, path.join(path.resolve(), \"www/launch.html\"), {\n            favicon: CACHE_IDENTIFIER\n        });\n});\n\n/**\n * @description\n * Main route / is redirecting to /CACHE_IDENTIFIER\n */\nwebserver_2.get('/', (_req: express.Request, res: express.Response) => {\n    Webserver.setCookie(res, \"rid\", true);\n    res.clearCookie(\"mid\");\n    res.redirect(`/${CACHE_IDENTIFIER}`);\n});\n\n/**\n * @description\n * When requesting the favicon under /l, it is excluded that a user already has valid data in the cache.\n */\nwebserver_2.get(\"/l/:ref\", (_req: express.Request, res: express.Response) => {\n    console.info(`supercookie | Unknown visitor detected.`);\n    Webserver.setCookie(res, \"mid\", generateWriteToken());\n    const data = Buffer.from(FILE, \"base64\");\n    res.writeHead(200, {\n        \"Cache-Control\": \"public, max-age=31536000\",\n        \"Expires\": new Date(Date.now() + 31536000000).toUTCString(),\n        \"Content-Type\": \"image/png\",\n        \"Content-Length\": data.length\n    });\n    res.end(data);\n});\n\n\nwebserver_2.get(\"/i/:ref\", (req: express.Request, res: express.Response) => {\n    const data = Buffer.from(FILE, \"base64\");\n    res.writeHead(200, {\n        \"Cache-Control\": \"public, max-age=31536000\",\n        \"Expires\": new Date(Date.now() + 31536000000).toUTCString(),\n        \"Content-Type\": \"image/png\",\n        \"Content-Length\": data.length\n    });\n    res.end(data);\n});\n/**\n * @description\n * /f route handles requests for favicons by the browser.\n * In write mode, some icons are delivered and other requests are aborted. \n * In read mode every request fails to not corrupt the cache.\n */\nwebserver_2.get(\"/f/:ref\", (req: express.Request, res: express.Response) => {\n    const referrer: string = req.params.ref;\n    const uid: string = req.cookies.uid;\n    if (!Profile.has(uid) || !Webserver.hasRoute(referrer))\n        return res.status(404), res.end();\n    const profile: Profile = Profile.get(uid);\n    if (profile._isReading()) {\n        profile._visitRoute(referrer);\n        console.info(`supercookie | Favicon requested by uid='${uid}' • Read `, Webserver.getIndexByRoute(referrer), \"•\", \n            Array.from(profile.visited).map(route => Webserver.getIndexByRoute(route)));\n        return; // res.type(\"gif\"), res.status(404), res.end();\n    }\n    if (!profile.vector.includes(referrer)) {\n        console.info(`supercookie | Favicon requested by uid='${uid}' • Write`, Webserver.getIndexByRoute(referrer), \"•\", \n            Array.from(profile.vector).map(route => Webserver.getIndexByRoute(route)));\n        return; // res.type(\"gif\"), res.status(404), res.end();\n    }\n    const data = Buffer.from(FILE, \"base64\");\n    res.writeHead(200, {\n        \"Cache-Control\": \"public, max-age=31536000\",\n        \"Expires\": new Date(Date.now() + 31536000000).toUTCString(),\n        \"Content-Type\": \"image/png\",\n        \"Content-Length\": data.length\n    });\n    res.end(data);\n});\n\nwebserver_1.use(\"/assets\", express.static(path.join(path.resolve(), \"www/assets\"), { index: false }));\nwebserver_2.use(\"/assets\", express.static(path.join(path.resolve(), \"www/assets\"), { index: false }));\nwebserver_1.get('/', (_req: express.Request, res: express.Response) => {\n    Webserver.sendFile(res, path.join(path.resolve(), \"www/index.html\"), {\n        url_demo: WEBSERVER_DOMAIN_2\n    });\n});\nwebserver_1.get(\"/favicon.ico\", (_req: express.Request, res: express.Response) => {\n    res.sendFile(path.join(path.resolve(), \"www/favicon.ico\"));\n});\nwebserver_2.get(\"/favicon.ico\", (_req: express.Request, res: express.Response) => {\n    res.sendFile(path.join(path.resolve(), \"www/favicon.ico\"));\n});\nwebserver_1.get(\"/workwise\", (_req: express.Request, res: express.Response) => {\n    Webserver.sendFile(res, path.join(path.resolve(), \"www/workwise.html\"), {\n        url_main: WEBSERVER_DOMAIN_1\n    });\n});\nwebserver_1.get(\"/api\", (_req: express.Request, res: express.Response) => {\n    res.type(\"json\");\n    res.status(200);\n    res.send({\n        index: STORAGE.index,\n        cache: STORAGE.cacheID,\n        bits: Math.floor(Math.log2(STORAGE.index ?? 1)) + 1,\n        N: N,\n        maxN: maxN\n    });\n});\nwebserver_1.get('*', (_req: express.Request, res: express.Response) => {\n    res.redirect('/');\n});\nwebserver_2.get('*', (req: express.Request, res: express.Response) => {\n    Webserver.sendFile(res, path.join(path.resolve(), \"www/404.html\"), {\n        path: decodeURIComponent(req.path),\n        url_main: WEBSERVER_DOMAIN_1\n    });\n});\n\nwebserver_1.listen(WEBSERVER_PORT_1, () => \n    console.info(`express-web | Webserver-1 for '${WEBSERVER_DOMAIN_1}' running on port:`, WEBSERVER_PORT_1));\nwebserver_2.listen(WEBSERVER_PORT_2, () => \n    console.info(`express-web | Webserver-2 for '${WEBSERVER_DOMAIN_2}' running on port:`, WEBSERVER_PORT_2));\nSTORAGE.index = STORAGE.index ?? 1;\nSTORAGE.cacheID = CACHE_IDENTIFIER;"
  },
  {
    "path": "server/package.json",
    "content": "{\n  \"type\": \"module\",\n  \"dependencies\": {\n    \"@types/cookie-parser\": \"^1.4.2\",\n    \"@types/cors\": \"^2.8.9\",\n    \"@types/express\": \"^4.17.11\",\n    \"cookie-parser\": \"^1.4.5\",\n    \"cors\": \"^2.8.5\",\n    \"dotenv\": \"^8.2.0\",\n    \"express\": \"^4.17.1\"\n  }\n}\n"
  },
  {
    "path": "server/tsconfig.json",
    "content": "{\n    \"compilerOptions\": {\n        \"allowSyntheticDefaultImports\": true,\n        \"sourceMap\": false,\n        \"removeComments\": true,\n        \"target\": \"ESNext\",\n        \"module\": \"ESNext\",\n        \"moduleResolution\": \"Node\",\n        \"allowJs\": false,\n        \"experimentalDecorators\": true,\n        \"lib\": [\n            \"esnext.array\",\n            \"esnext\",\n            \"dom\"\n        ]\n    }\n  }"
  },
  {
    "path": "server/www/404.html",
    "content": "<!DOCTYPE html>\n<html>\n    <head>\n        <meta charset=\"utf-8\"/>\n        <title>supercookie • progress</title>\n        <meta name=\"author\" content=\"Jonas Strehle\"/>\n        <meta name=\"robots\" content=\"noindex\"/>\n        <meta name=\"viewport\" content=\"viewport-fit=cover, user-scalable=no, width=device-width, initial-scale=1, maximum-scale=1\">\n        <style>\n            html, body {\n                margin: 0px;\n                width: 100%;\n                height: 100%;\n                padding: 0px;\n                display: flex;\n                justify-content: center;\n                align-items: center;\n                flex-direction: column;\n                background-color: #202020;\n                color: rgb(158, 158, 158);\n                font-size: large;\n                font-family: sans-serif;\n                user-select: none;\n            }\n            img {\n                width: 100px;\n            }\n            img:hover {\n                transform: scale(1.01);\n            }\n            * {\n                transition: all 0.4s;\n            }\n            b {\n                color: orange;\n            }\n            .background {\n                position: absolute;\n                width: 100%;\n                height: 100%;\n                background-image: url(/assets/background.png);\n                opacity: 0.05;\n            }\n            .button {\n                width: 100px;\n                height: 40px;\n                text-align: center;\n                display: flex;\n                justify-content: center;\n                z-index: 2;\n                align-items: center;\n                color: white!important;\n                text-decoration: none!important;\n                border-radius: 57px;\n                background: linear-gradient(145deg, #1c1c1c, #212121);\n                box-shadow:  20px 20px 60px #1a1a1a,\n                            -20px -20px 60px #242424;\n            }\n            .button:hover {\n                transform: scale(0.95);\n            }\n            .button:active {\n                transform: scale(0.9);\n            }\n            .image-container {\n                z-index: 2;\n            }\n        </style>\n    </head>\n    \n    <body>\n        <div class=\"background\"></div>\n        <a class=\"image-container\" href=\"//{{url_main}}\">\n            <img src=\"/assets/404.png\">\n        </a>\n        <p>\n            <b>{{path}}</b> not found!\n        </p>\n        <a class=\"button\" href=\"//{{url_main}}\">\n            Back\n        </a>\n    </body>\n</html>"
  },
  {
    "path": "server/www/identity.html",
    "content": "<!DOCTYPE html>\n<html>\n    <head>\n        <meta charset=\"utf-8\"/>\n        <title>id • {{hash}}</title>\n        <meta name=\"author\" content=\"Jonas Strehle\"/>\n        <meta name=\"robots\" content=\"noindex\"/>\n        <meta name=\"viewport\" content=\"viewport-fit=cover, user-scalable=no, width=device-width, initial-scale=1, maximum-scale=1\">\n        <link href=\"https://fonts.googleapis.com/icon?family=Material+Icons\" rel=\"stylesheet\">\n        <link href=\"https://fonts.googleapis.com/css?family=Muli:400,700&display=swap\" rel=\"stylesheet dns-prefetch preconnect\" crossorigin=\"\">\n        <link rel=\"shortcut icon\" href=\"/favicon.ico\" type=\"image/x-icon\"/>\n        <style>\n            html, body {\n                margin: 0px;\n                width: 100%;\n                height: 100%;\n                padding: 0px;\n                display: flex;\n                justify-content: center;\n                align-items: center;\n                background-color: rgb(32, 32, 32);\n                color: white;\n                font-size: large;\n                font-family: Muli, sans-serif;\n            }\n            .main {\n                position: absolute;\n                z-index: 1;\n                width: 88%;\n                display: flex;\n                left: 0;\n                right: 0;\n                margin: auto;\n                justify-content: center;\n                align-items: center;\n                flex-direction: column;\n                border-radius: 50px;\n                background: #202020;\n                box-shadow: 11px 11px 22px #1b1b1b, -11px -11px 22px #252525;\n                padding: 2%;\n            }\n            .identifier-container div {\n                font-weight: bold;\n                text-align: center;\n            }\n            .identifier-container a {\n                color: #ff0358;\n                font-weight: normal;\n                white-space: nowrap;\n            }\n            .identifier-container img {\n                width: 100%;\n                min-width: 50%;\n            }\n            .button-container {\n                width: 80%;\n                margin-top: 20px;\n                display: flex;\n                justify-content: center;\n                flex-wrap: wrap;\n                align-items: center;\n            }\n            .button-container > .button {\n                width: 150px;\n                height: 60px;\n                margin: 5px;\n                background-color: #73c53c;\n                display: flex;\n                justify-content: center;\n                align-items: center;\n                border-radius: 10px;\n                font-size: medium;\n                color: white!important;\n                text-decoration: none!important;\n                text-align: center;\n                display: flex;\n                transition: all 0.3s;\n            }\n            .button-container > .button.blue {\n                background-color: #20aee4;\n                font-weight: bold;\n            }\n            .button-container > .button.red {\n                background-color: #de3f37\n            }\n            .button:hover {\n                opacity: 0.8;\n                transform: scale(0.95);\n            }\n            .button:active {\n                opacity: 0.5;\n                transform: scale(1.01);\n            }\n            * {\n                -webkit-user-select: none;\n                -moz-user-select: none;\n                -ms-user-select: none;\n                user-select: none;\n            }\n            .button > i {\n                width: 25%;\n                height: 100%;\n                display: flex;\n                justify-content: center;\n                align-items: center;\n                background-color: rgba(0, 0, 0, 0.1);\n            }\n            .button > div {\n                width: 75%;\n            }\n            .identifier-hash {\n                -webkit-user-select: text;\n                -moz-user-select: text;\n                -ms-user-select: text;\n                user-select: text;\n            }\n            .identifier-hash:hover {\n                transform: scale(1.1);\n            }\n            .speechbubble {\n                position: relative;\n                max-width: 30em;\n                background-color: #292929;\n                padding: 1.125em 1.5em;\n                font-size: 1.25em;\n                border-radius: 1rem;\n                box-shadow:\t0 0.125rem 0.5rem rgba(0, 0, 0, .3), 0 0.0625rem 0.125rem rgba(0, 0, 0, .2);\n            }\n            .speechbubble::before {\n                content: '';\n                position: absolute;\n                width: 0;\n                height: 0;\n                bottom: 100%;\n                left: 2.5em;\n                border: .75rem solid transparent;\n                border-top: none;\n                border-bottom-color: #292929;\n                filter: drop-shadow(0 -0.0625rem 0.0625rem rgba(0, 0, 0, .1));\n            }\n            .background {\n                position: absolute;\n                width: 100%;\n                height: 100%;\n                background-image: url(/assets/background.png);\n                opacity: 0.05;\n            }\n            small {\n                color: white;\n                font-size: x-small;\n            }\n        </style>\n    </head>\n    \n    <body>\n        <div class=\"background\"></div>\n        <div class=\"main\">\n            <div class=\"identifier-container\">\n                <div>\n                    <a href=\"//{{url_main}}\" target=\"_blank\">\n                        <img src=\"/assets/header.png\">\n                    </a>\n                    <p class=\"speechbubble\">\n                        Your ID: <a class=\"identifier-hash\" onclick=\"window.getSelection().selectAllChildren(this); document.execCommand('copy');\">{{hash}}</a> <small>({{identifier}})</small>\n                    </p>\n                </div>\n            </div>\n            <div class=\"button-container\">\n                <a class=\"button\" href=\"/\">\n                    <i class=\"material-icons\">cached</i>\n                    <div>Try Again</div>\n                </a>\n                <a class=\"button blue\" target=\"_blank\" href=\"//{{url_workwise}}\" id=\"workwise\">\n                    <i class=\"material-icons\">help</i>\n                    <div>How does it work?</div>\n                </a>\n                <a class=\"button red\" target=\"_blank\" href=\"https://github.com/jonasstrehle/supercookie\">\n                    <i class=\"material-icons\">source</i>\n                    <div>Get Source-Code</div>\n                </a>\n            </div>\n        </div>\n    </body>\n</html>"
  },
  {
    "path": "server/www/index.html",
    "content": "<!DOCTYPE html>\n<html>\n    <head>\n        <meta charset=\"utf-8\"/>\n        <title>supercookie • welcome</title>\n        <meta name=\"description\" content=\"Favicons as supercookies! Check out this cache-based fingerprinting method in our demonstration.\"/>\n        <meta name=\"keywords\" content=\"JavaScript, Web, Fingerprinting, Favicons, Cache, Anonym, Security, Browser\"/>\n        <meta name=\"author\" content=\"Jonas Strehle\"/>\n        <meta name=\"viewport\" content=\"viewport-fit=cover, user-scalable=no, width=device-width, initial-scale=1, maximum-scale=1\">\n        <link rel=\"shortcut icon\" href=\"/favicon.ico\" type=\"image/x-icon\"/>\n        <link href=\"https://fonts.googleapis.com/icon?family=Material+Icons+Outlined\" rel=\"stylesheet\">\n        <link href=\"https://fonts.googleapis.com/css?family=Muli:400,700&display=swap\" rel=\"stylesheet dns-prefetch preconnect\" crossorigin=\"\">\n        <script async defer src=\"https://buttons.github.io/buttons.js\"></script>\n        <style>\n            html, body {\n                margin: 0px;\n                width: 100%;\n                height: 100%;\n                padding: 0px;\n                display: flex;\n                justify-content: center;\n                align-items: center;\n                background-color: rgb(32, 32, 32);\n                color: white;\n                font-size: large;\n                font-family: Muli, sans-serif;\n            }\n            .main {\n                position: absolute;\n                z-index: 1;\n                width: 90%;\n                display: flex;\n                left: 0;\n                right: 0;\n                margin: auto;\n                justify-content: center;\n                align-items: center;\n                flex-direction: column;\n            }\n            h1 {\n                text-align: center;\n            }\n            h1 b {\n                color: orange;\n            }\n            .background {\n                position: absolute;\n                width: 100%;\n                height: 100%;\n                background-image: url(/assets/background.png);\n                opacity: 0.05;\n            }\n            .action-button {\n                text-decoration: none!important;\n                color: white!important;\n                display: flex;\n                justify-content: center;\n                align-items: center;\n                border-radius: 50px;\n                background: #202020;\n                box-shadow: 20px 20px 60px #0e0e0e, -20px -20px 60px #323232;\n                padding: 20px;\n            }\n            .action-button i {\n                color: orange!important;\n            }\n            .action-button:hover, .action-button:active {\n                opacity: 0.8;\n                color: orange!important;\n                box-shadow: 20px 20px 80px #0e0e0e, -20px -20px 80px #323232;\n            }\n            .action-button:hover > i, .action-button:active > i {\n                opacity: 0.8;\n                transform: scale(1.2);\n                color: white!important;\n            } \n            * {\n                transition: all 0.4s;\n            }\n            .action-button i {\n                margin-right: 8px;\n                color: orange;\n            }\n            .tag-container {\n                position: absolute;\n                top: 10px;\n                left: 10px;\n                display: flex;\n                justify-content: center;\n                align-items: center;\n                flex-direction: column;\n            }\n            .tag {\n                margin: 3px;\n                width: 40px;\n                height: 40px;\n                display: flex;\n                justify-content: center;\n                align-items: center;\n                border-radius: 20px;\n                background: #1a1a1a;\n            }\n            .tag a {\n                text-decoration: none!important;\n                color: #656565!important;\n                display: flex;\n                justify-content: center;\n                align-items: center;\n            }\n            .tag:hover, .tag:active {\n                transform: scale(0.92);\n            }\n            .tag:hover a .tag:active a {\n                color: rgba(255, 255, 255, 0.6)!important;\n            }\n            .tag > img {\n                width: 90%;\n            }\n\n            .footer {\n                position: absolute;\n                bottom: 30px;\n                left: 0;\n                right: 0;\n                margin: auto;\n                width: 90%;\n                display: flex;\n                justify-content: space-between;\n                align-items: center;\n                flex-direction: row;\n            }\n            @media only screen and (max-width: 767px), only screen and (max-device-width: 767px) {\n                .footer {\n                    display: none;\n                }\n            }\n            .bmc-btn {\n                height: 50px!important;\n            }\n            .footer > div {\n                display: flex;\n                flex-direction: row;\n                align-items: center;\n                justify-content: center;\n            }\n            .footer > div > div {\n                margin-right: 18px;\n            }\n            span {\n                display: flex;\n                justify-content: center;\n                align-items: center;\n            }\n            .github-button {\n                color: white!important;\n                text-decoration: none!important;\n            }\n        </style>\n    </head>\n    \n    <body>\n\n        <div class=\"background\"></div>\n        <div class=\"tag-container\">\n            <div class=\"tag\">\n                <a href=\"/workwise\">\n                    <span class=\"material-icons-outlined\">help</span>\n                </a>\n            </div>\n            <div class=\"tag\">\n                <a href=\"https://jonas.strehles.info\">\n                    <span class=\"material-icons-outlined\">add</span>\n                </a>\n            </div>\n        </div>\n\n\n        <div class=\"main\">\n            <h1>Browser-Fingerprinting<br>via <b>Favicon</b></h1>\n            \n            <a class=\"action-button\" href=\"//{{url_demo}}\"><i class=\"material-icons-outlined\">visibility</i>To the Demo!</a>\n        </div>\n\n        <div class=\"footer\">\n            <script type=\"text/javascript\" src=\"https://cdnjs.buymeacoffee.com/1.0.0/button.prod.min.js\" data-name=\"bmc-button\" data-slug=\"jonasstrehle\" data-color=\"#f4a00a\" data-emoji=\"\" data-font=\"Cookie\" data-text=\"Buy me a cookie\" data-outline-color=\"#000000\" data-font-color=\"#000000\" data-coffee-color=\"#ffffff\" ></script>\n            <div>\n                <div>This project is now <b>Open-Source</b>!</div>\n                <a class=\"github-button\" href=\"https://github.com/jonasstrehle/supercookie\" data-icon=\"octicon-star\" data-size=\"large\" data-show-count=\"true\" aria-label=\"Star jonasstrehle/supercookie on GitHub\">Star on GitHub</a>\n            </div>\n        </div>\n    </body>\n</html>"
  },
  {
    "path": "server/www/launch.html",
    "content": "<!DOCTYPE html>\n<html>\n    <head>\n        <meta charset=\"utf-8\"/>\n        <title>supercookie</title>\n        <meta name=\"author\" content=\"Jonas Strehle\"/>\n        <meta name=\"robots\" content=\"noindex\"/>\n        <meta name=\"viewport\" content=\"viewport-fit=cover, user-scalable=no, width=device-width, initial-scale=1, maximum-scale=1\">\n        <link rel=\"shortcut icon\" href=\"/l/{{favicon}}\" type=\"image/x-icon\"/>\n        <style>\n            html, body {\n                margin: 0px;\n                width: 100%;\n                height: 100%;\n                padding: 0px;\n                display: flex;\n                justify-content: center;\n                align-items: center;\n                background-color: rgb(32, 32, 32);\n                color: white;\n                font-size: large;\n                font-family: sans-serif;\n            }\n        </style>\n    </head>\n    \n    <body>\n        <h1>...</h1>\n        <script type=\"module\">\n            window.onload = async () => {\n                await new Promise((resolve) => setTimeout(resolve, 500));\n                const mid = (document.cookie.match(new RegExp(`(^| )mid=([^;]+)`)) || [])[2];\n                const route = !!mid ? `/write/${mid}` : \"/read\";\n                document.cookie.split(\";\").forEach((c) => document.cookie = c.replace(/^ +/, \"\").replace(/=.*/, `=;expires=${new Date().toUTCString()};path=/`));\n                window.location.href = route;\n            }\n        </script>\n    </body>\n</html>"
  },
  {
    "path": "server/www/redirect.html",
    "content": "<!DOCTYPE html>\n<html>\n    <head>\n        <meta charset=\"utf-8\"/>\n        <title>supercookie • progress</title>\n        <meta name=\"author\" content=\"Jonas Strehle\"/>\n        <meta name=\"robots\" content=\"noindex\"/>\n        <meta name=\"viewport\" content=\"viewport-fit=cover, user-scalable=no, width=device-width, initial-scale=1, maximum-scale=1\">\n        <style>\n            html, body {\n                margin: 0px;\n                width: 100%;\n                height: 100%;\n                padding: 0px;\n                display: flex;\n                justify-content: center;\n                align-items: center;\n                background-color: rgb(32, 32, 32);\n                color: orange;\n                font-size: large;\n                font-family: sans-serif;\n            }\n        </style>\n    </head>\n    \n    <body>\n        <h1>...</h1>\n        <script type=\"module\">\n            window.onload = () =>\n                setTimeout(() => window.location.href = \"//{{url_demo}}\", 3_000);\n        </script>\n    </body>\n</html>"
  },
  {
    "path": "server/www/referrer-v2.html",
    "content": "<!DOCTYPE html>\n<html>\n    <head>\n        <meta charset=\"utf-8\"/>\n        <title>supercookie • {{index}}</title>\n        <meta name=\"author\" content=\"Jonas Strehle\"/>\n        <meta name=\"robots\" content=\"noindex\"/>\n        <meta name=\"viewport\" content=\"viewport-fit=cover, user-scalable=no, width=device-width, initial-scale=1, maximum-scale=1\">\n        <style>\n            html, body {\n                margin: 0px;\n                width: 100%;\n                height: 100%;\n                padding: 0px;\n                display: flex;\n                justify-content: center;\n                align-items: center;\n                background-color: rgb(32, 32, 32);\n                color: #ff0358;\n                font-size: large;\n                font-family: sans-serif;\n            }\n        </style>\n    </head>\n    \n    <body>\n        <h1>{{index}}</h1>\n        <script type=\"module\">\n            console.info(`{{index}} • bit v2`, JSON.parse(\"{{bit}}\"));\n            if (JSON.parse(\"{{bit}}\"))\n                document.location.href = `${document.location.origin}/{{referrer}}`;\n            const favicon = document.createElement('link');\n            favicon.rel = \"icon preload\";\n            favicon.as = \"image\";\n            favicon.type = \"image/x-icon\";\n            favicon.onload = favicon.onerror = () => \n                document.location.href = `${document.location.origin}/{{referrer}}`;\n            document.head.appendChild(favicon);\n            globalThis.f = favicon;\n            favicon.href = `//${window.location.host}/f/{{favicon}}`;\n        </script>\n    </body>\n</html>"
  },
  {
    "path": "server/www/referrer.html",
    "content": "<!DOCTYPE html>\n<html>\n    <head>\n        <meta charset=\"utf-8\"/>\n        <title>supercookie • {{index}}</title>\n        <meta name=\"author\" content=\"Jonas Strehle\"/>\n        <meta name=\"robots\" content=\"noindex\"/>\n        <meta name=\"viewport\" content=\"viewport-fit=cover, user-scalable=no, width=device-width, initial-scale=1, maximum-scale=1\">\n        <link rel=\"shortcut icon\" href=\"/f/{{favicon}}\" type=\"image/x-icon\"/>\n        <style>\n            html, body {\n                margin: 0px;\n                width: 100%;\n                height: 100%;\n                padding: 0px;\n                display: flex;\n                justify-content: center;\n                align-items: center;\n                background-color: rgb(32, 32, 32);\n                color: #ff0358;\n                font-size: large;\n                font-family: sans-serif;\n            }\n        </style>\n    </head>\n    \n    <body>\n        <h1>{{index}}</h1>\n        <script>\n            console.info(`{{index}} • bit v1`, JSON.parse(\"{{bit}}\"));\n\n            window.addEventListener(\"DOMContentLoaded\", () => {\n                setTimeout(()=>{\n                    document.location.href = `${document.location.origin}/{{referrer}}`;\n                }, +\"{{delay}}\");\n            });\n        </script>\n    </body>\n</html>\n"
  },
  {
    "path": "server/www/tsconfig.json",
    "content": "{\n    \"compilerOptions\": {\n        \"sourceMap\": false,\n        \"removeComments\": true,\n        \"target\": \"ESNext\",\n        \"module\": \"ESNext\",\n        \"allowJs\": false,\n        \"experimentalDecorators\": true,\n        \"lib\": [\n            \"esnext.array\",\n            \"esnext\",\n            \"dom\"\n        ]\n    }\n  }"
  },
  {
    "path": "server/www/workwise.html",
    "content": "<!DOCTYPE html>\n<html>\n    <head>\n        <meta charset=\"utf-8\"/>\n        <title>supercookie • workwise</title>\n        <meta name=\"description\" content=\"Favicons as supercookies! Check out this cache-based fingerprinting method in our demonstration.\"/>\n        <meta name=\"keywords\" content=\"JavaScript, Web, Fingerprinting, Favicons, Cache, Anonym, Security, Browser\"/>\n        <meta name=\"author\" content=\"Jonas Strehle\"/>\n        <meta name=\"robots\" content=\"noindex\"/>\n        <meta name=\"viewport\" content=\"viewport-fit=cover, user-scalable=no, width=device-width, initial-scale=1, maximum-scale=1\">\n        <link href=\"https://fonts.googleapis.com/icon?family=Material+Icons+Outlined\" rel=\"stylesheet\">\n        <link href=\"https://fonts.googleapis.com/css?family=Muli:400,700&display=swap\" rel=\"stylesheet dns-prefetch preconnect\" crossorigin=\"\">\n        <link rel=\"shortcut icon\" href=\"/favicon.ico\" type=\"image/x-icon\"/>\n        <style>\n            html, body {\n                margin: 0px;\n                width: 100%;\n                height: 100%;\n                padding: 0px;\n                display: flex;\n                justify-content: center;\n                align-items: center;\n                background-color: #151d32;\n                color: white;\n                font-size: large;\n                font-family: Muli, sans-serif;\n            }\n            .main {\n                width: 100%;\n                display: flex;\n                left: 0;\n                right: 0;\n                margin: auto;\n                justify-content: center;\n                align-items: center;\n                flex-direction: column;\n                padding: 3%;\n                position: relative;\n                overflow: hidden;\n            }\n\n            .background {\n                width: 100%;\n                height: 100%;\n                position: absolute;\n                top: 0;\n                background-image: url(/assets/background.png);\n                opacity: 0.05;\n            }\n            .markdown-body {\n                z-index: 2;\n                width: 98%;\n            }\n            .diagram-image {\n                width: 90%;\n                margin-top: 20px;\n                background-color: #151D32!important;\n                border-radius: 40px;\n                border-radius: 28px;\n                padding: 20px;\n                box-shadow:  12px 12px 24px #0d111e,\n                            -12px -12px 24px #1d2946;\n            }\n        </style>\n\n        <style>\n            .markdown-body {\n                -ms-text-size-adjust: 100%;\n                -webkit-text-size-adjust: 100%;\n                line-height: 1.5;\n                font-size: 16px;\n                line-height: 1.5;\n                word-wrap: break-word;\n                color: white;\n            }\n            .markdown-body .octicon {\n                display: inline-block;\n                fill: currentColor;\n                vertical-align: text-bottom;\n            }\n            .markdown-body .anchor {\n                float: left;\n                line-height: 1;\n                margin-left: -20px;\n                padding-right: 4px;\n            }\n            .markdown-body .anchor:focus {\n                outline: none;\n            }\n            .markdown-body h1 .octicon-link,\n            .markdown-body h2 .octicon-link,\n            .markdown-body h3 .octicon-link,\n            .markdown-body h4 .octicon-link,\n            .markdown-body h5 .octicon-link,\n            .markdown-body h6 .octicon-link {\n                color: #1b1f23;\n                vertical-align: middle;\n                visibility: hidden;\n            }\n            .markdown-body h1:hover .anchor,\n            .markdown-body h2:hover .anchor,\n            .markdown-body h3:hover .anchor,\n            .markdown-body h4:hover .anchor,\n            .markdown-body h5:hover .anchor,\n            .markdown-body h6:hover .anchor {\n                text-decoration: none;\n            }\n            \n            .markdown-body h1:hover .anchor .octicon-link,\n            .markdown-body h2:hover .anchor .octicon-link,\n            .markdown-body h3:hover .anchor .octicon-link,\n            .markdown-body h4:hover .anchor .octicon-link,\n            .markdown-body h5:hover .anchor .octicon-link,\n            .markdown-body h6:hover .anchor .octicon-link {\n                visibility: visible;\n            }\n            \n            .markdown-body h1:hover .anchor .octicon-link:before,\n            .markdown-body h2:hover .anchor .octicon-link:before,\n            .markdown-body h3:hover .anchor .octicon-link:before,\n            .markdown-body h4:hover .anchor .octicon-link:before,\n            .markdown-body h5:hover .anchor .octicon-link:before,\n            .markdown-body h6:hover .anchor .octicon-link:before {\n                width: 16px;\n                height: 16px;\n                content: ' ';\n                display: inline-block;\n                background-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' version='1.1' width='16' height='16' aria-hidden='true'%3E%3Cpath fill='white' fill-rule='evenodd' d='M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z'%3E%3C/path%3E%3C/svg%3E\");\n            }\n            .markdown-body details {\n                display: block;\n            }\n            .markdown-body summary {\n                display: list-item;\n            }\n            .markdown-body a {\n                background-color: initial;\n            }\n            .markdown-body a:active,\n            .markdown-body a:hover {\n                outline-width: 0;\n            }\n            .markdown-body strong {\n                font-weight: inherit;\n                font-weight: bolder;\n            }\n            .markdown-body h1 {\n                font-size: 2em;\n                margin: .67em 0;\n            }\n            .markdown-body img {\n                border-style: none;\n            }\n            .markdown-body code,\n            .markdown-body kbd,\n            .markdown-body pre {\n                font-family: monospace,monospace;\n                font-size: 1em;\n            }\n            .markdown-body hr {\n                box-sizing: initial;\n                height: 0;\n                overflow: visible;\n            }\n            .markdown-body input {\n                font: inherit;\n                margin: 0;\n            }\n            .markdown-body input {\n                overflow: visible;\n            }\n            .markdown-body [type=checkbox] {\n                box-sizing: border-box;\n                padding: 0;\n            }\n            .markdown-body * {\n                box-sizing: border-box;\n            }\n            .markdown-body input {\n                font-family: inherit;\n                font-size: inherit;\n                line-height: inherit;\n            }\n            .markdown-body a {\n                color: #0366d6;\n                text-decoration: none;\n            }\n            .markdown-body a:hover {\n                text-decoration: underline;\n            }\n            .markdown-body strong {\n                font-weight: 600;\n            }\n            .markdown-body hr {\n                height: 0;\n                margin: 15px 0;\n                overflow: hidden;\n                background: transparent;\n                border: 0;\n                border-bottom: 1px solid #dfe2e5;\n            }\n            .markdown-body hr:after,\n            .markdown-body hr:before {\n                display: table;\n                content: \"\";\n            }\n            .markdown-body hr:after {\n                clear: both;\n            }\n            .markdown-body table {\n                border-spacing: 0;\n                border-collapse: collapse;\n            }\n            .markdown-body td,\n            .markdown-body th {\n                padding: 0;\n            }\n            .markdown-body details summary {\n                cursor: pointer;\n            }\n            .markdown-body kbd {\n                display: inline-block;\n                padding: 3px 5px;\n                font: 11px SFMono-Regular,Consolas,Liberation Mono,Menlo,monospace;\n                line-height: 10px;\n                color: #444d56;\n                vertical-align: middle;\n                background-color: #fafbfc;\n                border: 1px solid #d1d5da;\n                border-radius: 3px;\n                box-shadow: inset 0 -1px 0 #d1d5da;\n            }\n            .markdown-body h1,\n            .markdown-body h2,\n            .markdown-body h3,\n            .markdown-body h4,\n            .markdown-body h5,\n            .markdown-body h6 {\n                margin-top: 0;\n                margin-bottom: 0;\n            }\n            .markdown-body h1 {\n                font-size: 32px;\n            }\n            .markdown-body h1,\n            .markdown-body h2 {\n                font-weight: 600;\n            }\n            .markdown-body h2 {\n                font-size: 24px;\n            }\n            .markdown-body h3 {\n                font-size: 20px;\n            }\n            .markdown-body h3,\n            .markdown-body h4 {\n                font-weight: 600;\n            }\n            .markdown-body h4 {\n                font-size: 16px;\n            }\n            .markdown-body h5 {\n                font-size: 14px;\n            }\n            .markdown-body h5,\n            .markdown-body h6 {\n                font-weight: 600;\n            }\n            .markdown-body h6 {\n                font-size: 12px;\n            }\n            .markdown-body p {\n                margin-top: 0;\n                margin-bottom: 10px;\n            }\n            .markdown-body blockquote {\n                margin: 0;\n            }\n            .markdown-body ol,\n            .markdown-body ul {\n                padding-left: 0;\n                margin-top: 0;\n                margin-bottom: 0;\n            }\n            .markdown-body ol ol,\n            .markdown-body ul ol {\n                list-style-type: lower-roman;\n            }\n            .markdown-body ol ol ol,\n            .markdown-body ol ul ol,\n            .markdown-body ul ol ol,\n            .markdown-body ul ul ol {\n                list-style-type: lower-alpha;\n            }\n            .markdown-body dd {\n                margin-left: 0;\n            }\n            .markdown-body code,\n            .markdown-body pre {\n                font-family: SFMono-Regular,Consolas,Liberation Mono,Menlo,monospace;\n                font-size: 12px;\n            }\n            .markdown-body pre {\n                margin-top: 0;\n                margin-bottom: 0;\n            }\n            .markdown-body input::-webkit-inner-spin-button,\n            .markdown-body input::-webkit-outer-spin-button {\n                margin: 0;\n                -webkit-appearance: none;\n                appearance: none;\n            }\n            .markdown-body :checked+.radio-label {\n                position: relative;\n                z-index: 1;\n                border-color: #0366d6;\n            }\n            .markdown-body .border {\n                border: 1px solid #e1e4e8!important;\n            }\n            .markdown-body .border-0 {\n                border: 0!important;\n            }\n            .markdown-body .border-bottom {\n                border-bottom: 1px solid #e1e4e8!important;\n            }\n            .markdown-body .rounded-1 {\n                border-radius: 3px!important;\n            }\n            .markdown-body .bg-white {\n                background-color: #fff!important;\n            }\n            .markdown-body .bg-gray-light {\n                background-color: #fafbfc!important;\n            }\n            .markdown-body .mb-0 {\n                margin-bottom: 0!important;\n            }\n            .markdown-body .my-2 {\n                margin-top: 8px!important;\n                margin-bottom: 8px!important;\n            }\n            .markdown-body .pl-0 {\n                padding-left: 0!important;\n            }\n            .markdown-body .py-0 {\n                padding-top: 0!important;\n                padding-bottom: 0!important;\n            }\n            .markdown-body .pl-1 {\n                padding-left: 4px!important;\n            }\n            .markdown-body .pl-2 {\n                padding-left: 8px!important;\n            }\n            .markdown-body .py-2 {\n                padding-top: 8px!important;\n                padding-bottom: 8px!important;\n            }\n            .markdown-body .pl-3,\n            .markdown-body .px-3 {\n                padding-left: 16px!important;\n            }\n            .markdown-body .px-3 {\n                padding-right: 16px!important;\n            }\n            .markdown-body .pl-4 {\n                padding-left: 24px!important;\n            }\n            .markdown-body .pl-5 {\n                padding-left: 32px!important;\n            }\n            .markdown-body .pl-6 {\n                padding-left: 40px!important;\n            }\n            .markdown-body .f6 {\n                font-size: 12px!important;\n            }\n            .markdown-body .lh-condensed {\n                line-height: 1.25!important;\n            }\n            .markdown-body .text-bold {\n                font-weight: 600!important;\n            }\n            .markdown-body .pl-c {\n                color: #6a737d;\n            }\n            .markdown-body .pl-c1,\n            .markdown-body .pl-s .pl-v {\n                color: #005cc5;\n            }\n            .markdown-body .pl-e,\n            .markdown-body .pl-en {\n                color: #6f42c1;\n            }\n            .markdown-body .pl-s .pl-s1,\n            .markdown-body .pl-smi {\n                color: #24292e;\n            }\n            .markdown-body .pl-ent {\n                color: #22863a;\n            }\n            .markdown-body .pl-k {\n                color: #d73a49;\n            }\n            .markdown-body .pl-pds,\n            .markdown-body .pl-s,\n            .markdown-body .pl-s .pl-pse .pl-s1,\n            .markdown-body .pl-sr,\n            .markdown-body .pl-sr .pl-cce,\n            .markdown-body .pl-sr .pl-sra,\n            .markdown-body .pl-sr .pl-sre {\n                color: #e9c730;\n            }\n            .markdown-body .pl-smw,\n            .markdown-body .pl-v {\n                color: #e36209;\n            }\n            .markdown-body .pl-bu {\n                color: #b31d28;\n            }\n            .markdown-body .pl-ii {\n                color: #fafbfc;\n                background-color: #b31d28;\n            }\n            .markdown-body .pl-c2 {\n                color: #fafbfc;\n                background-color: #d73a49;\n            }\n            .markdown-body .pl-c2:before {\n                content: \"^M\";\n            }\n            .markdown-body .pl-sr .pl-cce {\n                font-weight: 700;\n                color: #22863a;\n            }\n            .markdown-body .pl-ml {\n                color: #735c0f;\n            }\n            .markdown-body .pl-mh,\n            .markdown-body .pl-mh .pl-en,\n            .markdown-body .pl-ms {\n                font-weight: 700;\n                color: #005cc5;\n            }\n            .markdown-body .pl-mi {\n                font-style: italic;\n                color: #24292e;\n            }\n            .markdown-body .pl-mb {\n                font-weight: 700;\n                color: #62686e;\n            }\n            .markdown-body .pl-md {\n                color: #b31d28;\n                background-color: #ffeef0;\n            }\n            .markdown-body .pl-mi1 {\n                color: #22863a;\n                background-color: #f0fff4;\n            }\n            .markdown-body .pl-mc {\n                color: #e36209;\n                background-color: #ffebda;\n            }\n            .markdown-body .pl-mi2 {\n                color: #f6f8fa;\n                background-color: #005cc5;\n            }\n            .markdown-body .pl-mdr {\n                font-weight: 700;\n                color: #6f42c1;\n            }\n            .markdown-body .pl-ba {\n                color: #586069;\n            }\n            .markdown-body .pl-sg {\n                color: #959da5;\n            }\n            .markdown-body .pl-corl {\n                text-decoration: underline;\n                color: #032f62;\n            }\n            .markdown-body .pl {\n                color: white;\n            }\n            .markdown-body .mb-0 {\n                margin-bottom: 0!important;\n            }\n            .markdown-body .my-2 {\n                margin-bottom: 8px!important;\n            }\n            .markdown-body .my-2 {\n                margin-top: 8px!important;\n            }\n            .markdown-body .pl-0 {\n                padding-left: 0!important;\n            }\n            .markdown-body .py-0 {\n                padding-top: 0!important;\n                padding-bottom: 0!important;\n            }\n            .markdown-body .pl-1 {\n                padding-left: 4px!important;\n            }\n            .markdown-body .pl-2 {\n                padding-left: 8px!important;\n            }\n            .markdown-body .py-2 {\n                padding-top: 8px!important;\n                padding-bottom: 8px!important;\n            }\n            .markdown-body .pl-3 {\n                padding-left: 16px!important;\n            }\n            .markdown-body .pl-4 {\n                padding-left: 24px!important;\n            }\n            .markdown-body .pl-5 {\n                padding-left: 32px!important;\n            }\n            .markdown-body .pl-6 {\n                padding-left: 40px!important;\n            }\n            .markdown-body .pl-7 {\n                padding-left: 48px!important;\n            }\n            .markdown-body .pl-8 {\n                padding-left: 64px!important;\n            }\n            .markdown-body .pl-9 {\n                padding-left: 80px!important;\n            }\n            .markdown-body .pl-10 {\n                padding-left: 96px!important;\n            }\n            .markdown-body .pl-11 {\n                padding-left: 112px!important;\n            }\n            .markdown-body .pl-12 {\n                padding-left: 128px!important;\n            }\n            .markdown-body hr {\n                border-bottom-color: #eee;\n            }\n            .markdown-body kbd {\n                display: inline-block;\n                padding: 3px 5px;\n                font: 11px SFMono-Regular, Consolas, Liberation Mono, Menlo, monospace;\n                line-height: 10px;\n                color: #444d56;\n                vertical-align: middle;\n                background-color: #fafbfc;\n                border: 1px solid #d1d5da;\n                border-radius: 3px;\n                box-shadow: inset 0 -1px 0 #d1d5da;\n            }\n            .markdown-body:after,\n            .markdown-body:before {\n                display: table;\n                content: \"\";\n            }\n            .markdown-body:after {\n                clear: both;\n            }\n            .markdown-body>:first-child {\n                margin-top: 0!important;\n            }\n            .markdown-body>:last-child {\n                margin-bottom: 0!important;\n            }\n            .markdown-body a:not([href]) {\n                color: inherit;\n                text-decoration: none;\n            }\n            .markdown-body blockquote,\n            .markdown-body details,\n            .markdown-body dl,\n            .markdown-body ol,\n            .markdown-body p,\n            .markdown-body pre,\n            .markdown-body table,\n            .markdown-body ul {\n                margin-top: 0;\n                margin-bottom: 16px;\n            }\n            .markdown-body hr {\n                height: .25em;\n                padding: 0;\n                margin: 24px 0;\n                background-color: #e1e4e8;\n                border: 0;\n            }\n            .markdown-body blockquote {\n                padding: 0 1em;\n                color: #bdbdbd;\n                border-left: .25em solid #dfe2e5;\n            }\n            .markdown-body blockquote>:first-child {\n                margin-top: 0;\n            }\n            .markdown-body blockquote>:last-child {\n                margin-bottom: 0;\n            }\n            .markdown-body h1,\n            .markdown-body h2,\n            .markdown-body h3,\n            .markdown-body h4,\n            .markdown-body h5,\n            .markdown-body h6 {\n                margin-top: 24px;\n                margin-bottom: 16px;\n                font-weight: 600;\n                line-height: 1.25;\n            }\n            .markdown-body h1 {\n                font-size: 2em;\n            }\n            .markdown-body h1,\n            .markdown-body h2 {\n                padding-bottom: .3em;\n                border-bottom: 1px solid #eaecef;\n            }\n            .markdown-body h2 {\n                font-size: 1.5em;\n            }\n            .markdown-body h3 {\n                font-size: 1.25em;\n            }\n            .markdown-body h4 {\n                font-size: 1em;\n            }\n            .markdown-body h5 {\n                font-size: .875em;\n            }\n            .markdown-body h6 {\n                font-size: .85em;\n            }\n            .markdown-body ol,\n            .markdown-body ul {\n                padding-left: 2em;\n            }\n            .markdown-body ol ol,\n            .markdown-body ol ul,\n            .markdown-body ul ol,\n            .markdown-body ul ul {\n                margin-top: 0;\n                margin-bottom: 0;\n            }\n            .markdown-body li {\n                word-wrap: break-all;\n            }\n            .markdown-body li>p {\n                margin-top: 16px;\n            }\n            .markdown-body li+li {\n                margin-top: .25em;\n            }\n            .markdown-body dl {\n                padding: 0;\n            }\n            .markdown-body dl dt {\n                padding: 0;\n                margin-top: 16px;\n                font-size: 1em;\n                font-style: italic;\n                font-weight: 600;\n            }\n            .markdown-body dl dd {\n                padding: 0 16px;\n                margin-bottom: 16px;\n            }\n            .markdown-body table {\n                display: block;\n                width: 100%;\n                overflow: auto;\n            }\n            .markdown-body table th {\n                font-weight: 600;\n            }\n            .markdown-body table td,\n            .markdown-body table th {\n                padding: 6px 13px;\n                border: 1px solid #070911;\n            }\n            .markdown-body table tr {\n                background-color: #0d1221;\n                border-top: 1px solid #c6cbd1;\n            }\n            .markdown-body table tr:nth-child(2n) {\n                background-color: #141c33;\n            }\n            .markdown-body img {\n                max-width: 100%;\n                box-sizing: initial;\n                background-color: #fff;\n            }\n            .markdown-body img[align=right] {\n                padding-left: 20px;\n            }\n            .markdown-body img[align=left] {\n                padding-right: 20px;\n            }\n            .markdown-body code {\n                padding: .2em .4em;\n                margin: 0;\n                font-size: 85%;\n                background-color: rgba(27, 31, 35, .05);\n                border-radius: 3px;\n            }\n            .markdown-body pre {\n                word-wrap: normal;\n            }\n            .markdown-body pre>code {\n                padding: 0;\n                margin: 0;\n                font-size: 100%;\n                word-break: normal;\n                white-space: pre;\n                background: transparent;\n                border: 0;\n            }\n            .markdown-body .highlight {\n                margin-bottom: 16px;\n            }\n            .markdown-body .highlight pre {\n                margin-bottom: 0;\n                word-break: normal;\n            }\n            .markdown-body .highlight pre,\n            .markdown-body pre {\n                padding: 16px;\n                overflow: auto;\n                font-size: 85%;\n                line-height: 1.45;\n                background-color: #0d1221;\n                border-radius: 3px;\n            }\n            .markdown-body pre code {\n                display: inline;\n                max-width: auto;\n                padding: 0;\n                margin: 0;\n                overflow: visible;\n                line-height: inherit;\n                word-wrap: normal;\n                background-color: initial;\n                border: 0;\n            }\n            .markdown-body .commit-tease-sha {\n                display: inline-block;\n                font-family: SFMono-Regular, Consolas, Liberation Mono, Menlo, monospace;\n                font-size: 90%;\n                color: #444d56;\n            }\n            .markdown-body .full-commit .btn-outline:not(:disabled):hover {\n                color: #005cc5;\n                border-color: #005cc5;\n            }\n            .markdown-body .blob-wrapper {\n                overflow-x: auto;\n                overflow-y: hidden;\n            }\n            .markdown-body .blob-wrapper-embedded {\n                max-height: 240px;\n                overflow-y: auto;\n            }\n            .markdown-body .blob-num {\n                width: 1%;\n                min-width: 50px;\n                padding-right: 10px;\n                padding-left: 10px;\n                font-family: SFMono-Regular, Consolas, Liberation Mono, Menlo, monospace;\n                font-size: 12px;\n                line-height: 20px;\n                color: rgba(27, 31, 35, .3);\n                text-align: right;\n                white-space: nowrap;\n                vertical-align: top;\n                cursor: pointer;\n                -webkit-user-select: none;\n                -moz-user-select: none;\n                -ms-user-select: none;\n                user-select: none;\n            }\n            .markdown-body .blob-num:hover {\n                color: rgba(27, 31, 35, .6);\n            }\n            .markdown-body .blob-num:before {\n                content: attr(data-line-number);\n            }\n            .markdown-body .blob-code {\n                position: relative;\n                padding-right: 10px;\n                padding-left: 10px;\n                line-height: 20px;\n                vertical-align: top;\n            }\n            .markdown-body .blob-code-inner {\n                overflow: visible;\n                font-family: SFMono-Regular, Consolas, Liberation Mono, Menlo, monospace;\n                font-size: 12px;\n                color: #24292e;\n                word-wrap: normal;\n                white-space: pre;\n            }\n            .markdown-body .pl-token.active,\n            .markdown-body .pl-token:hover {\n                cursor: pointer;\n                background: #ffea7f;\n            }\n            .markdown-body .tab-size[data-tab-size=\"1\"] {\n                -moz-tab-size: 1;\n                tab-size: 1;\n            }\n            .markdown-body .tab-size[data-tab-size=\"2\"] {\n                -moz-tab-size: 2;\n                tab-size: 2;\n            }\n            .markdown-body .tab-size[data-tab-size=\"3\"] {\n                -moz-tab-size: 3;\n                tab-size: 3;\n            }\n            .markdown-body .tab-size[data-tab-size=\"4\"] {\n                -moz-tab-size: 4;\n                tab-size: 4;\n            }\n            .markdown-body .tab-size[data-tab-size=\"5\"] {\n                -moz-tab-size: 5;\n                tab-size: 5;\n            }\n            .markdown-body .tab-size[data-tab-size=\"6\"] {\n                -moz-tab-size: 6;\n                tab-size: 6;\n            }\n            .markdown-body .tab-size[data-tab-size=\"7\"] {\n                -moz-tab-size: 7;\n                tab-size: 7;\n            }\n            .markdown-body .tab-size[data-tab-size=\"8\"] {\n                -moz-tab-size: 8;\n                tab-size: 8;\n            }\n            .markdown-body .tab-size[data-tab-size=\"9\"] {\n                -moz-tab-size: 9;\n                tab-size: 9;\n            }\n            .markdown-body .tab-size[data-tab-size=\"10\"] {\n                -moz-tab-size: 10;\n                tab-size: 10;\n            }\n            .markdown-body .tab-size[data-tab-size=\"11\"] {\n                -moz-tab-size: 11;\n                tab-size: 11;\n            }\n            .markdown-body .tab-size[data-tab-size=\"12\"] {\n                -moz-tab-size: 12;\n                tab-size: 12;\n            }\n            .markdown-body .task-list-item {\n                list-style-type: none;\n            }\n            .markdown-body .task-list-item+.task-list-item {\n                margin-top: 3px;\n            }\n            .markdown-body .task-list-item input {\n                margin: 0 .2em .25em -1.6em;\n                vertical-align: middle;\n            }\n\n\n            .tag {\n                margin: 3px;\n                width: 40px;\n                height: 40px;\n                display: flex;\n                justify-content: center;\n                align-items: center;\n                border-radius: 20px;\n                background: #101625;\n                position: absolute;\n                right: 10px;\n                top: 10px;\n                transition: all 0.3s;\n                z-index: 3;\n            }\n            .tag a {\n                text-decoration: none!important;\n                color: #656565!important;\n                display: flex;\n                justify-content: center;\n                align-items: center;\n            }\n            .tag:hover {\n                transform: scale(0.92);\n            }\n            .tag:hover a {\n                color: rgba(255, 255, 255, 0.6)!important;\n            }\n            .tag > img {\n                width: 90%;\n            }\n\n        </style>\n    </head>\n    \n    <body>\n        <div class=\"main\">\n            <div class=\"tag\">\n                <a href=\"//{{url_main}}\">\n                    <span class=\"material-icons-outlined\">home</span>\n                </a>\n            </div>\n            <div class=\"background\"></div>\n            <article class=\"markdown-body\">\n                <h1>\n                    <a class=\"anchor\" href=\"#\" aria-hidden=\"true\">\n                        <span class=\"octicon octicon-link\"></span>\n                    </a>\n                    Workwise • <i>supercookie</i> \n                </h1>\n                \n                <div>📚 Please have a look at this <b>elaboration</b> from University of Illinois:\n                    <s><a href=\"https://www.cs.uic.edu/~polakis/papers/solomos-ndss21.pdf\" target=\"_blank\">www.cs.uic.edu</a></s>\n                </div>\n\n                <h3>\n                    <a id=\"content-introduction\" class=\"anchor\" href=\"#content-introduction\" aria-hidden=\"true\">\n                        <span class=\"octicon octicon-link\"></span>\n                    </a>\n                    Introduction\n                </h3>\n                \n                <blockquote>\n                    <p>\n                        <strong>Data is the new gold!</strong><br>\n                    </p>\n                </blockquote>\n\n                \n                Browsers are the most widespread access medium that makes it incredibly easy for us humans to connect to the <a href=\"https://en.wikipedia.org/wiki/World_Wide_Web\" target=\"_blank\">Word Wide Web</a>.<br>\n                Due to the constant development of the Internet, such as the continuous elaboration of new standards and features, the introduction of powerful APIs and further interfaces on the browser side, the possibilities for collecting and analyzing data have also significantly expanded over the last few decades!\n                <br>\n                <br>\n                First and foremost, there is nothing wrong with collecting data at all. All of us collect data, whether unconsciously in private everyday life or completely consciously in school or at work - collecting data, interpreting it and drawing conclusions is actually incredibly important!\n                <br>\n                <br>\n                With the launch of the WWW for the public and the development of the first online services, data collection also started to become interesting for the various website providers, according to the motto <i>if I own a website, I also want to know who is surfing it</i>.\n                <br>\n                However, in most cases we as consumers only want to disclose as little as possible and only the data necessary for the intended service - in fact, <i>my private data is no one else's business</i>.\n                <br>\n                <br>\n                The above-mentioned further development of the WWW's capabilities has allowed data to be assigned to individual profiles, enabling the recognition of unique users and the ability to trace their browsing activities even across different pages - the so called <a href=\"https://en.wikipedia.org/wiki/Device_fingerprint\" target=\"_blank\">device fingerprinting</a>.\n                <br>\n                Some known methods for assigning a unique fingerprint to browsers are <a href=\"https://en.wikipedia.org/wiki/Device_fingerprint#Hardware_benchmarking\" target=\"_blank\">hardware benchmarking</a>, <a href=\"https://en.wikipedia.org/wiki/Device_fingerprint#Canvas_and_WebGL\" target=\"_blank\">fingerprinting via Canvas and WebGL</a> or <a href=\"https://en.wikipedia.org/wiki/Device_fingerprint#Browser_extensions\" target=\"_blank\">analysis of active browser extensions</a>. \n                <br><b>This article is about a less known way to achieve something similar!</b>\n                <br>\n                \n                <h3>\n                    <a id=\"content-background\" class=\"anchor\" href=\"#content-background\" aria-hidden=\"true\">\n                        <span class=\"octicon octicon-link\"></span>\n                    </a>\n                    Background\n                </h3>\n\n\n                Modern browsers offer a wide range of features to improve and simplify the user experience.<br>\n                One of these features are the so-called favicons: A <a href=\"https://en.wikipedia.org/wiki/Favicon\" target=\"_blank\">favicon</a> is a small (usually 16×16 or 32×32 pixels) logo used by web browsers to brand a website in a recognizable way. \n                Favicons are usually shown by most browsers in the address bar and next to the page's name in a list of bookmarks.\n\n                <br><br>\n                To serve a favicon on their website, a developer has to include an <a href=\"https://developer.mozilla.org/en-US/docs/Web/HTML/Element/link\" target=\"_blank\">&lt;link rel&gt;</a> attribute in the webpage’s header.\n                If this tag does exist, the browser requests the icon from the predefined source and if the server response contains an valid icon file that can be properly rendered this icon is displayed by the browser. In any other case, a blank favicon is shown.\n                <br><br>\n                <div class=\"highlight highlight-source-js\">\n                    <pre><span class=\"pl-ent\">&lt;link </span><span class=\"pl-c1\">rel</span><span class=\"pl-mb\">=</span><span class=\"pl-k\">\"icon\" </span><span class=\"pl-c1\">href</span><span class=\"pl-mb\">=</span><span class=\"pl-k\">\"/favicon.ico\"</span> </span><span class=\"pl-c1\">type</span><span class=\"pl-mb\">=</span><span class=\"pl-k\">\"image/x-icon\"</span><span class=\"pl-ent\">&gt;</span></pre>\n                </div>\n\n                The favicons must be made very easily accessible by the browser. Therefore, they are cached in a separate local database on the system, called the favicon cache (F-Cache).\n                A F-Cache data entries includes the visited URL (subdomain, domain, route, URL paramter), the favicon ID and the time to live (TTL). \n                <br>While this provides web developers the ability to delineate parts of their website using a wide variety of icons for individual routes and subdomains, it also leads to a possible <b>tracking scenario</b>.\n                <br>\n                <br>\n                    When a user visits a website, the browser checks if a favicon is needed by looking up the source of the shortcut icon link reference of the requested webpage.<br>\n                    The browser initialy checks the local F-cache for an entry containing the URL of the active website. If a favicon entry exists, the icon will be loaded from the cache and then displayed. \n                    However, if there is no entry, for example because no favicon has ever been loaded under this particular domain, or the data in the cache is out of date, the browser makes a GET request to the server to load the site's favicon.\n                <br>\n                \n                <h3>\n                    <a id=\"content-threat-model\" class=\"anchor\" href=\"#content-threat-model\" aria-hidden=\"true\">\n                        <span class=\"octicon octicon-link\"></span>\n                    </a>\n                    Threat Model\n                </h3>\n                In the article a possible threat model is explained that allows to assign a <b>unique identifier to each browser</b> in order to draw conclusions about the user and to be able to identify this user even in case of applied anti-fingerprint measures, such as the use of a <a href=\"https://en.wikipedia.org/wiki/Virtual_private_network\" target=\"_blank\">VPN</a>, deletion of <a href=\"https://en.wikipedia.org/wiki/HTTP_cookie\" target=\"_blank\">cookies</a>, deletion of the <a href=\"https://en.wikipedia.org/wiki/Web_cache\" target=\"_blank\">browser cache</a> or manipulation of the <a href=\"https://developer.mozilla.org/en-US/docs/Glossary/Request_header\" target=\"_blank\">client header information</a>.\n                <br><br>\n                A web server can draw conclusions about whether a browser has already loaded a favicon or not:<br>\n                So when the browser requests a web page, if the favicon is not in the local F-cache, another request for the favicon is made. If the icon already exists in the F-Cache, no further request is sent.<br>\n                By combining the state of delivered and not delivered favicons for specific URL paths for a browser, a unique pattern (identification number) can be assigned to the client.\n                <br>When the website is reloaded, the web server can reconstruct the identification number with the network requests sent by the client for the missing favicons and thus identify the browser.\n                <br>\n                <br>\n                <br>\n\n                <ol>\n                    <li>\n                        <b>Write</b> identification\n                        <p>\n                            The goal of the <b>write</b> operation is to generate a unique identifier and store it on the client side.<br>\n                            First step is to create a new N-bit ID on the server and translate it to a path vector as shown below.<br><br>\n                            <strong>Example:</strong>\n                            <div class=\"highlight highlight-source-js\">\n                                <pre><span class=\"pl-k\">const</span> N<span class=\"pl-k\"> = </span><span class=\"pl-s\">4</span>;<br><span class=\"pl-k\">const</span> ROUTES<span class=\"pl-k\"> = </span>[<span class=\"pl-s\"><span class=\"pl-pds\">\"</span>/a<span class=\"pl-pds\">\"</span></span>, <span class=\"pl-s\"><span class=\"pl-pds\">\"</span>/b<span class=\"pl-pds\">\"</span></span>, <span class=\"pl-s\"><span class=\"pl-pds\">\"</span>/c<span class=\"pl-pds\">\"</span></span>, <span class=\"pl-s\"><span class=\"pl-pds\">\"</span>/d<span class=\"pl-pds\">\"</span></span>];<br><span class=\"pl-k\">const</span> ID<span class=\"pl-k\"> = </span><span class=\"pl-ms\">generateNewID()</span>;<span class=\"pl-mb\"> // -> 1010 • (select unassigned decimal number, here ten: 10 -> 1010b in binary)</span></pre>\n                            </div>\n                            <div class=\"highlight highlight-source-js\">\n                                <pre><span class=\"pl-k\">const</span> vector<span class=\"pl-k\"> = </span><span class=\"pl-ms\">generateVectorFromID(<span class=\"pl\">ID</span>)</span>;<span class=\"pl-mb\"> // -> [\"/a\", \"/c\"] • (because [a, b, c, d] where [1, 0, 1, 0] is 1 -> a, c)</span></pre>\n                            </div>\n                        </p>\n                        Second step is to store the actual data inside the browser:<br>\n                        The user will be redirected along all of the website paths, starting at <i>/a</i>, navigating to <i>/b</i>, to <i>/c</i> and finally to <i>/d</i>.\n                        <br>\n                        <ul>\n                            <li>/a</li>\n                            <li>/b</li>\n                            <li>/c</li>\n                            <li>/d</li>\n                        </ul>\n                        <br>\n                       \n                        While the user is redirected on every load the browser requests a favicon for the respective route, going the same way like<i>/a/favicon.ico</i>, to <i>/b/favicon.ico</i>, to <i>/c/favicon.ico</i> and finally to <i>/d/favicon.ico</i>.\n                        <br>\n                        <ul>\n                            <li>/a/favicon.ico</li>\n                            <li>/b/favicon.ico</li>\n                            <li>/c/favicon.ico</li>\n                            <li>/d/favicon.ico</li>\n                        </ul>\n\n                        <br>\n\n                        The webserver will now only process those favicon requests whose path is present in the previously created path vector. If the route is present the webserver answers with the favicon file and <a href=\"https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/200\" target=\"_blank\">Status 200 OK</a>.<br>\n                        If the requested route is not in the path vector, the webserver aborts the request with an <a href=\"https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/404\" target=\"_blank\">Error 404 Not Found</a>, or sends no response.<br>\n                        Since the browser - as described earlier - only stores the delivered favicons in the F-Cache, we have now stored our unique identification number and the writing process is complete. <br><br>\n                        In the above example, the webserver only responds to requests for the favicons under paths <i>/a/favicon.ico</i> and <i>/c/favicon.ico</i>. The F-Cache only has favicons-entries for these two paths.<br>\n\n\n                        <a href=\"/assets/diagram-write.png\" target=\"_blank\">\n                            <img class=\"diagram-image\" src=\"/assets/diagram-write.svg\">\n                        </a>\n\n                        <br>\n                        \n                    </li>\n                    <br>\n                    <br>\n                    <li>\n                        <b>Read</b> identification\n                        <p>\n                            Here the goal is to re-identify a returning user based on his existing F-Cache entries.<br><br>\n                            In read mode the server always responds to favicon requests with an <a href=\"https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/404\" target=\"_blank\">Error 404 Not Found</a> status, but responds normally to all other requests. \n                            This preserves the <b>integrity of the cached favicons</b> during the read operation, since no new F-cache entry is created by the browser.<br>\n                            To reconstruct a visitor's identifier, the browser must be routed through all available routes. The server records which favions are requested by the browser (those that are not present in the browsers F-cache) and which are not.\n                            <br>\n                            <br>\n                            <strong>Example:</strong>\n                            <div class=\"highlight highlight-source-js\">\n                                <pre><span class=\"pl-k\">const</span> visitedRoutes<span class=\"pl-k\"> = </span><span class=\"pl\">[]</span>;<br><span class=\"pl-k\">Webserver.onvisit</span><span class=\"pl-k\"> = <span class=\"pl\">(route)</span> => </span><span class=\"pl-s\"><span class=\"pl\">visitedRoutes.<span class=\"pl-s\">push(</span></span></span>route<span class=\"pl-s\">)</span>; <span class=\"pl-mb\"> // -> [\"/b\", \"/d\"]</span><br><span class=\"pl-k\">Webserver.ondone</span><span class=\"pl-k\"> = <span class=\"pl\">()</span> => </span><span class=\"pl-s\"><span class=\"pl\">{ <span class=\"pl-k\">const</span> ID = <span class=\"pl-ms\">getIDFromVector(</span></span></span>visitedRoutes<span class=\"pl-ms\">)</span> };<span class=\"pl-mb\"> // -> 10 • (because \"/a\" and \"/b\" are missing -> 1010b)</span></pre>\n                            </div>\n                            The server can thus reconstruct the identification from the missing favicon requests and the reading process is complete.\n                            <br>\n                        </p>\n\n                        <a href=\"/assets/diagram-read.png\" target=\"_blank\">\n                            <img class=\"diagram-image\" src=\"/assets/diagram-read.svg\">\n                        </a>\n                        <br>\n                    </li>\n                </ol>\n                \n                <h3>\n                    <a id=\"content-target\" class=\"anchor\" href=\"#content-target\" aria-hidden=\"true\">\n                        <span class=\"octicon octicon-link\"></span>\n                    </a>\n                    Target\n                </h3>\n\n                It looks like all top browsers are vulnerable to this attack scenario. <br>Mobile browsers are also affected.<br><br>\n                <table>\n                    <thead>\n                      <tr>\n                        <th align=\"center\"><p>Browser</p></th>\n                        <th align=\"center\"><p>Windows</p></th>\n                        <th align=\"center\"><p>MacOS</p></th>\n                        <th align=\"center\"><p>Linux</p></th>\n                        <th align=\"center\"><p>iOS</p></th>\n                        <th align=\"center\"><p>Android</p></th>\n                        <th align=\"center\"><i>Info</i></th>\n                      </tr>\n                    </thead>\n                    <tbody>\n                      <tr>\n                          <td>Chrome <em>(v 87.0)</em></td>\n                          <td align=\"center\">✅</td>\n                          <td align=\"center\">✅</td>\n                          <td align=\"center\">✅</td>\n                          <td align=\"center\">✅</td>\n                          <td align=\"center\">✅</td>\n                          <td>-</td>\n                      </tr>\n                      <tr>\n                          <td>Safari <em>(v 14.0)</em></td>\n                          <td align=\"center\">-</td>\n                          <td align=\"center\">✅</td>\n                          <td align=\"center\">-</td>\n                          <td align=\"center\">✅</td>\n                          <td align=\"center\">-</td>\n                          <td>-</td>\n                      </tr>\n                      <tr>\n                          <td>Edge <em>(v 87.0)</em></td>\n                          <td align=\"center\">✅</td>\n                          <td align=\"center\">✅</td>\n                          <td align=\"center\">❌</td>\n                          <td align=\"center\">❌</td>\n                          <td align=\"center\">✅</td>\n                          <td>-</td>\n                      </tr>\n                      <tr>\n                          <td>Firefox <em>(v 85.0)</em></td>\n                          <td align=\"center\">✅</td>\n                          <td align=\"center\">✅</td>\n                          <td align=\"center\">❌</td>\n                          <td align=\"center\">❌</td>\n                          <td align=\"center\">❌</td>\n                          <td>Fingerprint different in incognito mode</td>\n                      </tr>\n                      <tr>\n                          <td>Brave <em>(v 1.19.92)</em></td>\n                          <td align=\"center\">❌</td>\n                          <td align=\"center\">❌</td>\n                          <td align=\"center\">❌</td>\n                          <td align=\"center\">❔</td>\n                          <td align=\"center\">❌</td>\n                          <td>-</td>\n                      </tr>\n                    </tbody>\n                </table>\n                <table>\n                    <thead>\n                      <tr>\n                        <th align=\"center\"><p>Browser</p></th>\n                        <th align=\"center\"><p>Windows</p></th>\n                        <th align=\"center\"><p>MacOS</p></th>\n                        <th align=\"center\"><p>Linux</p></th>\n                        <th align=\"center\"><p>iOS</p></th>\n                        <th align=\"center\"><p>Android</p></th>\n                        <th align=\"center\"><i>Info</i></th>\n                      </tr>\n                    </thead>\n                    <tbody>\n                      <tr>\n                        <td><b>Brave</b> (v 1.14.0)</td>\n                        <td align=\"center\">✅</td>\n                        <td align=\"center\">✅</td>\n                        <td align=\"center\">✅</td>\n                        <td align=\"center\">✅</td>\n                        <td align=\"center\">✅</td>\n                        <td>-</td>\n                      </tr>\n                      <tr>\n                        <td><b>Firefox</b> (&lt; v 84.0)</td>\n                        <td align=\"center\">✅</td>\n                        <td align=\"center\">✅</td>\n                        <td align=\"center\">❔</td>\n                        <td align=\"center\">❌</td>\n                        <td align=\"center\">✅</td>\n                        <td>-</td>\n                      </tr>\n                    </tbody>\n                </table>\n\n                <br><br>\n\n                The demonstration also impressively shows that applying anti-tracking software, adblockers, VPN or surfing in incognito mode does not offer any significant improvement and the browser remains vulnerable to the tracking even with these measures:<br><br>\n                <table>\n                    <thead>\n                        <tr>\n                            <th>Browser</th>\n                            <th align=\"center\">Incognito / Private mode</th>\n                            <th align=\"center\">Clear Website Data</th>\n                            <th align=\"center\">VPN</th>\n                            <th align=\"center\">Adblock / Anti-Tracking</th>\n                        </tr>\n                    </thead>\n                    <tbody>\n                        <tr>\n                            <td>Chrome</td>\n                            <td align=\"center\">✅</td>\n                            <td align=\"center\">✅</td>\n                            <td align=\"center\">✅</td>\n                            <td align=\"center\">✅</td>\n                        </tr>\n                        <tr>\n                            <td>Safari</td>\n                            <td align=\"center\">✅</td>\n                            <td align=\"center\">✅</td>\n                            <td align=\"center\">✅</td>\n                            <td align=\"center\">✅</td>\n                        </tr>\n                        <tr>\n                            <td>Edge</td>\n                            <td align=\"center\">✅</td>\n                            <td align=\"center\">✅</td>\n                            <td align=\"center\">✅</td>\n                            <td align=\"center\">✅</td>\n                        </tr>\n                        <tr>\n                            <td>Firefox</td>\n                            <td align=\"center\">✅</td>\n                            <td align=\"center\">✅</td>\n                            <td align=\"center\">✅</td>\n                            <td align=\"center\">✅</td>\n                        </tr>\n                    </tbody>\n                </table>\n\n\n                <h3>\n                    <a id=\"content-scalability-performance\" class=\"anchor\" href=\"#content-scalability-performance\" aria-hidden=\"true\">\n                        <span class=\"octicon octicon-link\"></span>\n                    </a>\n                    Scalability & Performance\n                </h3>\n\n                By varying the number of bits that corresponds to the number of redirects to subpaths, this attack can be scaled almost arbitrarily.<br>\n                It can distinguish 2^N unique users, where N is the number of redirects on the client side.<br>\n                \n                <br>\n\n                Since each subpath redirection increases the duration of the identification, the performance of the attack the webserver can dynamically increase the number of redirects. This is done trivially by appending a new subpath in the sequence of subpaths.\n                <br>The calculation of the number of redirects (N) is done by the operation: \"<i>floor(log2(id))+1</i>\", where id corresponds to the decimal identification number.\n                <br>\n                For example, if the server changes from 3-bit identifiers to 4-bit identifiers, the subpath vector will change from [\"/a\", \"/b\", \"/c\"] to [\"/a\", \"/b\", \"/c\", \"/d\"] and\n                the identifier of a client (here dec. 6) changes from \"011\" to \"0110\" without changing the actual value of already written F-Cache identifiers. \n                <br><br>This leads to the fact that only the minimum number of redirections is necessary for the attack.\n\n                <a href=\"/assets/diagram-scalability.png\" target=\"_blank\">\n                    <img class=\"diagram-image\" src=\"/assets/diagram-scalability.svg\">\n                </a>\n\n                <br>\n                <br>\n                \n                \n                The time taken for the read and write operation increases as the number of distinguishable clients and redirects does.<br><br>\n                <i>The following measured times prove to be the minimum time required for this attack to work. The actual time required in practice depends on many more factors, such as Internet speed, location, hardware setup and browser type.</i>\n                <br><br>\n                <table>\n                    <thead>\n                        <tr>\n                            <th>Redirects<br>(N bit)</th>\n                            <th>distinguishable clients</th>\n                            <th>write time</th>\n                            <th>read time</th>\n                            <th>scale information</th>\n                        </tr>\n                    </thead>\n                    <tbody>\n                        <tr>\n                            <td align=\"center\">2</em></td>\n                            <td align=\"center\">4</td>\n                            <td>&lt; 300<small>ms</small></td>\n                            <td>&lt; 300<small>ms</small></td>\n                            <td>One user with four browsers</td>\n                        </tr>\n                        <tr>\n                            <td align=\"center\">3</em></td>\n                            <td align=\"center\">8</td>\n                            <td>&lt; 300<small>ms</small></td>\n                            <td>~ 300<small>ms</small></td>\n                            <td>About the amount of Kardashians</td>\n                        </tr>\n                        <tr>\n                            <td align=\"center\">4</em></td>\n                            <td align=\"center\">16</td>\n                            <td>&lt; 1<small>s</small></td>\n                            <td>~ 1<small>s</small></td>\n                            <td>Bunch of your neighbors</td>\n                        </tr>\n                        <tr>\n                            <td align=\"center\">8</em></td>\n                            <td align=\"center\">256</td>\n                            <td>&lt; 1<small>s</small></td>\n                            <td>~ 1<small>s</small></td>\n                            <td>All your facebook-friends</td>\n                        </tr>\n                        <tr>\n                            <td align=\"center\">10</em></td>\n                            <td align=\"center\">1024</td>\n                            <td>&lt; 1.2<small>s</small></td>\n                            <td>~ 1<small>s</small></td>\n                            <td>Really small village</td>\n                        </tr>\n                        <tr>\n                            <td align=\"center\">20</em></td>\n                            <td align=\"center\">1,048,576</td>\n                            <td>&lt; 1.8<small>s</small></td>\n                            <td>&lt; 1.5<small>s</small></td>\n                            <td>Small city (San Jose, California)</td>\n                        </tr>\n                        <tr>\n                            <td align=\"center\">24</em></td>\n                            <td align=\"center\">16,777,216</td>\n                            <td>&lt; 2.4<small>s</small></td>\n                            <td>&lt; 2<small>s</small></td>\n                            <td>Whole Netherlands</td>\n                        </tr>\n                        <tr>\n                            <td align=\"center\">32</em></td>\n                            <td align=\"center\">4,294,967,296</td>\n                            <td>~ 3<small>s</small></td>\n                            <td>&lt; 3<small>s</small></td>\n                            <td>All people with internet access</td>\n                        </tr>\n                        <tr>\n                            <td align=\"center\">34</em></td>\n                            <td align=\"center\">17,179,869,184</td>\n                            <td>~ 4<small>s</small></td>\n                            <td>~ 4<small>s</small></td>\n                            <td>All people with internet access each using 4 different browsers</td>\n                        </tr>\n                    </tbody>\n                </table>\n\n\n                <h3>\n                    <a id=\"content-related-work\" class=\"anchor\" href=\"#content-related-work\" aria-hidden=\"true\">\n                        <span class=\"octicon octicon-link\"></span>\n                    </a>\n                    Related work\n                </h3>\n\n                <ul>\n                    <li>\n                        <s><a target=\"_blank\" href=\"https://www.cs.uic.edu/~polakis/papers/solomos-ndss21.pdf\">cs.uic.edu</a></s>:\n                        Study by Scientists at the University of Illinois, Chicago\n                    </li>\n                    <li>\n                        <a target=\"_blank\" href=\"https://heise.de/-5027814\">heise.de</a>:\n                        Browser-Fingerprinting: Favicons als \"Super-Cookies\"\n                    </li>\n                </ul>\n\n                <hr>\n            </article>\n        </div>\n    </body>\n</html>\n"
  }
]