Compare commits

..

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

32 changed files with 100 additions and 895 deletions

7
.gitignore vendored
View File

@ -5,10 +5,3 @@ sidebar.js
lib/*
/yarn-error.log
/rentgen.zip
# Generated PNG icons (build artifacts)
/assets/icons/*.png
/assets/icon-addon-*.png
# Exception: do not ignore the `browser-api` directory inside `lib`
!/lib/browser-api/

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>
<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.
**Funkcje Rentgena:**
- 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;
- 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
@ -104,7 +38,7 @@ Firefox: https://addons.mozilla.org/pl/firefox/addon/rentgen/
## 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.
@ -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 { 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(
@ -14,7 +13,6 @@ export default function deduceProblems(
): Problem[] {
return [
NoInformationAtAllProblem,
UnknownPurposes,
UnlawfulCookieAccess,
UnknownLegalBasis,
UnknownIdentity,

View File

@ -7,8 +7,6 @@ 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';
@ -138,23 +136,6 @@ 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,49 +9,7 @@ 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. 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>
internetowy i numer identyfikacyjny jako przykłady danych osobowych.
</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>
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,8 +5,7 @@ import { Problem } from './problem';
export class TransferOutsideEU extends Problem {
getNecessaryExplainers(): ExplainerKey[] {
const has_cookies = this.getRelatedClusters().some((cluster) => cluster.hasCookies());
return has_cookies ? ['cookies_are_pii'] : [];
return [];
}
qualifies(): boolean {

View File

@ -19,8 +19,7 @@ export class UnknownIdentity extends Problem {
{mode == 'email' ? (
<p>
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.
administratora strony przed podjęciem wyboru dotyc
</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 {
getNecessaryExplainers(): ExplainerKey[] {
const has_cookies = this.getRelatedClusters().some((cluster) => cluster.hasCookies());
return [
'responsibility_for_third_parties',
...(has_cookies ? ['cookies_are_pii' as ExplainerKey] : []),
];
return ['responsibility_for_third_parties'];
}
qualifies(): boolean {
@ -74,8 +70,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órej dane dotyczą, o
tym, jaka jest podstawa prawna takiego przetwarzania danych.
przetwarzać dane osobowe, trzeba poinformować osobę, któ©ej dane dotyczą, o
tym, jak ajest podstaw aprawna takiego przetwarzania danych.
</p>
)}
{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);
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>
Państwa strona {mode == 'email' ? 'dokonała' : 'dokonuje'} odczytu plików Cookie
zapisanych na dysku twardym{' '}
@ -67,9 +67,9 @@ export class UnlawfulCookieAccess extends Problem {
})}
</ul>
<p>
Zgodnie z treścią Art. 399.{' '}
<a href="https://isap.sejm.gov.pl/isap.nsf/DocDetails.xsp?id=WDU20240001221">
ustawy Prawo Komunikacji Elektronicznej
Zgodnie z treścią Art. 173.{' '}
<a href="https://isap.sejm.gov.pl/isap.nsf/download.xsp/WDU20041711800/U/D20041800Lj.pdf">
ustawy Prawo Telekomunikacyjne
</a>
, strona może pozyskać dostęp do treści plików cookies pod warunkiem spełnienia
jednego z następujących warunków:
@ -78,10 +78,9 @@ export class UnlawfulCookieAccess extends Problem {
<li>
Użytkownik wyraził zgodę na takie przetwarzanie danych <em>po</em> tym, jak
został poinformowany bezpośrednio o celu uzyskania dostępu do tej
informacji. Zgodnie z Art. 400 ustawy Prawo Komunikacji Elektronicznej, taka
zgoda musi spełniać warunki zgody ustalone przez RODO, aby mogła być użyta
jako podstawa prawna uzyskania dostępu do cookies i podobnych technologii w
przeglądarce;
informacji. Zgodnie z Art. 174 ustawy Prawo Telekomunikacyjne, taka zgoda
musi spełniać warunki zgody ustalone przez RODO, aby mogła być jako podstawa
prawna uzyskania dostępu do cookies i podobnych technologii w przeglądarce;
</li>
<li>
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
świetle RODO, musi być dobrowolna. Brak możliwości
odmówienia zgody sprawia, że tak wyrażona zgoda nie jest
ważna w świetle RODO. Dlatego nie jest spełniony warunek
1.{' '}
ważna w świetle RODO. Dlatego nie jest spełniony warunek 1.{' '}
</>
) : (
<>

View File

@ -5,17 +5,6 @@
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;
}
@ -300,9 +289,3 @@ h1 {
color: $ultra-black-color;
}
}
.diag-toolbox {
position: fixed;
bottom: 10px;
left: 10px;
}

View File

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

View File

@ -38,7 +38,7 @@ interface screenshotTask {
}
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,
''
)}`;

View File

@ -7,15 +7,6 @@
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,10 +2,12 @@ import React from 'react';
import { getMemory } from '../../memory';
import { StolenDataEntry } from '../../stolen-data-entry';
import { useEmitter } from '../../util';
import { maskString, 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;
@ -41,16 +43,13 @@ 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}`}>
<label htmlFor={entry.id.toString()}>{entry.name}</label>
</th>
<th title={`Nazwa: ${entry.name}\nŹródło: ${entry.source}`}>{entry.name}</th>
<td className="icons">
{entry.source === 'cookie' ? (
<span title="Dane przechowywane w Cookies">
@ -104,11 +103,13 @@ export default function StolenDataCluster({
shorthost,
minValueLength,
cookiesOnly,
refreshToken,
cookiesOrOriginOnly,
detailsVisibility,
}: {
origin: string;
shorthost: string;
refreshToken: number;
minValueLength: number;
cookiesOnly: boolean;
cookiesOrOriginOnly: boolean;
@ -121,34 +122,20 @@ export default function StolenDataCluster({
return (
<div className="stolen-data-cluster-container">
<header className="domains-container">
<div className="domains-container__header">
<div>
<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>{' '}
{cluster.hasCookies() ? (
<img
src="/assets/icons/cookie.svg"
height={16}
width={16}
className="icon cookie-data"
/>
) : (
''
)}
</a>
</div>
<div className="subdomains-container">
{fullHosts.map((host, index) => (

View File

@ -14,15 +14,6 @@ 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());
@ -62,13 +53,6 @@ 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('');
@ -102,13 +86,6 @@ 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('');
@ -249,7 +226,7 @@ const Toolbar = () => {
<p>
Takie przetwarzanie danych może być niezgodne z prawem. Przejdź
do analizy aby pomóc ustalić, czy ta strona nie narusza RODO lub
ustawy Prawo Komunikacji Elektronicznej.
ustawy Prawo Telekomunikacyjne.
</p>
</section>
<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/report-window/report-window.tsx',
'background.ts',
'diag.tsx',
'styles/global.scss',
'styles/fonts.scss',
],

View File

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

View File

@ -1,54 +0,0 @@
/**
* Chrome Browser API Implementation
*
* Mapuje Chrome chrome.* API na nasze ujednolicone BrowserAPI
*/
import type { BrowserAPI } from './types';
// Chrome używa globalnego obiektu `chrome`
declare const chrome: any;
export const chromeAPI: BrowserAPI = {
// Tabs API - chrome.tabs.* → tabs.*
tabs: {
query: chrome.tabs.query,
onUpdated: {
addListener: chrome.tabs.onUpdated.addListener,
removeListener: chrome.tabs.onUpdated.removeListener,
},
},
// Badge API - Chrome używa action (nie browserAction)
badge: {
setBadgeText: chrome.action.setBadgeText,
setTitle: chrome.action.setTitle,
setBadgeBackgroundColor: chrome.action.setBadgeBackgroundColor,
},
// WebRequest API - chrome.webRequest.* → webRequest.*
webRequest: {
onBeforeRequest: {
addListener: chrome.webRequest.onBeforeRequest.addListener,
},
onBeforeSendHeaders: {
addListener: chrome.webRequest.onBeforeSendHeaders.addListener,
},
},
// Cookies API - chrome.cookies.* → cookies.*
cookies: {
getAll: chrome.cookies.getAll,
remove: chrome.cookies.remove,
},
// Extension API - chrome.extension.* → extension.*
extension: {
getBackgroundPage: chrome.extension.getBackgroundPage,
},
// Windows API - chrome.windows.* → windows.*
windows: {
WINDOW_ID_CURRENT: chrome.windows.WINDOW_ID_CURRENT,
},
};

