Compare commits

..

No commits in common. "develop" and "fix-checkbox" have entirely different histories.

27 changed files with 99 additions and 556 deletions

127
README.md
View File

@ -1,70 +1,8 @@
<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> <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> 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.
**Features:**
- analysis of web traffic generated by the visited website;
- visualization of data transmitted to third parties by the visited site (user's browsing history and cookies);
- preparation of screenshots of development tools as evidence of data transmitted to third parties;
- 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
- OS: Linux x86_64
- Node.js: 16.x version
- npm: 7.x version or higher
### Build steps
1. Pull repository or download a zip package
2. Go to the root directory of the pulled repository
3. Run command: `npm install`
4. Run command: `npm run build`
5. Run command: `npm run create-package`
6. Go to the `web-ext-artifacts` directory
7. You will find a zip archive: `rentgen-x-x-x.zip` (`x-x-x` means add-on version)
### Run steps
1. Run Firefox and go to `about:debugging`
2. Click _This Firefox_ tab
3. Click _Load Temporary Add-on..._ button
4. Pick the zip archive from last step of build process.
## 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.
## Screenshots
<img src="./assets/screenshots/image-14.png" />
<img src="./assets/screenshots/image-15.png" />
<img src="./assets/screenshots/3a.png" />
<img src="./assets/screenshots/3b.png" />
<img src="./assets/screenshots/4a.png" />
<img src="./assets/screenshots/4b.png" />
<img src="./assets/screenshots/5a.png" />
<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. <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:** **Funkcje Rentgena:**
- analiza ruchu sieciowego generowanego przez stronę internetową; - analiza ruchu sieciowego generowanego przez stronę internetową;
@ -73,10 +11,6 @@ Each issue will be reviewed and moved to an internal issues list of our Gitea in
- pomoc w oszacowaniu potencjalnych obszarów roboczych względem zgodności z RODO; - 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. - 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ł ## Jak zbudować i uruchomić Rentgena ze źródeł
### Wymagania wstępne ### Wymagania wstępne
@ -104,7 +38,7 @@ Firefox: https://addons.mozilla.org/pl/firefox/addon/rentgen/
## Zgłaszanie błędów ## Zgłaszanie błędów
Jeżeli znajdziesz jakieś problem, napisz do nas maila: kontakt@internet-czas-dzialac.pl 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. Nie przyjmujemy zgłoszeń na platformie Microsoft Github.
@ -112,3 +46,60 @@ Każdy problem zostanie sprawdzony i przeniesiony na wewnętrzną listę problem
--- ---
## 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.
**Features:**
- analysis of web traffic generated by the visited website;
- visualization of data transmitted to third parties by the visited site (user's browsing history and cookies);
- preparation of screenshots of development tools as evidence of data transmitted to third parties;
- 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.
## How to build and run Rentgen on your own
### Pre-requirements
- OS: Linux x86_64
- Node.js: 16.x version
- npm: 7.x version or higher
### Build steps
1. Pull repository or download a zip package
2. Go to the root directory of the pulled repository
3. Run command: `npm install`
4. Run command: `npm run build`
5. Run command: `npm run create-package`
6. Go to the `web-ext-artifacts` directory
7. You will find a zip archive: `rentgen-x-x-x.zip` (`x-x-x` means add-on version)
### Run steps
1. Run Firefox and go to `about:debugging`
2. Click _This Firefox_ tab
3. Click _Load Temporary Add-on..._ button
4. Pick the zip archive from last step of build process.
## Issue tracker
Link to issue tracker on our Discourse instance: https://forum.internet-czas-dzialac.pl/c/rentgen-issue-tracker
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.
## Screenshots
<img src="./assets/screenshots/image-14.png" />
<img src="./assets/screenshots/image-15.png" />
<img src="./assets/screenshots/3a.png" />
<img src="./assets/screenshots/3b.png" />
<img src="./assets/screenshots/4a.png" />
<img src="./assets/screenshots/4b.png" />
<img src="./assets/screenshots/5a.png" />
<img src="./assets/screenshots/5b.png" />
<img src="./assets/screenshots/2022-07-14_21-04.png" />

View File

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

View File

@ -7,8 +7,6 @@ import './email-content.scss';
import { Fragment, useState } from 'react'; import { Fragment, useState } from 'react';
import emailIntro from './email-intro'; import emailIntro from './email-intro';
import { reportIntro } from './report-intro'; import { reportIntro } from './report-intro';
import { downloadText } from '../../util';
import { getFakeClusterData } from './fake-clusters';
const SS_URL = 'http://65.108.60.135:3000'; const SS_URL = 'http://65.108.60.135:3000';
@ -138,23 +136,6 @@ export default function EmailContent({
</p> </p>
</section> </section>
) : null} ) : 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> </div>
</Fragment> </Fragment>
); );

