Compare commits

...

24 Commits

Author SHA1 Message Date
Arkadiusz Wieczorek ea70d26a38 Update 'README.md' 2023-09-26 23:05:42 +02:00
Kuba Orlik 1680026bc4 Typos 2023-06-21 17:14:40 +02:00
Kuba Orlik d5a8172759 Fix typos 2023-06-21 17:14:07 +02:00
Kuba Orlik 039698264a Bardziej podejrzane domeny dawaj na początku w toolbarze 2023-03-22 15:51:07 +01:00
Arkadiusz Wieczorek 7cf1b95461 Update 'README.md' 2023-02-17 10:37:40 +01:00
Kuba Orlik 799f17eac8 Add mozilla addons link 2023-02-14 20:34:21 +01:00
Kuba Orlik 5c96a7f4cb Bump version 2022-09-25 14:04:51 +02:00
Kuba Orlik 32107f0ebc Dodanie explainerów dot. cookies na podstawie komentarzy, od których
odwołał się WSA w uzasadnieniu wyroku w sprawie z iSecure
2022-09-25 14:03:06 +02:00
Kuba Orlik 3910b5c67e Update version 2022-09-12 11:44:29 +02:00
Arkadiusz Wieczorek b2487c0511 Merge pull request 'Fix sometimes not being able to uncheck a domain - Fixes #92' (#101) from fix-checkbox into develop
Reviewed-on: #101
2022-09-12 09:48:04 +02:00
Arkadiusz Wieczorek 1106e86b41 Merge pull request 'Problem: nieznany cel. Dodanie narzędzi diagnostycznych' (#99) from unknown-purpose into develop
Reviewed-on: #99
2022-09-12 09:47:52 +02:00
Arkadiusz Wieczorek c16e8f3d7c Merge pull request 'Poprawa zaznaczania kawałków danych - teraz nie trzeba klikać na' (#95) from #64 into develop
Reviewed-on: #95
2022-09-12 09:47:11 +02:00
Kuba Orlik aae1a7e970 HOTFIX: nie działały URL-e, które mają '#' 2022-09-02 22:07:04 +02:00
Kuba Orlik 3a32456534 Fix $ typo 2022-09-01 07:47:16 +02:00
Kuba Orlik 67544a4bff Finish broken sentence 2022-08-31 21:23:35 +02:00
Kuba Orlik e3f4f8858d Fix rentgen being fooled with a "=" in cookie value (e.g. the NID cookie on google.com) 2022-08-31 09:50:37 +02:00
Kuba Orlik e3d6e6b83c Ikonka cookiesów przy tych domenach, które mają entriesy z cookiesami 2022-08-16 14:08:13 +02:00
Kuba Orlik cc713004b0 Fix sometimes not being able to uncheck a domain - Fixes #92 2022-08-14 12:55:21 +02:00
Kuba Orlik ea59a506f8 Problem: nieznany cel. Fixes #68 #97 2022-08-13 22:42:50 +02:00
Kuba Orlik 65e1ab2ecc Poprawa zaznaczania kawałków danych - teraz nie trzeba klikać na
checkbox, wystarczy klikać na labelkę
2022-07-31 16:05:48 +02:00
Kuba Orlik 5f29730613 Zmiana wordingu w problemie z administratorem. Wyświetlanie pytania o
konieczność w większej ilości przypadków
2022-07-31 16:01:01 +02:00
Arkadiusz Wieczorek 8fe2083d8d Update 'README.md' 2022-07-16 23:59:21 +02:00
Arkadiusz Wieczorek 45d87bc2a7 Bump version 2022-07-15 14:15:19 +02:00
Kuba Orlik 09dfe8d63a Change domain of screenshot service 2022-07-15 14:14:42 +02:00
27 changed files with 546 additions and 90 deletions

106
README.md
View File

@ -1,52 +1,5 @@
<h1 style="display: flex; align-items: center;"><img src="./assets/icon-addon-2048.png" alt="Rentgen logo" style="margin-right: 1rem;" width="48"/> Rentgen</h1>
<strong>Rentgen</strong> to wtyczka dla przeglądarek opartych o Firefoxa, która automatycznie wizualizuje, jakie dane zostały ~~wykradzione~~ wysłane do podmiotów trzecich przez odwiedzane strony.
**Funkcje Rentgena:**
- analiza ruchu sieciowego generowanego przez stronę internetową;
- wizualizacja danych przekazanych do podmiotów trzecich przez odwiedzaną stronę (historia przeglądania użytkownika oraz jego ciasteczka);
- przygotowywanie zrzutów ekranów narzędzi deweloperskich będących dowodem przekazanych danych do podmiotów trzecich;
- pomoc w oszacowaniu potencjalnych obszarów roboczych względem zgodności z RODO;
- generowanie raportu lub treści maila, którą można wysłać do administratora oraz Urzędu Ochrony Danych Osobowych.
## Jak zbudować i uruchomić Rentgena ze źródeł
### Wymagania wstępne
- System operacyjny: Linux x86_64
- Node.js: 16.x
- npm: 7.x lub wyższy
### Proces budowy
1. Pobierz repozytorium przez `git pull https://git.internet-czas-dzialac.pl/icd/rentgen.git` lub pobierz archwium zip
2. Przejdź do głównego katalogu pobranego repozytorium
3. Uruchom komendę: `npm install`
4. Uruchom komendę: `npm run build`
5. Uruchom komendę: `npm run create-package`
6. Przejdź do katalogu `web-ext-artifacts`
7. Znajdziesz tam archiwum zip: `rentgen-x-x-x.zip` (`x-x-x` oznaczają wersję wtyczki)
### Kroki do uruchomienia
1. Uruchom Firefoxa i przejdź do strony `about:debugging`
2. Kliknij zakładkę _This Firefox_
3. Kliknij przycisk _Load Temporary Add-on..._
4. Wybierz archiwum, które zbudowałeś w ostatnim kroku procesu budowy
## Zgłaszanie błędów
Link do issue trackera na naszej instancji Discourse: https://forum.internet-czas-dzialac.pl/c/rentgen-issue-tracker
Nie przyjmujemy zgłoszeń na platformie Microsoft Github.
Każdy problem zostanie sprawdzony i przeniesiony na wewnętrzną listę problemów na naszej instancji Gitea: https://git.internet-czas-dzialac.pl/icd/rentgen/issues. Korzystamy z Gitea i najprawdopodobniej w przyszłości dzięki federalizacji Gitea będziemy w stanie wpuścić użytkowników do zgłaszania błędów bezpośrednio ze strony Gitea.
---
## English description 🇬🇧
<strong>Rentgen</strong> is an add-on prepared for Firefox-based browsers. This extension will automatically visualize all the data that a given website ~~steals~~ sends to third parties.
Note: At the moment, we support Polish language because this extension generates mail content that is dedicated to Polish website owners. In further versions of this add-on, we will add other languages as well.
@ -59,6 +12,10 @@ Note: At the moment, we support Polish language because this extension generates
- assisting in the evaluation of potential work areas for compliance with GDPR;
- generating a report or email content that can be sent to an administrator and Personal Data Protection Office in Poland.
## Installation
Firefox: https://addons.mozilla.org/en-US/firefox/addon/rentgen/
## How to build and run Rentgen on your own
### Pre-requirements
@ -86,7 +43,9 @@ Note: At the moment, we support Polish language because this extension generates
## Issue tracker
Link to issue tracker on our Discourse instance: https://forum.internet-czas-dzialac.pl/c/rentgen-issue-tracker
If you find a problem, please send us an email: kontakt@internet-czas-dzialac.pl
We don't receive issues on Microsoft Github.
Each issue will be reviewed and moved to an internal issues list of our Gitea instance: https://git.internet-czas-dzialac.pl/icd/rentgen/issues. We use Gitea and most likely in the future with the federalization of Gitea, we will be able to let users in to report issues directly from the Gitea site.
@ -102,3 +61,54 @@ Each issue will be reviewed and moved to an internal issues list of our Gitea in
<img src="./assets/screenshots/5b.png" />
<img src="./assets/screenshots/2022-07-14_21-04.png" />
---
<strong>Rentgen</strong> to wtyczka dla przeglądarek opartych o Firefoxa, która automatycznie wizualizuje, jakie dane zostały ~~wykradzione~~ wysłane do podmiotów trzecich przez odwiedzane strony. Wtyczka obrazuje ilość skryptów śledzących na stronie internetowej i pomaga w sformułowaniu maila do administratora strony, który może być podstawą do skargi RODO w Urzędzie Ochrony Danych Osobowych.
**Funkcje Rentgena:**
- analiza ruchu sieciowego generowanego przez stronę internetową;
- wizualizacja danych przekazanych do podmiotów trzecich przez odwiedzaną stronę (historia przeglądania użytkownika oraz jego ciasteczka);
- przygotowywanie zrzutów ekranów narzędzi deweloperskich będących dowodem przekazanych danych do podmiotów trzecich;
- pomoc w oszacowaniu potencjalnych obszarów roboczych względem zgodności z RODO;
- generowanie raportu lub treści maila, którą można wysłać do administratora oraz Urzędu Ochrony Danych Osobowych.
## Instalacja
Firefox: https://addons.mozilla.org/pl/firefox/addon/rentgen/
## Jak zbudować i uruchomić Rentgena ze źródeł
### Wymagania wstępne
- System operacyjny: Linux x86_64
- Node.js: 16.x
- npm: 7.x lub wyższy
### Proces budowy
1. Pobierz repozytorium przez `git pull https://git.internet-czas-dzialac.pl/icd/rentgen.git` lub pobierz archwium zip
2. Przejdź do głównego katalogu pobranego repozytorium
3. Uruchom komendę: `npm install`
4. Uruchom komendę: `npm run build`
5. Uruchom komendę: `npm run create-package`
6. Przejdź do katalogu `web-ext-artifacts`
7. Znajdziesz tam archiwum zip: `rentgen-x-x-x.zip` (`x-x-x` oznaczają wersję wtyczki)
### Kroki do uruchomienia
1. Uruchom Firefoxa i przejdź do strony `about:debugging`
2. Kliknij zakładkę _This Firefox_
3. Kliknij przycisk _Load Temporary Add-on..._
4. Wybierz archiwum, które zbudowałeś w ostatnim kroku procesu budowy
## Zgłaszanie błędów
Jeżeli znajdziesz jakieś problem, napisz do nas maila: kontakt@internet-czas-dzialac.pl
Nie przyjmujemy zgłoszeń na platformie Microsoft Github.
Każdy problem zostanie sprawdzony i przeniesiony na wewnętrzną listę problemów na naszej instancji Gitea: https://git.internet-czas-dzialac.pl/icd/rentgen/issues. Korzystamy z Gitea i najprawdopodobniej w przyszłości dzięki federalizacji Gitea będziemy w stanie wpuścić użytkowników do zgłaszania błędów bezpośrednio ze strony Gitea.
---

View File

@ -5,6 +5,7 @@ import { Problem } from './problems/problem';
import { TransferOutsideEU } from './problems/transfer-outside-eu';
import { UnknownIdentity } from './problems/unknown-identity';
import { UnknownLegalBasis } from './problems/unknown-legal-basis';
import { UnknownPurposes } from './problems/unknown-purpose';
import { UnlawfulCookieAccess } from './problems/unlawful-cookies';
export default function deduceProblems(
@ -13,6 +14,7 @@ export default function deduceProblems(
): Problem[] {
return [
NoInformationAtAllProblem,
UnknownPurposes,
UnlawfulCookieAccess,
UnknownLegalBasis,
UnknownIdentity,

View File

@ -7,6 +7,8 @@ import './email-content.scss';
import { Fragment, useState } from 'react';
import emailIntro from './email-intro';
import { reportIntro } from './report-intro';
import { downloadText } from '../../util';
import { getFakeClusterData } from './fake-clusters';
const SS_URL = 'http://65.108.60.135:3000';
@ -136,6 +138,23 @@ export default function EmailContent({
</p>
</section>
) : null}
<div className="diag-toolbox">
<a
href="#"
onClick={() =>
downloadText(
'diag.json',
JSON.stringify({
answers,
fake_clusters_data: getFakeClusterData(clusters),
visited_url,
})
)
}
>
Pobierz plik diagnostyczny
</a>
</div>
</div>
</Fragment>
);

View File

@ -9,7 +9,49 @@ export const Explainers: Record<ExplainerKey, (zaimek_index: 0 | 1 | 2 | 3) => J
<p>
Sztucznie wygenerowane identyfikatory przechowywane w plikach Cookies stanowią dane
osobowe. Wskazuje na to wprost Art. 4. pkt 1. RODO, wymieniając identyfikator
internetowy i numer identyfikacyjny jako przykłady danych osobowych.
internetowy i numer identyfikacyjny jako przykłady danych osobowych. Losowe
przypisane identyfikatory mogą nie zawierać imienia i nazwiska osoby, której
dotyczą, ani nie prowadzić wprost do ich ustalenia, ale pozwalają odróżnić jedną,
daną konkretną osobę, od innych.
</p>
<p>
Por. komentarz z D. Lubasz [w:] Ochrona Danych Osobowych [red.] D. Lubasz, Warszawa
2020 r., str. 81:
</p>
<p>
<em>
Zidentyfikowaną osobą fizyczną jest osoba, której tożsamość jest ustalona -
bezpośrednio i natychmiast, czyli taka, którą bezpośrednio można wskazać,
wyodrębnić lub wyróżnić z określonej zbiorowości.{' '}
<strong>Nie musi to natomiast polegać na podaniu jej imienia nazwiska</strong>.
Konstatacja ta jest zwłaszcza istotna w środowisku cyfrowym, w którym
identyfikacja sprowadza się do oznaczenia danego użytkownika w celu wywierania
na niego określonego wpływu. (...) Możliwą do zidentyfikowania jest osoba,
której tożsamość dopiero administrator może ustalić -{' '}
<strong>niezależnie od tego, czy to zrobi, czy nie</strong>.
</em>
</p>
<p>
Podobnie za{' '}
<em>
P. Litwiński [w:] Rozporządzenie UE w sprawie ochrony osób fizycznych w związku
z przetwarzaniem danych osobowych i w sprawie swobodnego przepływu takich
danych, Komentarz [red.] P. Litwiński, Warszawa 2018 r.
</em>
:
</p>
<p>
<em>
Jak zwrócono uwagę w nauce prawa, identyfikacja osoby powinna być rozumiana jako
możliwość fizycznego wskazania tejże osoby, nie zaś jako ustalenie
podstawowych danych tej osoby (...). Analogicznie,{' '}
<em>identyfikacja osoby nie wymaga znajomości jej imienia lub nazwiska</em>,
wymaga natomiast znajomości pewnych unikalnych cech tej osoby, które odróżniają
od innych osób (...). W ten sam sposób należy więc rozumieć zwrot można
zidentyfikować - nie tylko jako możliwość odniesienia konkretnej informacji do
konkretnej osoby, lecz także jako możliwość wskazania tej osoby, rozumianego
jako faktyczne wyodrębnienie jej spośród innych osób.
</em>
</p>
</>
),

View File

@ -0,0 +1,56 @@
// good for diagnostic purposes
import { RequestCluster } from '../../request-cluster';
import { DataLocation } from '../../stolen-data-entry';
export type FakeRequestClusterData = {
id: string;
hasCookies: boolean;
hasMarkedCookies: boolean;
hasMarks: boolean;
exposesOriginWhere: DataLocation[];
exposesOrigin: boolean;
};
export function getFakeClusterData(
clusters: Record<string, RequestCluster>
): Record<string, FakeRequestClusterData> {
return Object.fromEntries(
Object.entries(clusters).map(([key, cluster]) => [key, cluster.makeDataForFake()])
);
}
export function makeFakeClusters(
fake_clusters_data: Record<string, FakeRequestClusterData>
): Record<string, FakeCluster> {
return Object.fromEntries(
Object.entries(fake_clusters_data).map(([key, cluster_data]) => [
key,
new FakeCluster(cluster_data),
])
);
}
export class FakeCluster extends RequestCluster {
constructor(public data: FakeRequestClusterData) {
super(data.id);
for (const key of [
'hasCookies',
'hasMarkedCookies',
'hasMarks',
'exposesOriginWhere',
'exposesOrigin',
]) {
//@ts-ignore
this[key] = () => {
//@ts-ignore
return this.data[key];
};
}
}
hasCookies() {
return this.data.hasCookies;
}
}

View File

@ -150,7 +150,9 @@ function generateHostPage(
...defaultValue('was_processing_necessary'),
visibleIf: `{${f('legal_basis_type')}} = "legitimate_interest" or {${f(
'present'
)}} = "not_mentioned" or {popup_type} = "none"`,
)}} = "not_mentioned" or {${f(
'present'
)}} = "not_before_making_a_choice" or {popup_type} = "none"`,
choices: [
{ value: 'yes', text: 'Tak, było konieczne' },
{ value: 'no', text: 'Nie, nie było konieczne' },

View File

@ -44,7 +44,7 @@ export default class NoInformationAtAllProblem extends Problem {
<li>Jaki jest cel takiego przetwarzania danych przez Państwa stronę?</li>
<li>
Jaka jest podstawa prawna takiego przetwarzania{' '}
{mode == 'email' ? _('moich') : ''} danych osobowych $
{mode == 'email' ? _('moich') : ''} danych osobowych
{mode == 'report' ? 'użytkowników końcowych' : ''} przez Państwa stronę?
</li>
</ul>

View File

@ -5,7 +5,8 @@ import { Problem } from './problem';
export class TransferOutsideEU extends Problem {
getNecessaryExplainers(): ExplainerKey[] {
return [];
const has_cookies = this.getRelatedClusters().some((cluster) => cluster.hasCookies());
return has_cookies ? ['cookies_are_pii'] : [];
}
qualifies(): boolean {

View File

@ -18,15 +18,12 @@ export class UnknownIdentity extends Problem {
<h2>Tożsamość administratora</h2>
{mode == 'email' ? (
<p>
Na Państwa stronie nie {_('znalazłem')} informacji o tym, kto jest
administratorem moich danych osobowych przetwarzanych przez Państwa stronę w
trakcie {_('moich')} odwiedzin.
Na Państwa stronie nie {_('znalazłem')} sposobu na poznanie tożsamości
administratora strony <strong>przed</strong> podjęciem wyboru dotyczącego
przetwarzania danych mnie dotyczących.
</p>
) : (
<p>
Na stronie brakuje informacji o tym, kto jest administratorem danych
osobowych osób odwiedzających.
</p>
<p>Na stronie brakuje sposobu na poznanie tożsamości administratora strony.</p>
)}
<p>
Zgodnie z treścią Art. 13 RODO, jeżeli dane osobowe osoby, której dane dotyczą,

View File

@ -21,7 +21,11 @@ const testCluster: (cluster: RequestCluster, answers: ParsedHostAnswers | undefi
export class UnknownLegalBasis extends Problem {
getNecessaryExplainers(): ExplainerKey[] {
return ['responsibility_for_third_parties'];
const has_cookies = this.getRelatedClusters().some((cluster) => cluster.hasCookies());
return [
'responsibility_for_third_parties',
...(has_cookies ? ['cookies_are_pii' as ExplainerKey] : []),
];
}
qualifies(): boolean {
@ -70,8 +74,8 @@ export class UnknownLegalBasis extends Problem {
Na stronie nie znajdują się informacje o tym, jaka jest podstawa prawna
takiego przetwarzania danych osobowych, jakimi jest część historii
przeglądania. Zgodnie z treścią Artykułu 13. p. 1 lit. c) RODO, aby
przetwarzać dane osobowe, trzeba poinformować osobę, któ©ej dane dotyczą, o
tym, jak ajest podstaw aprawna takiego przetwarzania danych.
przetwarzać dane osobowe, trzeba poinformować osobę, której dane dotyczą, o
tym, jaka jest podstawa prawna takiego przetwarzania danych.
</p>
)}
{mode == 'email' ? (

View File

@ -0,0 +1,114 @@
import { RequestCluster } from '../../../request-cluster';
import { dataLocationToText, wordlist } from '../../../util';
import { ExplainerKey } from '../explainers';
import { v } from '../verbs';
import { Problem } from './problem';
export class UnknownPurposes extends Problem {
getNecessaryExplainers(): ExplainerKey[] {
const has_cookies = this.getAffectedClusters().some((cluster) => cluster.hasCookies());
if (has_cookies) {
return ['cookies_are_pii'];
} else {
return [];
}
}
isHostAffected(host: string) {
const answers = this.answers.hosts[host];
if (!answers) {
return false;
}
return (
['not_mentioned', 'not_before_making_a_choice'].includes(answers.present) &&
['no', 'not_sure'].includes(answers.was_processing_necessary) &&
(this.clusters[host].hasCookies() || this.clusters[host].exposesOrigin())
);
}
qualifies(): boolean {
return Object.keys(this.answers.hosts).some((host) => this.isHostAffected(host));
}
getAffectedClusters(): RequestCluster[] {
return Object.keys(this.answers.hosts)
.filter((host) => this.isHostAffected(host))
.map((host) => this.clusters[host]);
}
getEmailContent({ mode, tone }: { mode: 'email' | 'report'; tone: 'official' | 'polite' }) {
const _ = (key: string) => v(key, this.answers.zaimek);
const affected_clusters = this.getAffectedClusters();
const has_history = affected_clusters.some((cluster) => cluster.exposesOrigin());
const has_cookies = affected_clusters.some((cluster) => cluster.hasCookies());
return (
<>
<h2>Cele przetwarzania danych</h2>
<p>
Państwa strona{' '}
{mode == 'email'
? `ujawniła dane ${_('mnie')} dotyczące`
: 'ujawnia dane dotyczące użytkowników'}{' '}
w zakresie{' '}
{wordlist([
...(has_cookies ? ['treści plików cookies'] : []),
...(has_history
? [
mode === 'email'
? `części ${_('mojej')} historii przeglądania`
: `części historii przeglądania`,
]
: []),
])}{' '}
podmiotom, które właścicielami nastepujących domen:
</p>
<ul>
{affected_clusters.map((cluster, index) => {
const locations = cluster.exposesOriginWhere();
return (
<li>
<strong>{cluster.id}</strong>:{' '}
{wordlist([
...(cluster.hasCookies() ? ['treść plików cookies'] : []),
...(cluster.exposesOrigin()
? [
(mode === 'email'
? `część ${_('mojej')} historii przeglądania`
: `część historii przeglądania użytkownika`) +
' (' +
wordlist(
locations.map((l) => dataLocationToText(l))
) +
')',
]
: []),
])}
{index === affected_clusters.length - 1 ? '.' : ';'}
</li>
);
})}
</ul>
{mode === 'email' ? (
tone === 'official' ? (
<p>
Proszę o wskazanie, jakie cele takiego przetwarzania danych, które
mnie dotyczą.
</p>
) : (
<p>
Apeluję o umieszczenie informacji na temat na Państwa stronie, aby jej
użytkownicy mogli podejmować w pełni świadome wybory dotyczące
przetwarzania danych ich dotyczących.
</p>
)
) : (
<p>
<strong>Zalecenie</strong>: warto dodać informacje o tym, jakie cele
ujawniania wyżej opisanych danych wyżej opisanym podmiotom trzecim.
</p>
)}
</>
);
}
}

View File

@ -5,6 +5,17 @@
font-family: 'OpenSans' !important;
}
#app {
min-height: 100vh;
display: flex;
flex-flow: column;
}
#main-section {
flex-grow: 1;
margin-bottom: 20px; // to contain diag section
}
html {
font-size: 1rem;
}
@ -289,3 +300,9 @@ h1 {
color: $ultra-black-color;
}
}
.diag-toolbox {
position: fixed;
bottom: 10px;
left: 10px;
}

View File

@ -110,7 +110,7 @@ function Report() {
)}
</div>
</header>
<section>{result}</section>
<section id="main-section">{result}</section>
</Fragment>
);
} catch (e) {

View File

@ -2,7 +2,7 @@ import React, { Fragment } from 'react';
import { RequestCluster } from '../../request-cluster';
import './screenshot-generator.scss';
const SS_URL = 'http://65.108.60.135:3000';
const SS_URL = 'https://screenshot-service.internet-czas-dzialac.pl';
enum taskState {
WAITING = 'waiting',
@ -38,7 +38,7 @@ interface screenshotTask {
}
function createTaskEndpoint(visited_url: string, domains: string[]) {
return `${SS_URL}/api/requests?url=${visited_url}${domains.reduce(
return `${SS_URL}/api/requests?url=${encodeURIComponent(visited_url)}${domains.reduce(
(prev: string, curr: string) => prev + '&domains[]=' + curr,
''
)}`;

View File

@ -7,6 +7,15 @@
flex-flow: column;
border-bottom: none;
&__header {
display: flex;
align-items: center;
.icon.cookie-data {
margin-left: 0.25rem;
}
}
.domain-checkbox {
margin-right: 0.5rem;
width: 0.875rem;

View File

@ -2,12 +2,10 @@ import React from 'react';
import { getMemory } from '../../memory';
import { StolenDataEntry } from '../../stolen-data-entry';
import { maskString, useEmitter } from '../../util';
import { useEmitter } from '../../util';
import './stolen-data-cluster.scss';
const MAX_STRING_VALUE_LENGTH = 100;
function StolenDataValue({ entry }: { entry: StolenDataEntry; prefixKey?: string }) {
const [version] = useEmitter(entry);
let body = null;
@ -43,13 +41,16 @@ function StolenDataRow({ entry }: { entry: StolenDataEntry }) {
<input
type="checkbox"
checked={entry.isMarked}
id={entry.id.toString()}
onChange={() => {
entry.toggleMark();
getMemory().emit('change', entry.request.shorthost);
}}
/>
</td>
<th title={`Nazwa: ${entry.name}\nŹródło: ${entry.source}`}>{entry.name}</th>
<th title={`Nazwa: ${entry.name}\nŹródło: ${entry.source}`}>
<label htmlFor={entry.id.toString()}>{entry.name}</label>
</th>
<td className="icons">
{entry.source === 'cookie' ? (
<span title="Dane przechowywane w Cookies">
@ -103,13 +104,11 @@ export default function StolenDataCluster({
shorthost,
minValueLength,
cookiesOnly,
refreshToken,
cookiesOrOriginOnly,
detailsVisibility,
}: {
origin: string;
shorthost: string;
refreshToken: number;
minValueLength: number;
cookiesOnly: boolean;
cookiesOrOriginOnly: boolean;
@ -122,20 +121,34 @@ export default function StolenDataCluster({
return (
<div className="stolen-data-cluster-container">
<header className="domains-container">
<div>
<div className="domains-container__header">
<input
type="checkbox"
className="domain-checkbox"
data-version={version}
checked={cluster.hasMarks()}
onChange={() => {
console.log('Clicked checkbox!', {
cluster_id: cluster.id,
has_marks: cluster.hasMarks(),
});
cluster.hasMarks() ? cluster.undoMark() : cluster.autoMark();
getMemory().emit('change', cluster.id);
}}
/>
<a className="domain" href={'https://' + cluster.id} target="_blank">
{cluster.id}
</a>
</a>{' '}
{cluster.hasCookies() ? (
<img
src="/assets/icons/cookie.svg"
height={16}
width={16}
className="icon cookie-data"
/>
) : (
''
)}
</div>
<div className="subdomains-container">
{fullHosts.map((host, index) => (

View File

@ -14,6 +14,15 @@ async function getCurrentTab() {
import './../../styles/global.scss';
import './toolbar.scss';
function isDomainHighlySuspicious(domain: string): boolean {
return (
domain.includes('facebook') ||
domain.includes('twitter') ||
domain.includes('linkedin') ||
false
);
}
const Toolbar = () => {
const [origin, setOrigin] = React.useState<string | null>(null);
const [eventCounts] = useEmitter(getMemory());
@ -53,6 +62,13 @@ const Toolbar = () => {
if (!origin) return;
const exposedOriginDomains = Object.values(getMemory().getClustersForOrigin(origin))
.filter((cluster) => cluster.exposesOrigin())
.sort((cluster1, cluster2) =>
isDomainHighlySuspicious(cluster1.id)
? -1
: isDomainHighlySuspicious(cluster2.id)
? 1
: 0
)
.map((cluster) => cluster.id);
setExposedOriginDomainCopy('');
@ -86,6 +102,13 @@ const Toolbar = () => {
if (!origin) return;
const cookieDomains = Object.values(getMemory().getClustersForOrigin(origin))
.filter((cluster) => cluster.hasCookies())
.sort((cluster1, cluster2) =>
isDomainHighlySuspicious(cluster1.id)
? -1
: isDomainHighlySuspicious(cluster2.id)
? 1
: 0
)
.map((cluster) => cluster.id);
setCookieDomainCopy('');

13
diag.html Normal file
View File

@ -0,0 +1,13 @@
<!DOCTYPE html>
<html>
<head>
<title>RENTGEN DIAG</title>
</head>
<body>
<div id="app"></div>
</body>
<script src="/node_modules/react/umd/react.production.min.js"></script>
<script src="/node_modules/react-dom/umd/react-dom.production.min.js"></script>
<script src="/node_modules/survey-react/survey.react.min.js"></script>
<script src="./lib/diag.js"></script>
</html>

65
diag.tsx Normal file
View File

@ -0,0 +1,65 @@
import React, { Fragment } from 'react';
import ReactDOM from 'react-dom';
import EmailContent from './components/report-window/email-content';
import { makeFakeClusters } from './components/report-window/fake-clusters';
class ErrorBoundary extends React.Component<any, { hasError: boolean; error: any }> {
constructor(props: any) {
super(props);
this.state = { hasError: false, error: null };
}
static getDerivedStateFromError(error: any) {
return { hasError: true, error };
}
render() {
if (this.state.hasError) {
return <h1>Something went wrong.</h1>;
}
return this.props.children;
}
}
function Diag() {
const [json, setjson] = React.useState(
JSON.stringify({ answers: { hosts: {} }, visited_url: '', fake_clusters_data: {} })
);
const { answers, visited_url, fake_clusters_data } = JSON.parse(json);
const fake_clusters = makeFakeClusters(fake_clusters_data);
return (
<div style={{ display: 'grid', gridTemplateColumns: '50% 50%', minHeight: '100vh' }}>
<div>
<textarea
style={{ width: 'calc(100% - 50px)', height: '100%' }}
value={json}
onChange={(e) => {
setjson(e.target.value);
}}
></textarea>
</div>
<div>
<EmailContent
{...{
answers,
visited_url,
clusters: fake_clusters,
scrRequestPath: '/screenshots',
downloadFiles: () => {
alert('download!');
},
user_role: 'user',
}}
/>
</div>
</div>
);
}
ReactDOM.render(
<ErrorBoundary>
<Diag />
</ErrorBoundary>,
document.getElementById('app')
);

View File

@ -48,6 +48,7 @@ esbuild
'components/sidebar/sidebar.tsx',
'components/report-window/report-window.tsx',
'background.ts',
'diag.tsx',
'styles/global.scss',
'styles/fonts.scss',
],

View File

@ -1,5 +1,5 @@
'use strict';
import { StolenDataEntry } from './stolen-data-entry';
import { DataLocation, StolenDataEntry } from './stolen-data-entry';
import {
flattenObjectEntries,
getshorthost,
@ -164,12 +164,12 @@ export default class ExtendedRequest {
);
}
exposesOrigin() {
exposesOriginWhere(): null | DataLocation {
const host = this.originalHost;
const path = this.originalPathname || '/';
const shorthost = getshorthost(host);
if (this.getReferer().includes(shorthost)) {
return true;
return { path: this.url, source: 'header', key: 'Referer' };
}
for (const entry of this.stolenData) {
if (
@ -177,10 +177,14 @@ export default class ExtendedRequest {
entry.value.includes(path) ||
entry.value.includes(shorthost)
) {
return true;
return entry.toDataLocation();
}
}
return false;
return null;
}
exposesOrigin() {
return this.exposesOriginWhere() !== null;
}
private getAllStolenData(): StolenDataEntry[] {
@ -301,6 +305,10 @@ export default class ExtendedRequest {
return this.stolenData.filter((data) => data.isMarked);
}
unmarkAllEntries() {
this.stolenData.forEach((entry) => entry.unmark());
}
getHost() {
return new URL(this.url).host;
}

View File

@ -3,7 +3,7 @@
"manifest_version": 2,
"name": "Rentgen",
"short_name": "Rentgen",
"version": "0.1.7",
"version": "0.1.10",
"author": "Kuba Orlik, Arkadiusz Wieczorek (Internet. Czas działać!)",
"homepage_url": "https://git.internet-czas-dzialac.pl/icd/rentgen",
"background": {

4
package-lock.json generated
View File

@ -1,12 +1,12 @@
{
"name": "rentgen",
"version": "0.1.7",
"version": "0.1.10",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "rentgen",
"version": "0.1.7",
"version": "0.1.10",
"license": "GPL-3.0-or-later",
"dependencies": {
"@iabtcf/core": "^1.3.1",

View File

@ -1,6 +1,6 @@
{
"name": "rentgen",
"version": "0.1.7",
"version": "0.1.10",
"description": "Rentgen is an add-on prepared for Firefox-based browsers. This extension will automatically visualize all the data that a given website sends to third parties.",
"main": "esbuild.config.js",
"type": "module",

View File

@ -1,6 +1,7 @@
import { FakeRequestClusterData } from './components/report-window/fake-clusters';
import ExtendedRequest from './extended-request';
import { SaferEmitter } from './safer-emitter';
import { Sources, StolenDataEntry } from './stolen-data-entry';
import { DataLocation, Sources, StolenDataEntry } from './stolen-data-entry';
import { allSubhosts, isSameURL, reduceConcat, unique } from './util';
@ -171,7 +172,13 @@ export class RequestCluster extends SaferEmitter {
return this.requests.map((request) => request.getMarkedEntries()).reduce(reduceConcat, []);
}
exposesOrigin() {
exposesOriginWhere(): DataLocation[] {
return this.requests
.map((request) => request.exposesOriginWhere())
.filter((l) => l !== null) as DataLocation[];
}
exposesOrigin(): boolean {
return this.requests.some((request) => request.exposesOrigin());
}
@ -184,9 +191,7 @@ export class RequestCluster extends SaferEmitter {
undoMark() {
this.calculateRepresentativeStolenData();
this.representativeStolenData.forEach((entry) => {
entry.unmark();
});
this.requests.forEach((request) => request.unmarkAllEntries());
}
getDataTypeDescription(noun = 'Twojej') {
@ -203,4 +208,15 @@ export class RequestCluster extends SaferEmitter {
}
return types_of_data.join(', ');
}
makeDataForFake(): FakeRequestClusterData {
return {
id: this.id,
hasCookies: this.hasCookies(),
hasMarkedCookies: this.hasMarkedCookies(),
hasMarks: this.hasMarks(),
exposesOriginWhere: this.exposesOriginWhere(),
exposesOrigin: this.exposesOrigin(),
};
}
}

View File

@ -33,6 +33,12 @@ const id = (function* id() {
export type DecodingSchema = 'base64' | 'raw';
export type DataLocation = {
path: string;
source: Sources;
key: string;
};
export class StolenDataEntry extends SaferEmitter {
public isIAB = false;
public id: number;
@ -253,4 +259,12 @@ export class StolenDataEntry extends SaferEmitter {
haystack.includes(getshorthost(this.request.origin))
);
}
toDataLocation(): DataLocation {
return {
path: this.request.url,
source: this.source,
key: this.name,
};
}
}

36
util.ts
View File

@ -1,5 +1,6 @@
import { EventEmitter } from 'events';
import React from 'react';
import { DataLocation, Sources } from './stolen-data-entry';
export type Unpromisify<T> = T extends Promise<infer R> ? R : T;
export type Unarray<T> = T extends Array<infer R> ? R : T;
@ -77,7 +78,7 @@ export function useEmitter(
export function parseCookie(cookie: string): Record<string, string> {
return cookie
.split(';')
.map((l) => l.split('='))
.map((l) => [l.slice(0, l.indexOf('=')), l.slice(l.indexOf('=') + 1)])
.reduce(
(acc, [key, value]) => ({
...acc,
@ -283,8 +284,37 @@ export function normalizeForClassname(string: string) {
}
export function wordlist(words: string[]) {
return words.reduce(
(acc, word, i) => `${acc}${i > 0 ? (i < words.length - 1 ? ',' : ' i') : ''} ${word}`,
return Array.from(new Set(words)).reduce(
(acc, word, i) =>
`${acc}${
i > 0 ? (i < words.length - 1 ? ', ' : Math.random() > 0.5 ? ' i ' : ' oraz ') : ''
}${word}`,
''
);
}
const source_to_word: Record<Sources, string> = {
cookie: 'plik cookie o nazwie',
pathname: 'fragment ścieżki w URL',
queryparams: 'query params w URL o nazwie',
header: 'nagłówek HTTP',
request_body: 'body zapytania HTTP, pod kluczem',
};
export function dataLocationToText(l: DataLocation) {
return `${source_to_word[l.source]} ${l.key}`;
}
export function downloadText(filename: string, text: string) {
// https://stackoverflow.com/questions/45831191/generate-and-download-file-from-js
var element = document.createElement('a');
element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(text));
element.setAttribute('download', filename);
element.style.display = 'none';
document.body.appendChild(element);
element.click();
document.body.removeChild(element);
}