View File

@ -1,54 +0,0 @@
/**
* Firefox Browser API Implementation
*
* Mapuje Firefox browser.* API na nasze ujednolicone BrowserAPI
*/
import type { BrowserAPI } from './types';
// Firefox używa globalnego obiektu `browser`
declare const browser: any;
export const firefoxAPI: BrowserAPI = {
// Tabs API - direct mapping
tabs: {
query: browser.tabs.query,
onUpdated: {
addListener: browser.tabs.onUpdated.addListener,
removeListener: browser.tabs.onUpdated.removeListener,
},
},
// Badge API - Firefox używa browserAction
badge: {
setBadgeText: browser.browserAction.setBadgeText,
setTitle: browser.browserAction.setTitle,
setBadgeBackgroundColor: browser.browserAction.setBadgeBackgroundColor,
},
// WebRequest API - direct mapping
webRequest: {
onBeforeRequest: {
addListener: browser.webRequest.onBeforeRequest.addListener,
},
onBeforeSendHeaders: {
addListener: browser.webRequest.onBeforeSendHeaders.addListener,
},
},
// Cookies API - direct mapping
cookies: {
getAll: browser.cookies.getAll,
remove: browser.cookies.remove,
},
// Extension API - direct mapping
extension: {
getBackgroundPage: browser.extension.getBackgroundPage,
},
// Windows API - direct mapping
windows: {
WINDOW_ID_CURRENT: browser.windows.WINDOW_ID_CURRENT,
},
};