View File

@ -9,49 +9,7 @@ export const Explainers: Record<ExplainerKey, (zaimek_index: 0 | 1 | 2 | 3) => J
<p> <p>
Sztucznie wygenerowane identyfikatory przechowywane w plikach Cookies stanowią dane Sztucznie wygenerowane identyfikatory przechowywane w plikach Cookies stanowią dane
osobowe. Wskazuje na to wprost Art. 4. pkt 1. RODO, wymieniając identyfikator osobowe. Wskazuje na to wprost Art. 4. pkt 1. RODO, wymieniając identyfikator
internetowy i numer identyfikacyjny jako przykłady danych osobowych. Losowe internetowy i numer identyfikacyjny jako przykłady danych osobowych.
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> </p>
</> </>
), ),

View File

@ -1,56 +0,0 @@
// 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

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

View File

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

View File

@ -19,8 +19,7 @@ export class UnknownIdentity extends Problem {
{mode == 'email' ? ( {mode == 'email' ? (
<p> <p>
Na Państwa stronie nie {_('znalazłem')} sposobu na poznanie tożsamości Na Państwa stronie nie {_('znalazłem')} sposobu na poznanie tożsamości
administratora strony <strong>przed</strong> podjęciem wyboru dotyczącego administratora strony przed podjęciem wyboru dotyc
przetwarzania danych mnie dotyczących.
</p> </p>
) : ( ) : (
<p>Na stronie brakuje sposobu na poznanie tożsamości administratora strony.</p> <p>Na stronie brakuje sposobu na poznanie tożsamości administratora strony.</p>

View File

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

View File

@ -1,114 +0,0 @@
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

@ -34,7 +34,7 @@ export class UnlawfulCookieAccess extends Problem {
const _ = (key: string) => v(key, this.answers.zaimek); const _ = (key: string) => v(key, this.answers.zaimek);
return ( return (
<> <>
<h2>Dostęp do cookies niezgodny z ustawą Prawo Komunikacji Elektronicznej</h2> <h2>Dostęp do cookies niezgodny z ustawą Prawo Telekomunikacyjne</h2>
<p> <p>
Państwa strona {mode == 'email' ? 'dokonała' : 'dokonuje'} odczytu plików Cookie Państwa strona {mode == 'email' ? 'dokonała' : 'dokonuje'} odczytu plików Cookie
zapisanych na dysku twardym{' '} zapisanych na dysku twardym{' '}
@ -67,9 +67,9 @@ export class UnlawfulCookieAccess extends Problem {
})} })}
</ul> </ul>
<p> <p>
Zgodnie z treścią Art. 399.{' '} Zgodnie z treścią Art. 173.{' '}
<a href="https://isap.sejm.gov.pl/isap.nsf/DocDetails.xsp?id=WDU20240001221"> <a href="https://isap.sejm.gov.pl/isap.nsf/download.xsp/WDU20041711800/U/D20041800Lj.pdf">
ustawy Prawo Komunikacji Elektronicznej ustawy Prawo Telekomunikacyjne
</a> </a>
, strona może pozyskać dostęp do treści plików cookies pod warunkiem spełnienia , strona może pozyskać dostęp do treści plików cookies pod warunkiem spełnienia
jednego z następujących warunków: jednego z następujących warunków:
@ -78,10 +78,9 @@ export class UnlawfulCookieAccess extends Problem {
<li> <li>
Użytkownik wyraził zgodę na takie przetwarzanie danych <em>po</em> tym, jak Użytkownik wyraził zgodę na takie przetwarzanie danych <em>po</em> tym, jak
został poinformowany bezpośrednio o celu uzyskania dostępu do tej został poinformowany bezpośrednio o celu uzyskania dostępu do tej
informacji. Zgodnie z Art. 400 ustawy Prawo Komunikacji Elektronicznej, taka informacji. Zgodnie z Art. 174 ustawy Prawo Telekomunikacyjne, taka zgoda
zgoda musi spełniać warunki zgody ustalone przez RODO, aby mogła być użyta musi spełniać warunki zgody ustalone przez RODO, aby mogła być jako podstawa
jako podstawa prawna uzyskania dostępu do cookies i podobnych technologii w prawna uzyskania dostępu do cookies i podobnych technologii w przeglądarce;
przeglądarce;
</li> </li>
<li> <li>
Dostęp do treści plików cookies jest konieczny do dostarczania usługi Dostęp do treści plików cookies jest konieczny do dostarczania usługi
@ -111,8 +110,7 @@ export class UnlawfulCookieAccess extends Problem {
przetwarzanie danych osobowych. Aby zgoda była ważna w przetwarzanie danych osobowych. Aby zgoda była ważna w
świetle RODO, musi być dobrowolna. Brak możliwości świetle RODO, musi być dobrowolna. Brak możliwości
odmówienia zgody sprawia, że tak wyrażona zgoda nie jest odmówienia zgody sprawia, że tak wyrażona zgoda nie jest
ważna w świetle RODO. Dlatego nie jest spełniony warunek ważna w świetle RODO. Dlatego nie jest spełniony warunek 1.{' '}
1.{' '}
</> </>
) : ( ) : (
<> <>

View File

@ -5,17 +5,6 @@
font-family: 'OpenSans' !important; 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 { html {
font-size: 1rem; font-size: 1rem;
} }
@ -300,9 +289,3 @@ h1 {
color: $ultra-black-color; color: $ultra-black-color;
} }
} }
.diag-toolbox {
position: fixed;
bottom: 10px;
left: 10px;
}

View File

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

View File

@ -38,7 +38,7 @@ interface screenshotTask {
} }
function createTaskEndpoint(visited_url: string, domains: string[]) { function createTaskEndpoint(visited_url: string, domains: string[]) {
return `${SS_URL}/api/requests?url=${encodeURIComponent(visited_url)}${domains.reduce( return `${SS_URL}/api/requests?url=${visited_url}${domains.reduce(
(prev: string, curr: string) => prev + '&domains[]=' + curr, (prev: string, curr: string) => prev + '&domains[]=' + curr,
'' ''
)}`; )}`;

View File

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

View File

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

View File

@ -14,15 +14,6 @@ async function getCurrentTab() {
import './../../styles/global.scss'; import './../../styles/global.scss';
import './toolbar.scss'; import './toolbar.scss';
function isDomainHighlySuspicious(domain: string): boolean {
return (
domain.includes('facebook') ||
domain.includes('twitter') ||
domain.includes('linkedin') ||
false
);
}
const Toolbar = () => { const Toolbar = () => {
const [origin, setOrigin] = React.useState<string | null>(null); const [origin, setOrigin] = React.useState<string | null>(null);
const [eventCounts] = useEmitter(getMemory()); const [eventCounts] = useEmitter(getMemory());
@ -62,13 +53,6 @@ const Toolbar = () => {
if (!origin) return; if (!origin) return;
const exposedOriginDomains = Object.values(getMemory().getClustersForOrigin(origin)) const exposedOriginDomains = Object.values(getMemory().getClustersForOrigin(origin))
.filter((cluster) => cluster.exposesOrigin()) .filter((cluster) => cluster.exposesOrigin())
.sort((cluster1, cluster2) =>
isDomainHighlySuspicious(cluster1.id)
? -1
: isDomainHighlySuspicious(cluster2.id)
? 1
: 0
)
.map((cluster) => cluster.id); .map((cluster) => cluster.id);
setExposedOriginDomainCopy(''); setExposedOriginDomainCopy('');
@ -102,13 +86,6 @@ const Toolbar = () => {
if (!origin) return; if (!origin) return;
const cookieDomains = Object.values(getMemory().getClustersForOrigin(origin)) const cookieDomains = Object.values(getMemory().getClustersForOrigin(origin))
.filter((cluster) => cluster.hasCookies()) .filter((cluster) => cluster.hasCookies())
.sort((cluster1, cluster2) =>
isDomainHighlySuspicious(cluster1.id)
? -1
: isDomainHighlySuspicious(cluster2.id)
? 1
: 0
)
.map((cluster) => cluster.id); .map((cluster) => cluster.id);
setCookieDomainCopy(''); setCookieDomainCopy('');
@ -249,7 +226,7 @@ const Toolbar = () => {
<p> <p>
Takie przetwarzanie danych może być niezgodne z prawem. Przejdź Takie przetwarzanie danych może być niezgodne z prawem. Przejdź
do analizy aby pomóc ustalić, czy ta strona nie narusza RODO lub do analizy aby pomóc ustalić, czy ta strona nie narusza RODO lub
ustawy Prawo Komunikacji Elektronicznej. ustawy Prawo Telekomunikacyjne.
</p> </p>
</section> </section>
<section className="actions"> <section className="actions">

View File

@ -1,13 +0,0 @@
<!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>

View File

@ -1,65 +0,0 @@
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,7 +48,6 @@ esbuild
'components/sidebar/sidebar.tsx', 'components/sidebar/sidebar.tsx',
'components/report-window/report-window.tsx', 'components/report-window/report-window.tsx',
'background.ts', 'background.ts',
'diag.tsx',
'styles/global.scss', 'styles/global.scss',
'styles/fonts.scss', 'styles/fonts.scss',
], ],

View File

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

View File

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

4
package-lock.json generated
View File

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

View File

@ -1,6 +1,6 @@
{ {
"name": "rentgen", "name": "rentgen",
"version": "0.1.10", "version": "0.1.8",
"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.", "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", "main": "esbuild.config.js",
"type": "module", "type": "module",

View File

@ -1,7 +1,6 @@
import { FakeRequestClusterData } from './components/report-window/fake-clusters';
import ExtendedRequest from './extended-request'; import ExtendedRequest from './extended-request';
import { SaferEmitter } from './safer-emitter'; import { SaferEmitter } from './safer-emitter';
import { DataLocation, Sources, StolenDataEntry } from './stolen-data-entry'; import { Sources, StolenDataEntry } from './stolen-data-entry';
import { allSubhosts, isSameURL, reduceConcat, unique } from './util'; import { allSubhosts, isSameURL, reduceConcat, unique } from './util';
@ -172,13 +171,7 @@ export class RequestCluster extends SaferEmitter {
return this.requests.map((request) => request.getMarkedEntries()).reduce(reduceConcat, []); return this.requests.map((request) => request.getMarkedEntries()).reduce(reduceConcat, []);
} }
exposesOriginWhere(): DataLocation[] { exposesOrigin() {
return this.requests
.map((request) => request.exposesOriginWhere())
.filter((l) => l !== null) as DataLocation[];
}
exposesOrigin(): boolean {
return this.requests.some((request) => request.exposesOrigin()); return this.requests.some((request) => request.exposesOrigin());
} }
@ -208,15 +201,4 @@ export class RequestCluster extends SaferEmitter {
} }
return types_of_data.join(', '); 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,12 +33,6 @@ const id = (function* id() {
export type DecodingSchema = 'base64' | 'raw'; export type DecodingSchema = 'base64' | 'raw';
export type DataLocation = {
path: string;
source: Sources;
key: string;
};
export class StolenDataEntry extends SaferEmitter { export class StolenDataEntry extends SaferEmitter {
public isIAB = false; public isIAB = false;
public id: number; public id: number;
@ -259,12 +253,4 @@ export class StolenDataEntry extends SaferEmitter {
haystack.includes(getshorthost(this.request.origin)) 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,6 +1,5 @@
import { EventEmitter } from 'events'; import { EventEmitter } from 'events';
import React from 'react'; import React from 'react';
import { DataLocation, Sources } from './stolen-data-entry';
export type Unpromisify<T> = T extends Promise<infer R> ? R : T; export type Unpromisify<T> = T extends Promise<infer R> ? R : T;
export type Unarray<T> = T extends Array<infer R> ? R : T; export type Unarray<T> = T extends Array<infer R> ? R : T;
@ -78,7 +77,7 @@ export function useEmitter(
export function parseCookie(cookie: string): Record<string, string> { export function parseCookie(cookie: string): Record<string, string> {
return cookie return cookie
.split(';') .split(';')
.map((l) => [l.slice(0, l.indexOf('=')), l.slice(l.indexOf('=') + 1)]) .map((l) => l.split('='))
.reduce( .reduce(
(acc, [key, value]) => ({ (acc, [key, value]) => ({
...acc, ...acc,
@ -284,37 +283,8 @@ export function normalizeForClassname(string: string) {
} }
export function wordlist(words: string[]) { export function wordlist(words: string[]) {
return Array.from(new Set(words)).reduce( return words.reduce(
(acc, word, i) => (acc, word, i) => `${acc}${i > 0 ? (i < words.length - 1 ? ',' : ' i') : ''} ${word}`,
`${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);
}