View File

@ -1,27 +0,0 @@
/**
* Browser API Abstraction - Main Export
*
* Eksportuje właściwą implementację na podstawie TARGET build variable
*/
import type { BrowserAPI } from './types';
// Build-time selection of browser API implementation
let browserApi: BrowserAPI;
// TARGET jest ustawiane przez esbuild.config.js na podstawie npm script
if (process.env.TARGET === 'chrome') {
// Chrome build - używamy chrome adapter
const { chromeAPI } = require('./chrome');
browserApi = chromeAPI;
} else {
// Firefox build (default) - używamy firefox adapter
const { firefoxAPI } = require('./firefox');
browserApi = firefoxAPI;
}
// Eksportuj jako default export
export default browserApi;
// Re-export typów dla wygody
export * from './types';

View File

@ -1,124 +0,0 @@
/**
* Browser API Abstraction - Typy na podstawie faktycznego użycia w kodzie
*
* Przeanalizowane pliki:
* - util.ts: tabs.query, Tab.id
* - tab-dropdown.tsx: tabs.query, Tab.id, Tab.title
* - toolbar.tsx: tabs.query, tabs.onUpdated, Tab.url, windows.WINDOW_ID_CURRENT
* - memory.ts: browserAction.*, webRequest.*, cookies.*, extension.*
*/
// === Tab API (util.ts, tab-dropdown.tsx, toolbar.tsx) ===
export interface Tab {
id?: number; // util.ts: tab.id, tab-dropdown.tsx: tab.id
title?: string; // tab-dropdown.tsx: tab.title
url?: string; // toolbar.tsx: tab.url
}
export interface TabQuery {
currentWindow?: boolean; // util.ts, tab-dropdown.tsx
active?: boolean; // toolbar.tsx
windowId?: number; // toolbar.tsx
}
// === Badge/BrowserAction API (memory.ts) ===
export interface BadgeTextDetails {
text: string; // memory.ts: setBadgeText
tabId?: number; // memory.ts: setBadgeText (optional)
}
export interface BadgeTitleDetails {
title: string; // memory.ts: setTitle
tabId?: number; // memory.ts: setTitle (optional)
}
export interface BadgeColorDetails {
color: string; // memory.ts: setBadgeBackgroundColor
}
// === WebRequest API (memory.ts) ===
export interface RequestDetails {
requestId: string; // memory.ts: request.requestId
requestHeaders?: RequestHeader[]; // memory.ts: request.requestHeaders
// Note: ExtendedRequest konstruktor używa więcej pól,
// ale tu skupiamy się na tym co bezpośrednio używa browser API
}
export interface RequestHeader {
name: string;
value?: string;
}
export interface RequestFilter {
urls: string[]; // memory.ts: { urls: ['<all_urls>'] }
}
export type RequestListener = (details: RequestDetails) => void;
// === Cookies API (memory.ts) ===
export interface Cookie {
name: string; // memory.ts: cookie.name
domain: string; // memory.ts: cookie.domain
}
export interface CookieQuery {
domain?: string; // memory.ts: { domain: shorthost }
}
export interface CookieRemove {
name: string; // memory.ts: { name: cookie.name, url: ... }
url: string; // memory.ts: { url: `https://${cookie.domain}` }
}
// === Main Browser API Interface ===
export interface BrowserAPI {
// Tabs API
tabs: {
query(queryInfo: TabQuery): Promise<Tab[]>;
onUpdated: {
addListener(listener: (tabId: number, changeInfo: any, tab: Tab) => void): void;
removeListener(listener: (tabId: number, changeInfo: any, tab: Tab) => void): void;
};
};
// Badge API (Firefox: browserAction, Chrome: action)
badge: {
setBadgeText(details: BadgeTextDetails): void;
setTitle(details: BadgeTitleDetails): void;
setBadgeBackgroundColor(details: BadgeColorDetails): void;
};
// WebRequest API
webRequest: {
onBeforeRequest: {
addListener(
listener: RequestListener,
filter: RequestFilter,
extraInfoSpec?: string[]
): void;
};
onBeforeSendHeaders: {
addListener(
listener: RequestListener,
filter: RequestFilter,
extraInfoSpec?: string[]
): void;
};
};
// Cookies API
cookies: {
getAll(details: CookieQuery): Promise<Cookie[]>;
remove(details: CookieRemove): Promise<Cookie | null>;
};
// Extension API
extension: {
getBackgroundPage(): Window | null;
};
// Windows API
windows: {
WINDOW_ID_CURRENT: number;
};
}

View File

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

67
package-lock.json generated
View File

@ -1,12 +1,12 @@
{
"name": "rentgen",
"version": "0.1.10",
"version": "0.1.7",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "rentgen",
"version": "0.1.10",
"version": "0.1.7",
"license": "GPL-3.0-or-later",
"dependencies": {
"@iabtcf/core": "^1.3.1",
@ -19,7 +19,6 @@
"tai-password-strength": "^1.1.3"
},
"devDependencies": {
"@types/chrome": "^0.1.3",
"@types/events": "^3.0.0",
"@types/react-dom": "^17.0.9",
"addons-linter": "^4.7.0",
@ -320,16 +319,6 @@
"node": ">=6"
}
},
"node_modules/@types/chrome": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/@types/chrome/-/chrome-0.1.3.tgz",
"integrity": "sha512-KVOIHEKjDZXMg8c18Ir3kbLc+bb8JxZjNJv27Wen3F0I/eeTyrYm7tWOjGhoBjI9fFQfjsTSyFcENBo9Wbl5kw==",
"dev": true,
"dependencies": {
"@types/filesystem": "*",
"@types/har-format": "*"
}
},
"node_modules/@types/decompress": {
"version": "4.2.4",
"resolved": "https://registry.npmjs.org/@types/decompress/-/decompress-4.2.4.tgz",
@ -358,21 +347,6 @@
"integrity": "sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g==",
"dev": true
},
"node_modules/@types/filesystem": {
"version": "0.0.36",
"resolved": "https://registry.npmjs.org/@types/filesystem/-/filesystem-0.0.36.tgz",
"integrity": "sha512-vPDXOZuannb9FZdxgHnqSwAG/jvdGM8Wq+6N4D/d80z+D4HWH+bItqsZaVRQykAn6WEVeEkLm2oQigyHtgb0RA==",
"dev": true,
"dependencies": {
"@types/filewriter": "*"
}
},
"node_modules/@types/filewriter": {
"version": "0.0.33",
"resolved": "https://registry.npmjs.org/@types/filewriter/-/filewriter-0.0.33.tgz",
"integrity": "sha512-xFU8ZXTw4gd358lb2jw25nxY9QAgqn2+bKKjKOYfNCzN4DKCFetK7sPtrlpg66Ywe3vWY9FNxprZawAh9wfJ3g==",
"dev": true
},
"node_modules/@types/got": {
"version": "8.3.6",
"resolved": "https://registry.npmjs.org/@types/got/-/got-8.3.6.tgz",
@ -383,12 +357,6 @@
"@types/node": "*"
}
},
"node_modules/@types/har-format": {
"version": "1.2.16",
"resolved": "https://registry.npmjs.org/@types/har-format/-/har-format-1.2.16.tgz",
"integrity": "sha512-fluxdy7ryD3MV6h8pTfTYpy/xQzCFC7m89nOH9y94cNqJ1mDIDPut7MnRHI3F6qRmh/cT2fUjG1MLdCNb4hE9A==",
"dev": true
},
"node_modules/@types/minimatch": {
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.5.tgz",
@ -7422,16 +7390,6 @@
"defer-to-connect": "^1.0.1"
}
},
"@types/chrome": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/@types/chrome/-/chrome-0.1.3.tgz",
"integrity": "sha512-KVOIHEKjDZXMg8c18Ir3kbLc+bb8JxZjNJv27Wen3F0I/eeTyrYm7tWOjGhoBjI9fFQfjsTSyFcENBo9Wbl5kw==",
"dev": true,
"requires": {
"@types/filesystem": "*",
"@types/har-format": "*"
}
},
"@types/decompress": {
"version": "4.2.4",
"resolved": "https://registry.npmjs.org/@types/decompress/-/decompress-4.2.4.tgz",
@ -7460,21 +7418,6 @@
"integrity": "sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g==",
"dev": true
},
"@types/filesystem": {
"version": "0.0.36",
"resolved": "https://registry.npmjs.org/@types/filesystem/-/filesystem-0.0.36.tgz",
"integrity": "sha512-vPDXOZuannb9FZdxgHnqSwAG/jvdGM8Wq+6N4D/d80z+D4HWH+bItqsZaVRQykAn6WEVeEkLm2oQigyHtgb0RA==",
"dev": true,
"requires": {
"@types/filewriter": "*"
}
},
"@types/filewriter": {
"version": "0.0.33",
"resolved": "https://registry.npmjs.org/@types/filewriter/-/filewriter-0.0.33.tgz",
"integrity": "sha512-xFU8ZXTw4gd358lb2jw25nxY9QAgqn2+bKKjKOYfNCzN4DKCFetK7sPtrlpg66Ywe3vWY9FNxprZawAh9wfJ3g==",
"dev": true
},
"@types/got": {
"version": "8.3.6",
"resolved": "https://registry.npmjs.org/@types/got/-/got-8.3.6.tgz",
@ -7485,12 +7428,6 @@
"@types/node": "*"
}
},
"@types/har-format": {
"version": "1.2.16",
"resolved": "https://registry.npmjs.org/@types/har-format/-/har-format-1.2.16.tgz",
"integrity": "sha512-fluxdy7ryD3MV6h8pTfTYpy/xQzCFC7m89nOH9y94cNqJ1mDIDPut7MnRHI3F6qRmh/cT2fUjG1MLdCNb4hE9A==",
"dev": true
},
"@types/minimatch": {
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.5.tgz",

View File

@ -1,23 +1,15 @@
{
"name": "rentgen",
"version": "0.1.10",
"description": "Rentgen is an add-on prepared for both Firefox-based and Chromium-based browsers. This extension will automatically visualize all the data that a given website sends to third parties.",
"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.",
"main": "esbuild.config.js",
"type": "module",
"scripts": {
"build": "node esbuild.config.js",
"build:firefox": "TARGET=firefox node esbuild.config.js",
"build:chrome": "TARGET=chrome node esbuild.config.js",
"watch": "node esbuild.config.js --watch",
"watch:firefox": "TARGET=firefox node esbuild.config.js --watch",
"watch:chrome": "TARGET=chrome node esbuild.config.js --watch",
"ext-test": "web-ext run",
"build-addon": "npm i && npm run build && npm run create-package",
"build-addon:firefox": "npm i && npm run build:firefox && npm run create-package:firefox",
"build-addon:chrome": "npm i && npm run build:chrome && npm run create-package:chrome",
"create-package": "web-ext build --ignore-files '!**/node_modules' '!**/node_modules/**/react-dom' '!**/node_modules/**/react-dom/umd' '!**/node_modules/**/*/react-dom.production.min.js' '!**/node_modules/**/react' '!**/node_modules/**/react/umd' '!**/node_modules/**/*/react.production.min.js' '!**/node_modules/**/survey-react' '!**/node_modules/**/survey-react/*.min.js' '!**/node_modules/**/survey-react/*.min.css' --overwrite-dest",
"create-package:firefox": "cd dist-firefox && web-ext build --overwrite-dest --artifacts-dir ../web-ext-artifacts",
"create-package:chrome": "cd dist-chrome && 7z a -tzip ../web-ext-artifacts/rentgen-chrome-0.1.10.zip * && cd ..",
"typecheck": "tsc --noEmit",
"lint": "web-ext lint"
},
@ -51,7 +43,6 @@
"tracking"
],
"devDependencies": {
"@types/chrome": "^0.1.3",
"@types/events": "^3.0.0",
"@types/react-dom": "^17.0.9",
"addons-linter": "^4.7.0",

View File

@ -1,7 +1,6 @@
import { FakeRequestClusterData } from './components/report-window/fake-clusters';
import ExtendedRequest from './extended-request';
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';
@ -172,13 +171,7 @@ export class RequestCluster extends SaferEmitter {
return this.requests.map((request) => request.getMarkedEntries()).reduce(reduceConcat, []);
}
exposesOriginWhere(): DataLocation[] {
return this.requests
.map((request) => request.exposesOriginWhere())
.filter((l) => l !== null) as DataLocation[];
}
exposesOrigin(): boolean {
exposesOrigin() {
return this.requests.some((request) => request.exposesOrigin());
}
@ -208,15 +201,4 @@ 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,12 +33,6 @@ 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;
@ -259,12 +253,4 @@ 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,6 +1,5 @@
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;
@ -78,7 +77,7 @@ export function useEmitter(
export function parseCookie(cookie: string): Record<string, string> {
return cookie
.split(';')
.map((l) => [l.slice(0, l.indexOf('=')), l.slice(l.indexOf('=') + 1)])
.map((l) => l.split('='))
.reduce(
(acc, [key, value]) => ({
...acc,
@ -284,37 +283,8 @@ export function normalizeForClassname(string: string) {
}
export function wordlist(words: string[]) {
return Array.from(new Set(words)).reduce(
(acc, word, i) =>
`${acc}${
i > 0 ? (i < words.length - 1 ? ', ' : Math.random() > 0.5 ? ' i ' : ' oraz ') : ''
}${word}`,
return words.reduce(
(acc, word, i) => `${acc}${i > 0 ? (i < words.length - 1 ? ',' : ' i') : ''} ${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);
}